我想在表xml_data
中导入很多XML文件:
create table xml_data(result xml);
要做到这一点,我有一个带循环的简单bash脚本:
#!/bin/sh
FILES=/folder/with/xml/files/*.xml
for f in $FILES
do
psql psql -d mydb -h myhost -U usr -c \'\copy xml_data from $f \'
done
但是,这会尝试将每个文件的每一行导入为单独的行。这会导致错误:
ERROR: invalid XML content
CONTEXT: COPY address_results, line 1, column result: "<?xml version="1.0" encoding="UTF-8"?>"
我理解它失败的原因,但无法弄清楚如何让\copy
一次将整个文件导入单行。
答案 0 :(得分:13)
Necromancing: 对于那些需要一个工作实例的人:
DO $$
DECLARE myxml xml;
BEGIN
myxml := XMLPARSE(DOCUMENT convert_from(pg_read_binary_file('MyData.xml'), 'UTF8'));
DROP TABLE IF EXISTS mytable;
CREATE TEMP TABLE mytable AS
SELECT
(xpath('//ID/text()', x))[1]::text AS id
,(xpath('//Name/text()', x))[1]::text AS Name
,(xpath('//RFC/text()', x))[1]::text AS RFC
,(xpath('//Text/text()', x))[1]::text AS Text
,(xpath('//Desc/text()', x))[1]::text AS Desc
FROM unnest(xpath('//record', myxml)) x
;
END$$;
SELECT * FROM mytable;
或者噪音较小
SELECT
(xpath('//ID/text()', myTempTable.myXmlColumn))[1]::text AS id
,(xpath('//Name/text()', myTempTable.myXmlColumn))[1]::text AS Name
,(xpath('//RFC/text()', myTempTable.myXmlColumn))[1]::text AS RFC
,(xpath('//Text/text()', myTempTable.myXmlColumn))[1]::text AS Text
,(xpath('//Desc/text()', myTempTable.myXmlColumn))[1]::text AS Desc
,myTempTable.myXmlColumn as myXmlElement
FROM unnest(
xpath
( '//record'
,XMLPARSE(DOCUMENT convert_from(pg_read_binary_file('MyData.xml'), 'UTF8'))
)
) AS myTempTable(myXmlColumn)
;
使用此示例XML文件(MyData.xml):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data-set>
<record>
<ID>1</ID>
<Name>A</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Address record</Text>
<Desc>Returns a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host, but it is also used for DNSBLs, storing subnet masks in RFC 1101, etc.</Desc>
</record>
<record>
<ID>2</ID>
<Name>NS</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Name server record</Text>
<Desc>Delegates a DNS zone to use the given authoritative name servers</Desc>
</record>
</data-set>
注意:
MyData.xml需要位于PG_Data目录(pg_stat目录的父目录)中。
例如/var/lib/postgresql/9.3/main/MyData.xml
这需要PostGreSQL 9.1 +
总的来说,你可以实现无文件化,如下所示:
SELECT
(xpath('//ID/text()', myTempTable.myXmlColumn))[1]::text AS id
,(xpath('//Name/text()', myTempTable.myXmlColumn))[1]::text AS Name
,(xpath('//RFC/text()', myTempTable.myXmlColumn))[1]::text AS RFC
,(xpath('//Text/text()', myTempTable.myXmlColumn))[1]::text AS Text
,(xpath('//Desc/text()', myTempTable.myXmlColumn))[1]::text AS Desc
,myTempTable.myXmlColumn as myXmlElement
-- Source: https://en.wikipedia.org/wiki/List_of_DNS_record_types
FROM unnest(xpath('//record',
CAST('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data-set>
<record>
<ID>1</ID>
<Name>A</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Address record</Text>
<Desc>Returns a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host, but it is also used for DNSBLs, storing subnet masks in RFC 1101, etc.</Desc>
</record>
<record>
<ID>2</ID>
<Name>NS</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Name server record</Text>
<Desc>Delegates a DNS zone to use the given authoritative name servers</Desc>
</record>
</data-set>
' AS xml)
)) AS myTempTable(myXmlColumn)
;
请注意,与MS-SQL不同,xpath text()在NULL值上返回NULL,而不是空字符串。
如果由于某种原因你需要显式检查是否存在NULL,你可以使用[not(@xsi:nil="true")]
,你需要传递一个命名空间数组,否则你会得到一个错误(但是,你可以省略所有名称空间但是xsi)。
SELECT
(xpath('//xmlEncodeTest[1]/text()', myTempTable.myXmlColumn))[1]::text AS c1
,(
xpath('//xmlEncodeTest[1][not(@xsi:nil="true")]/text()', myTempTable.myXmlColumn
,
ARRAY[
-- ARRAY['xmlns','http://www.w3.org/1999/xhtml'], -- defaultns
ARRAY['xsi','http://www.w3.org/2001/XMLSchema-instance'],
ARRAY['xsd','http://www.w3.org/2001/XMLSchema'],
ARRAY['svg','http://www.w3.org/2000/svg'],
ARRAY['xsl','http://www.w3.org/1999/XSL/Transform']
]
)
)[1]::text AS c22
,(xpath('//nixda[1]/text()', myTempTable.myXmlColumn))[1]::text AS c2
--,myTempTable.myXmlColumn as myXmlElement
,xmlexists('//xmlEncodeTest[1]' PASSING BY REF myTempTable.myXmlColumn) AS c1e
,xmlexists('//nixda[1]' PASSING BY REF myTempTable.myXmlColumn) AS c2e
,xmlexists('//xmlEncodeTestAbc[1]' PASSING BY REF myTempTable.myXmlColumn) AS c1ea
FROM unnest(xpath('//row',
CAST('<?xml version="1.0" encoding="utf-8"?>
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<xmlEncodeTest xsi:nil="true" />
<nixda>noob</nixda>
</row>
</table>
' AS xml)
)
) AS myTempTable(myXmlColumn)
;
您还可以通过执行
检查XML文本中是否包含字段 ,xmlexists('//xmlEncodeTest[1]' PASSING BY REF myTempTable.myXmlColumn) AS c1e
例如,当您将XML值传递给CRUD的存储过程/函数时。 (见上文)
另请注意,在XML中传递空值的正确方法是<elementName xsi:nil="true" />
而不是<elementName />
或者没有。没有正确的方法在属性中传递NULL(您只能省略该属性,但是在大型数据集中推断列数及其名称变得困难/缓慢)。
e.g。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<table>
<row column1="a" column2="3" />
<row column1="b" column2="4" column3="true" />
</table>
(更紧凑,但是如果你需要导入它会非常糟糕,特别是如果来自具有多GB数据的XML文件 - 请参阅stackoverflow数据转储中的一个很好的例子)
SELECT
myTempTable.myXmlColumn
,(xpath('//@column1', myTempTable.myXmlColumn))[1]::text AS c1
,(xpath('//@column2', myTempTable.myXmlColumn))[1]::text AS c2
,(xpath('//@column3', myTempTable.myXmlColumn))[1]::text AS c3
,xmlexists('//@column3' PASSING BY REF myTempTable.myXmlColumn) AS c3e
,case when (xpath('//@column3', myTempTable.myXmlColumn))[1]::text is null then 1 else 0 end AS is_null
FROM unnest(xpath('//row', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<table>
<row column1="a" column2="3" />
<row column1="b" column2="4" column3="true" />
</table>'
)) AS myTempTable(myXmlColumn)
答案 1 :(得分:11)
我会尝试不同的方法:将XML文件直接读入plpgsql函数内的变量并从那里继续。应该快得多并且更强大。
CREATE OR REPLACE FUNCTION f_sync_from_xml()
RETURNS boolean AS
$BODY$
DECLARE
myxml xml;
datafile text := 'path/to/my_file.xml';
BEGIN
myxml := pg_read_file(datafile, 0, 100000000); -- arbitrary 100 MB max.
CREATE TEMP TABLE tmp AS
SELECT (xpath('//some_id/text()', x))[1]::text AS id
FROM unnest(xpath('/xml/path/to/datum', myxml)) x;
...
您需要超级用户权限,并且文件必须是本地数据库服务器,位于可访问目录中。
完整的代码示例以及更多解释和链接:
答案 2 :(得分:4)
扩展@ stefan-steiger的优秀答案,这是一个从包含多个兄弟节点(例如,多个<synonym>
元素的子节点中提取XML元素的示例,用于特定的<synomyms>
父节点节点)。
我的数据遇到了这个问题,搜索了很多解决方案;对我来说,他的回答是最有帮助的。
示例数据文件hmdb_metabolites_test.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<hmdb>
<metabolite>
<accession>HMDB0000001</accession>
<name>1-Methylhistidine</name>
<synonyms>
<synonym>(2S)-2-amino-3-(1-Methyl-1H-imidazol-4-yl)propanoic acid</synonym>
<synonym>1-Methylhistidine</synonym>
<synonym>Pi-methylhistidine</synonym>
<synonym>(2S)-2-amino-3-(1-Methyl-1H-imidazol-4-yl)propanoate</synonym>
</synonyms>
</metabolite>
<metabolite>
<accession>HMDB0000002</accession>
<name>1,3-Diaminopropane</name>
<synonyms>
<synonym>1,3-Propanediamine</synonym>
<synonym>1,3-Propylenediamine</synonym>
<synonym>Propane-1,3-diamine</synonym>
<synonym>1,3-diamino-N-Propane</synonym>
</synonyms>
</metabolite>
<metabolite>
<accession>HMDB0000005</accession>
<name>2-Ketobutyric acid</name>
<synonyms>
<synonym>2-Ketobutanoic acid</synonym>
<synonym>2-Oxobutyric acid</synonym>
<synonym>3-Methyl pyruvic acid</synonym>
<synonym>alpha-Ketobutyrate</synonym>
</synonyms>
</metabolite>
</hmdb>
除此之外:原始XML文件在文档元素中有一个URL
<hmdb xmlns="http://www.hmdb.ca">
阻止xpath
解析数据。它将运行(没有错误消息),但关系/表是空的:
[hmdb_test]# \i /mnt/Vancouver/Programming/data/hmdb/sql/hmdb_test.sql
DO
accession | name | synonym
-----------+------+---------
由于源文件是3.4GB,我决定使用sed
编辑该行:
sed -i '2s/.*hmdb xmlns.*/<hmdb>/' hmdb_metabolites.xml
[添加2
(指示sed
编辑&#34;第2行&#34;) - 巧合的是,在这种情况下 - 加倍sed
命令执行速度。]
我的postgres数据文件夹(PSQL:SHOW data_directory;
)是
/mnt/Vancouver/Programming/RDB/postgres/postgres/data
所以,作为sudo
,我需要将我的XML数据文件复制到那里并chown
将其用于PostgreSQL:
sudo chown postgres:postgres /mnt/Vancouver/Programming/RDB/postgres/postgres/data/hmdb_metabolites_test.xml
脚本(hmdb_test.sql
):
DO $$DECLARE myxml xml;
BEGIN
myxml := XMLPARSE(DOCUMENT convert_from(pg_read_binary_file('hmdb_metabolites_test.xml'), 'UTF8'));
DROP TABLE IF EXISTS mytable;
-- CREATE TEMP TABLE mytable AS
CREATE TABLE mytable AS
SELECT
(xpath('//accession/text()', x))[1]::text AS accession
,(xpath('//name/text()', x))[1]::text AS name
-- The "synonym" child/subnode has many sibling elements, so we need to
-- "unnest" them,otherwise we only retrieve the first synonym per record:
,unnest(xpath('//synonym/text()', x))::text AS synonym
FROM unnest(xpath('//metabolite', myxml)) x
;
END$$;
-- select * from mytable limit 5;
SELECT * FROM mytable;
执行,输出(在PSQL
中):
[hmdb_test]# \i /mnt/Vancouver/Programming/data/hmdb/hmdb_test.sql
accession | name | synonym
-------------+--------------------+----------------------------------------------------------
HMDB0000001 | 1-Methylhistidine | (2S)-2-amino-3-(1-Methyl-1H-imidazol-4-yl)propanoic acid
HMDB0000001 | 1-Methylhistidine | 1-Methylhistidine
HMDB0000001 | 1-Methylhistidine | Pi-methylhistidine
HMDB0000001 | 1-Methylhistidine | (2S)-2-amino-3-(1-Methyl-1H-imidazol-4-yl)propanoate
HMDB0000002 | 1,3-Diaminopropane | 1,3-Propanediamine
HMDB0000002 | 1,3-Diaminopropane | 1,3-Propylenediamine
HMDB0000002 | 1,3-Diaminopropane | Propane-1,3-diamine
HMDB0000002 | 1,3-Diaminopropane | 1,3-diamino-N-Propane
HMDB0000005 | 2-Ketobutyric acid | 2-Ketobutanoic acid
HMDB0000005 | 2-Ketobutyric acid | 2-Oxobutyric acid
HMDB0000005 | 2-Ketobutyric acid | 3-Methyl pyruvic acid
HMDB0000005 | 2-Ketobutyric acid | alpha-Ketobutyrate
[hmdb_test]#
答案 3 :(得分:0)
我使用tr
用空格替换所有换行符。这将创建仅包含一行的XML文件。这样的文件我可以使用\copy
轻松导入一行。
显然,如果你在XML中有多行值,这不是一个好主意。幸运的是,这不是我的情况。
要导入文件夹中的所有XML文件,您可以使用此bash脚本:
#!/bin/sh
FILES=/folder/with/xml/files/*.xml
for f in $FILES
do
tr '\n' ' ' < $f > temp.xml
psql -d database -h localhost -U usr -c '\copy xml_data from temp.xml'
done