我有一个大型(2500 万行)数据文件,该文件以竖线 ( |
) 分隔。数据供应商提供文件,我运行自动化作业将文件加载到 Redshift 数据库,然后处理数据。
以下是数据示例:
123|110092|ACCT|"HC Account"|"Account1||||||||||||"Mary"|||"|"||||132|"STE|504"|1253|Unspecified Account|||N||ACTV|Active||||04/30/2013|12/31/2099|||||||||||||
到目前为止,我看到过三组现场数据:
- 文本字段用双引号 (
"
) 括起来,例如:"HC Account"
、"Mary"
和"|"
。这是正确的,应该在不使用引号的情况下加载数据。 - 有些值会包含竖线分隔符。例如:
"STE|504"
。在这种情况下,字段必须用双引号括起来。如果不是,则属于下面的第三类。 - 有时只提供开始引语而没有结束引语。例如:
"Account1
。
TL;DR:任何以 开头的字段都
|"
必须以 结尾"|
。如果没有,并且|"
遇到另一个,则必须对第一个双引号进行转义。
因此,在我使用 Unix/Python/其他建议对数据行进行预处理后,数据行应该被编辑为以下内容:
123|110092|ACCT|"HC Account"|"Account1||||||||||||"Mary"|||"|"||||132|"STE|504"|1253|Unspecified Account|||N||ACTV|Active||||04/30/2013|12/31/2099|||||||||||||
我计划编写一个 Unix 脚本来使用 SED 修改文件。到目前为止,我编写的正则表达式是:
(\|")(?!([a-zA-Z0-9]|\s|\||\/)*("\|))
但这无法正确匹配字符串。
以下是我正在测试的链接:https://regexr.com/3toib
我想保持代码轻量,因为平均文件大小在 3-5 GB 之间,并且通常有多个(10+)这样的文件。
PS Redshift 是使用 Postgre SQL 引擎的 AWS 数据库服务,能够从正确引用的字段中删除引号,并使用 转义引号的特殊含义\
。
此外,由于代码量很轻,我愿意使用 Python 或任何其他脚本语言来执行此操作。
答案1
您为数据提供的规范存在一个大问题。如果"|"
是有效字符串,或者更准确地说,允许带引号的字符串以竖线开头,那么如果缺少结尾引号的字符串(例如)的"Account1
第一个后续带引号字段以竖线开头(例如)"|Mary"
,则无法确定在所有情况下如果是"|
的结束引文|"Account1||||||||||||"|
或的开始引文|"|Mary"|
。
例如,使用缩短的(为了可读性)稍微修改过的数据版本,其中从第二个开始的所有带引号的字符串都以竖线开头,并且缺少结尾的引号
123|110092|ACCT|"HC Account"|"Account1||||||||||||"|Mary|||"|||||132|"|STE|504|1253
可以看出,这将被错误地解释为
123
110092
ACCT
"HC Account"
"Account1||||||||||||"
Mary
"|||||132|"
STE
504
1253
请注意,无论使用正则表达式、Python 还是其他语言,这都是一个问题。一般情况下的问题能可以“解决”,但它会很复杂,需要了解每行有多少个字段以及这些字段的数据结构。(并且可能总是有一些边缘情况没有得到解决。)
话虽如此,一个正则表达式解决方案至少可以检测最多开头双引号缺少结尾双引号的情况需要采用多遍方法,因为正则表达式需要捕获从每行开头到第一个未处理的不匹配开头双引号的所有文本。(否则,正如您的正则表达式所示,即使在最简单的情况下也会发现误报。)
所需的传递次数是整个文件中任何一行中仅打开引号的字段的最大数量加一。终止每个文件的处理需要检测正则表达式何时不再对文件进行修改。
这是适用于大多数情况的最简单的正则表达式:
Capturing Group 1 Capturing Group 2
(All previous valid fields) (Unclosed opening quote)
__________________________|_________________________ |
| || |
^((?:(?:(?!")[^|\r\n]*|"[^"\r\n]*"(?=$|\|))(?:$|\|))*+)(")
|____________| |_________________| |______|
| | |
Unquoted field OR Quoted field EOL or hypen delimiter
将其与以下替换字符串一起使用:
$1\\$2