如何解决表示允许重复列/键名的数据映射的通用格式 自定义格式格式解析构建 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 举报,一经查实,本站将立刻删除。