原文出处: freenik@Jianshu
将 外部数据导入(import)数据库是在数据库应用中一个很常见的需求。其实这就是在数据的管理和操作中的ETL (Extract, transform, load)的L (Load)部分,也就是说,将特定结构(structure)或者格式(format)的数据导入某个目的地(比如数据库,这里我们讨论MySQL)。
本文要讨论的内容,是如何方便地将多种格式(JSON, Text, XML, CSV)的数据导入MySQL之中。
本文大纲:
这里我们的讨论是基于一个假定,Text file和CSV file是有着比较规范的格式的(properly formatted),比如说每行的每个数据域(field)之间是由一个共同的分隔符(比如tab: t)分隔的。
那么首先,你需要根据你的数据的格式(有哪些域),来设计好数据库的对应的表 (的Schema)。
举个例子,要处理的Text文件或者CSV文件是以t作为分隔符的,每行有id, name, balance这么三个数据域,那么首先我们需要在数据库中创建这个表:
CREATE
TABLE
sometable(id INT, name
VARCHAR(255), balance DECIMAL(8,4));
创建成功以后就可以导入了。操作方式很简单:
LOAD
DATA LOCAL
INFILE '你的文件路径(如~/file.csv)'
INTO
TABLE
sometable FIELDS TERMINATED BY
't'
[ENCLOSED BY
'"'(可选)] LINES TERMINATED BY
'n'
(id, name, balance)
这里要注意的是,我们需要开启local-infile这个MySQL的配置参数,才能够成功导入。究其原因,从MySQL的Manual中可以看到这么一段话:
LOCAL works only if your server and your client both have been configured to permit it. For example, if mysqld was started with –local-infile=0, LOCAL does not work. See Section 6.1.6, “Security Issues with LOAD DATA LOCAL”.
这是MySQL出于安全考虑的默认配置。因此,我们需要在配置文件my.cnf中(以Debian发行版的Linux, 如Ubuntu为例, 即是在/etc/my.cnf中),确保:
local-infile=1
抑或是在命令行启动MySQL时加上–local-infile这一项:
mysql --local-infile -uroot -pyourpwd yourdbname
此外,我们也可以使用MySQL的一个官方导入程序 mysqlimport,这个程序本质上就是为LOAD DATA FILE提供了一个命令行的interface,很容易理解,我们这里就不再详述。
这件事的完成方式,与我们的XML的形式有着很大的关系。
举个例子说,当你的XML数据文件有着很非常规范的格式,比如:
<?xml
version="1.0"?>
<row>
<field
name="id">1</field>
<field
name="name">Free</field>
<field
name="balance">2333.3333</field>
</row>
<row>
<field
name="id">2</field>
<field
name="name">Niki</field>
<field
name="balance">1289.2333</field>
</row>
或者
<row
column1="value1"
column2="value2"
.../>
我们就可以很方便使用LOAD XML来导入,这里可以参见MySQL的官方手册–LOAD XML Syntax。 然而我们可能有另外一些需求,比如说,我们可能会想要将XML文件的域映射到不同名字的列(TABLE COLUMN)之中。这里要注意,MySQL v5.0.7以后,MySQL的Stored Procedure中不能再运行LOAD XML INFILE
或者LOAD DATA INFILE
。所以转换的程序(procedure)的编写方式与在此之前有所不同。这里,我们需要使用Load_File()和ExtractValue()这两个函数。 以下是一个示例XML文件和程序:
文件:
<?xml
version="1.0"?>
<some_list>
<someone
id="1"
fname="Rob"
lname="Gravelle"/>
<someone
id="2"
fname="Al"
lname="Bundy"/>
<someone
id="3"
fname="Little"
lname="Richard"/>
</some_list>
程序:
DELIMITER $$
CREATE
DEFINER=`root`@`localhost` PROCEDURE
`import_some_xml`(path varchar(255), node varchar(255))
BEGIN
declare
xml_content text;
declare
v_row_index int
unsigned default
0;
declare
v_row_count int
unsigned;
declare
v_xpath_row varchar(255);
set
xml_content = load_file(path);
-- calculate the number of row elements.
set
v_row_count = extractValue(xml_content, concat('count(', node, ')'));
-- loop through all the row elements
while v_row_index < v_row_count do
set
v_row_index = v_row_index + 1;
set
v_xpath_row = concat(node, '[', v_row_index, ']/@*');
insert
into
applicants values
(
extractValue(xml_content, concat(v_xpath_row, '[1]')),
extractValue(xml_content, concat(v_xpath_row, '[2]')),
extractValue(xml_content, concat(v_xpath_row, '[3]'))
);
end
while;
END
我们知道MySQL的命令分隔符默认为分号,然而脚本中很显然是有分号的,但是我们并不希望立即执行,所以我们需要临时更改分隔符。