【问题标题】:Import XML files to PostgreSQL将 XML 文件导入 PostgreSQL
【发布时间】:2013-10-01 05:16:56
【问题描述】:

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 将整个文件一次导入单行。

【问题讨论】:

  • dba SO 站点上的this 线程有帮助吗?

标签: xml bash postgresql


【解决方案1】:

我使用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

【讨论】:

    【解决方案2】:

    我会尝试不同的方法:将 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;
       ...
    

    您需要超级用户权限,并且文件必须是数据库服务器本地,位于可访问的目录中。
    包含更多解释和链接的完整代码示例:

    【讨论】:

    • 有没有类似的方法可以在没有 xpath 的情况下只接收整个文件?
    • 值得指出的是,这仅适用于服务器本身,因为pg_read_file 在服务器上读取。
    • @BenCollins:是的,值得注意。链接的答案说明了很多。我也在这里添加了一个注释。
    • 导入 100Gb xml 文件怎么样?
    【解决方案3】:

    死灵术: 对于那些需要工作示例的人:

    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 中传递空值的正确方法是 &lt;elementName xsi:nil="true" /&gt; 而不是 &lt;elementName /&gt; 或什么都没有。在属性中传递 NULL 没有正确的方法(您只能省略该属性,但是在大型数据集中推断列数及其名称变得困难/缓慢)。

    例如

    <?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) 
    

    【讨论】:

      【解决方案4】:

      扩展@stefan-steiger 的出色答案,这是一个从包含多个兄弟节点的子节点中提取 XML 元素的示例(例如,对于特定的 &lt;synomyms&gt; 父节点,多个 &lt;synonym&gt; 元素)。

      我的数据遇到了这个问题,并搜索了很多解决方案;他的回答对我最有帮助。

      示例数据文件,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 编辑“第2 行”)也——巧合的是,在这种情况下——将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]#
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-06
      • 1970-01-01
      相关资源
      最近更新 更多