MySQL LOAD DATA INFILE:可以工作,但不可预测的行终止符

18

MySQL有一个很好的CSV导入功能LOAD DATA INFILE

我有一个需要定期从CSV导入的大型数据集,因此这个功能正是我所需要的。我已经有一个可以完美导入我的数据的工作脚本。

.....除了.... 我不知道行尾终止符会是什么。

目前我的SQL代码看起来像这样:

LOAD DATA INFILE '{fileName}'
 INTO TABLE {importTable}
 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
 LINES TERMINATED BY '\n'
 IGNORE 1 LINES
( {fieldList} );

这对于某些导入文件非常有效。

然而,导入的数据来自多个来源。其中一些具有\n终止符号;另一些则具有\r\n
我无法预测我会得到哪一个。

是否可以使用LOAD DATA INFILE来指定我的行可能以\n\r\n中的任何一个终止符号作为结尾?我该如何处理这种情况?


你是否只需要处理 \n(大多数*nix系统,包括OS X)和\r\n(Windows)--如果是这种情况,@Devart的答案看起来完美无缺--或者你可能会遇到其他行终止序列,例如\n\r(例如来自RISC OS),\r(例如Mac OS 9)和其他一些? - eggyal
@eggyal - 这是一个非常好的问题。我只考虑了*nix/Windows的情况,但正如我所说,导入文件的创建不在我的控制范围之内,所以我想我需要为任何情况提供解决方案。我选择了一个基本的预处理解决方案:Devart的解决方案看起来很棒,但有太多弱点,无法使其足够健壮。 - SDC
遗憾的是,在这种情况下,我认为没有简单的解决方案。可以解析文件并计算每个候选行终止序列的出现次数,以尝试猜测它使用的是哪个,但要注意包含多行文本字段的文件,其中行终止符与实际记录终止符不同(您可能需要计算/验证每个记录终止符之间出现了预期数量的字段)。我不知道您的应用程序使用的是什么代码/框架,但可能有库可以在此处帮助您。否则,请提示用户告诉您它是哪个。 - eggyal
7个回答

11
你可以在加载时将行分隔符指定为'\n',并从最后一个字段中删除尾随的'\r'分隔符(如果必要)。
例如:
假设我们有一个名为'entries.txt'的文件。 行分隔符是'\r\n',只有在行ITEM2 | CLASS3 | DATE2之后分隔符才是'\n':
COL1  | COL2   | COL3
ITEM1 | CLASS1 | DATE1
ITEM2 | CLASS3 | DATE2
ITEM3 | CLASS1 | DATE3
ITEM4 | CLASS2 | DATE4

CREATE TABLE语句:

CREATE TABLE entries(
  column1 VARCHAR(255) DEFAULT NULL,
  column2 VARCHAR(255) DEFAULT NULL,
  column3 VARCHAR(255) DEFAULT NULL
)

我们的LOAD DATA INFILE查询:

LOAD DATA INFILE 'entries.txt' INTO TABLE entries
FIELDS TERMINATED BY '|'
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(column1, column2, @var)
SET column3 = TRIM(TRAILING '\r' FROM @var);

显示结果:

SELECT * FROM entries;
+---------+----------+---------+
| column1 | column2  | column3 |
+---------+----------+---------+
| ITEM1   |  CLASS1  |  DATE1  |
| ITEM2   |  CLASS3  |  DATE2  |
| ITEM3   |  CLASS1  |  DATE3  |
| ITEM4   |  CLASS2  |  DATE4  |
+---------+----------+---------+

啊,现在看起来很有前途... :-) 我会进一步调查并告诉你结果。谢谢你的建议。 - SDC
如果CSV中的COL3被引用了,会怎样呢?当MySQL在引用字段结束和行终止符之间遇到意外字符时,这不会导致解析器错误吗?如果column3是未引用的文本,并且应该\r字符结尾(可能最好做一些稍微丑陋的事情,涉及测试最后一个字符,如果是\r,则仅删除该字符)? - eggyal
我也考虑过引用的COL3。我尝试了一些变体,并且可以说,引用的COL3会导致行终止问题。因此,最后一个字段应该是未引用的。关于 ...\r\r\r - 是的,这种方法可以重写以仅删除最后一个 \r - Devart
在没有接受的情况下,我不知道这是否有效,并且我意识到评论中提出的观点,但我授予您赏金,因为这是唯一尝试在SQL查询范围内解决问题的答案。预处理文件始终是最简单的方法,但很高兴看到仅使用SQL的创造性解决方案。谢谢。 - Spudley
我选择了一个简单的预处理解决方案。我真的很喜欢你在这里提出的想法,但是像上面的评论一样,我不能指望它适用于所有输入。导入文件来自多个第三方,因此我无法对内容进行足够的控制,以确保不会被引号等绊倒。(我猜如果我有那种控制权,这个问题也不会被问到)。但是感谢您的建议;它很有趣和创造性。 - SDC
是的,这个解决方案并不涵盖所有情况。它只是一种在这个问题中使用MySQL的方法。无论如何,感谢您的慷慨奖励! - Devart

