微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

表示允许重复列/键名的数据映射的通用格式 自定义格式格式解析构建 CSV 文件

如何解决表示允许重复列/键名的数据映射的通用格式 自定义格式格式解析构建 CSV 文件

上下文:
我实际上在 PHP 中有一个提取任务,它执行一个 sql 查询生成一个 CSV 文件。 CSV 列名称基于查询名称
CSV 必须包含很多列,并且某些列名称出现多次(2 或 3 次)。处理此提取的团队需要所有列,因为值可能会从重复列更改为其他列,并且顺序很重要。

sql 到 CSV:
我可以在具有重复名称sql 查询 SELECT 语句中设置别名:

SELECT
    col_a AS 'duplicate',col_b AS 'some_col','' AS 'duplicate',col_c AS 'other_col',...

使用 PDO 执行查询,并使用返回关联数组的经典 PDO::FETCH_ASSOC 获取。由于数组的键是唯一的,我丢失了重复的列:

[
    'duplicate' => 'something','some_col'  => 'value','other_col' => 'other value'
    ...
]

失败的解决方案:
使用 PDO::FETCH_NAMED 获取模式不起作用,因为我会丢失列的顺序,需要做更多的工作才能使其以可重用的方式工作(任务是通用 sql/CSV 提取)。

我可以将 SELECT 子句中的别名重命名为唯一,但随后我将无法使用 CSV 的列名。
除非我将 sql 列映射到 CSV 列。

我想过像 JSON 这样的格式,但这不会起作用,因为 JSON 数据不应该包含重复的键,而且无论如何我会在用 PHP 读取它们时丢失它们:

{
    "CSV_COL": "sql_COL","OTHER_CSV": "OTHER_sql","CSV_COL": "something_else"
}

是否有一种简单的格式可以允许 PHP 在读取时不会删除重复的列/键名?

解决方法

我用自定义格式解决了这个问题。这不是理想的解决方案,因为我更喜欢更标准的解决方案,但它有效。详情如下:

自定义格式

首先,表单域接受如下文本输入:

csv_column:sql_column

Name of CSV column   :   "some string"
 csv_column :

格式规则:

  • 每行将一个 CSV 列映射到一个 SQL 列/值
  • 空白行会被忽略但没问题
  • CSV 列与 SQL 列用 :
  • 分隔
  • 您可以在列名周围添加任意数量的空格
  • 您可以为 SQL 端定义默认字符串值,方法是用 '" 将值括起来
  • 如果 CSV 列必须为空,则 SQL 部分是可选的

格式解析

然后我写了一个函数来把这个字符串转换成一个多维数组:

/**
 * Parse CSV/SQL column mapping.
 * @param string $textMapping column mapping in text format
 * @return string[][]|null each array has a "CSV" & "SQL" key with corresponding column/value and a "type" key to
 *                         differentiate SQL columns from SQL values
 */
function getColumnMapping(string $textMapping): ?array
{
    // No mapping
    if (trim($textMapping) === '') {
        return null;
    }

    /*
     * RegEx explanation:
     *      \h*(.*\S)   optional whitepaces following by a CSV column name
     *                  (anything but does not end with whitespace)
     *      \h*:\h*     a colon optionally surrounded by whitespaces
     *      ([^\v]*)\h* an optional SQL column/value,optionally followed by whitespaces
     *                  (anything but line breaks)
     *      (?:\R|$)    a line break or the end of the string
     *
     * CSV parts are at index 1 and SQL parts are at index 2
     */
    preg_match_all('~\h*(.*\S)\h*:\h*([^\v]*)\h*(?:\R|$)~',$textMapping,$matches);

    // No mapping found
    if ([] === $matches[0]) {
        return null;
    }

    $mapping = [];

    for ($i = 0; $i < count($matches[0]); $i++) {
        $mappingItem = [
            'CSV' => $matches[1][$i],'SQL' => $matches[2][$i],'type' => 'column',];

        if ('' === $mappingItem['SQL']) {
            // Empty: empty value
            $mappingItem['type'] = 'value';
        } elseif (1 === preg_match('~^(\'|")(.*)\1$~',$mappingItem['SQL'],$match)) {
            // Enclosed by quotes: string value
            $mappingItem['SQL'] = $match[2];
            $mappingItem['type'] = 'value';
        }

        $mapping[] = $mappingItem;
    }

    return $mapping;
}

前面的示例将产生以下数组:

[
    [
        'CSV'  => 'csv_column','SQL'  => 'sql_column',],[
        'CSV'  => 'Name of CSV column','SQL'  => 'some string','type' => 'value',[
        'CSV'  => 'csv_column','SQL'  => '',]

构建 CSV 文件

最后,在执行 SQL 查询后,将每个结果传递给一个函数来构建数据行:

/**
 * Build a CSV data line based on a column mapping
 * @param array $resultRow SQL result row
 * @param array $mapping CSV/SQL column mapping
 * @return array formatted data
 */
function buildRowFromColumnMapping(array $resultRow,array $mapping): array
{
    $row = [];

    foreach ($mapping as $mappingItem) {
        if ($mappingItem['type'] === 'value') {
            $row[] = $mappingItem['SQL'];
        } elseif (false === array_key_exists($mappingItem['SQL'],$resultRow)) {
            throw new \LogicException(sprintf('Column "%s" not found.',$mappingItem['SQL']));
        } else {
            $row[] = $resultRow[$mappingItem['SQL']];
        }
    }

    return $row;
}

结果是一个一维数组,你可以发送到任何你想要的东西(fputcsv(),特定的库,...)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。