10

我只需要对其进行预处理。将\r\n替换为\n的全局搜索/替换操作可作为导入过程的一部分通过命令行工具轻松地实现,简单且高效。


1
目前看来这是我最好的选择。我本来希望有一个纯SQL的解决方案,但如果没有,那么一个脚本预处理步骤就可以了。我会等待看看是否有更好的答案出现,但如果没有,那么这可能就是最好的选择了。 - SDC
没错。大多数从各种来源提取数据的ETL系统最终都需要进行一些预处理。如果你只需要规范化行终止符,那么你应该庆幸自己很幸运 ;) - codemonkey
尽管@Devart的答案表面上看起来不错,但我选择了一个简单的预处理工具。它似乎更加健壮,正如你所说,性能也相当不错。本来很想看到一个纯SQL解决方案,但看起来并没有这样的解决方案。(看起来LOAD DATA INFILE无法接受任何行终止符选项似乎很奇怪,但它确实没有,所以就这样吧) - SDC

3

我假设您只需要通过mysql获取信息,而不是通过任何编程语言。

在使用load data之前,如果您使用的是notepad++,请将格式转换为windows格式\r\n(CR LF)。然后处理Load data查询。确保LINES TERMINATED BY '\r\n'。

enter image description here

编辑:

由于编辑器通常不适合转换较大的文件。对于较大的文件,经常使用以下命令,无论是在windows还是linux中。

1)在Windows中将其转换为Windows格式

TYPE [unix_file] | FIND "" /V > dos_file

2) 在Linux中将文件转换为Windows格式

unix2dos  [file]

其他可用的命令如下:

可以通过 tr -d '\r' < inputfile > outputfile 命令将Windows格式文件转换为Unix格式,只需删除所有ASCII CR \r字符即可。

grep -PL $'\r\n' myfile.txt # show UNIX format  style file (LF terminated)
grep -Pl $'\r\n' myfile.txt # show WINDOS format style file (CRLF terminated)

在Linux/Unix系统中,file命令可以检测使用的换行符类型。因此,可以使用该命令来检查文件类型。

这不是我希望得到的答案,但目前看来这种转换方式似乎是最好的选择。不过不要使用像Notepad++这样的程序,因为文件大小达到了几百兆;最好使用命令行工具。 - SDC
@SDC 请参见编辑部分,了解Windows和Unix命令行转换。 - Prathap

1
如果第一次加载没有0行,则使用其他行终止符执行相同的语句。这应该可以通过一些基本的计数逻辑来完成。
至少它都在SQL中,如果第一次就成功了,那么你就赢了。并且可能会比重新扫描所有行并删除特定字符更少地引起头痛。

1
您也可以考虑使用其中一个数据集成软件包。Talend Open Studio具有非常灵活的数据输入程序。例如,您可以使用一组分隔符处理文件,并捕获拒绝并以另一种方式处理它们。

1

为什么不先看一眼行末是如何的呢?

$handle = fopen('inputFile.csv', 'r');

$i = 0;
if ($handle) {
    while (($buffer = fgets($handle)) !== false) {

        $s =  substr($buffer,-50);

        echo $s; 
        echo preg_match('/\r/', $s) ? 'cr ' : '-- ';
        echo preg_match('/\n/', $s) ? 'nl<br>' : '--<br>';          

        if( $i++ > 5)
            break;

    }

    fclose($handle);
}

这帮助了我解决了这个问题 - 一旦确定了行尾,就可以将SQL命令包装成条件语句。 - Giles

0

您可以使用“LINES STARTING”来分隔文本中的常规行尾和新行:

LOAD DATA LOCAL INFILE '/home/laptop/Downloads/field3-utf8.csv' 
IGNORE INTO TABLE Field FIELDS 
TERMINATED BY ';' 
OPTIONALLY ENCLOSED BY '^' 
LINES STARTING BY '^' 
TERMINATED BY '\r\n' 
(Id, Form_id, Name, Value)

对于通常带有“引号”的CSV文件,它将是:

...
LINES STARTING BY '"' 
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接