在雪花中解析具有未知结构的 XML

时间:2021-04-27 13:16:10

标签: sql xml snowflake-cloud-data-platform

我有一堆 XML 文件,它们通过嵌套标签定义树层次结构(ID:s 之间的关系)。我想使用用于半结构化数据的 Snowflake 的 SQL 语法将其解析为表格格式。对于结构已知的 XML 文件,我知道该怎么做。但是对于这些树,在解析时结构是未知的,在这种情况下我不知道如何解决它。重复的通用模式是

<Nodes>
    <Node>
        ...
    </Node>
</Nodes>

请参阅下面的示例数据和所需的输出。

有没有办法使用 Snowflake 的 SQL 语法来实现这一点?

示例数据:

<Nodes>
    <Node Id="1">
        <Nodes>
            <Node Id="2">
            </Node>
            <Node Id="3">
                <Nodes>
                    <Node Id="4">
                    </Node>
                    <Node Id="5">
                        <Nodes>
                            <Node Id="6">
                            </Node>
                        </Nodes>
                    </Node>
                    <Node Id="7">
                    </Node>
                </Nodes>
            </Node>
            <Node Id="8">
            </Node>
        </Nodes>
    </Node>
    <Node Id="9">
        <Nodes>
            <Node Id="10">
            </Node>
        </Nodes>
    </Node>
</Nodes>

所需的表格输出是:

|-----------|---------|
| parent_id | node_id |
|-----------|---------|
|      null |       1 |
|         1 |       2 |
|         1 |       3 |
|         3 |       4 |
|         3 |       5 |
|         5 |       6 |
|         3 |       7 |
|         1 |       8 |
|      null |       9 |
|         9 |      10 |
|-----------|---------|

1 个答案:

答案 0 :(得分:1)

所以 RECURSIVE 是您想在此处使用的 FLATTEN 属性:

with data as (
    select parse_xml('<Nodes>
    <Node Id="1">
        <Nodes>
            <Node Id="2">
            </Node>
            <Node Id="3">
                <Nodes>
                    <Node Id="4">
                    </Node>
                    <Node Id="5">
                        <Nodes>
                            <Node Id="6">
                            </Node>
                        </Nodes>
                    </Node>
                    <Node Id="7">
                    </Node>
                </Nodes>
            </Node>
            <Node Id="8">
            </Node>
        </Nodes>
    </Node>
    <Node Id="9">
        <Nodes>
            <Node Id="10">
            </Node>
        </Nodes>
    </Node>
</Nodes>') as xml
)
select 
    GET(f.value, '@Id') as id
    ,f.path as path
    ,len(path) as p_len
from data,
    TABLE(FLATTEN(INPUT=>get(xml,'$'), recursive=>true)) f
    where get(f.value, '@') = 'Node'
;

给出:

ID  PATH    P_LEN
1   [0] 3
2   [0]['$']['$'][0]    16
3   [0]['$']['$'][1]    16
4   [0]['$']['$'][1]['$']['$'][0]   29
5   [0]['$']['$'][1]['$']['$'][1]   29
6   [0]['$']['$'][1]['$']['$'][1]['$']['$'] 39
7   [0]['$']['$'][1]['$']['$'][2]   29
8   [0]['$']['$'][2]    16
9   [1] 3
10  [1]['$']['$']   13

由此您现在可以通过查找路径的所有匹配项并采用最长匹配项来重建层次结构。

你可以做一个双嵌套循环,如:

select 
    GET(f1.value, '@Id') as id
    ,GET(f2.value, '@Id') as id
    ,f1.value
    ,f2.*
    , get(f2.value, '@') 
from data,
    TABLE(FLATTEN(INPUT=>get(xml,'$'), recursive=>true)) f1,
    TABLE(FLATTEN(INPUT=>GET(xmlget(f1.value,'Nodes'), '$'))) f2
    where get(f1.value, '@') = 'Node'
;

但它没有给你第一行,并且雪花在扩展节点时表现不同

<node>
  <nodes>
    <node></node>
  </nodes>
<node>

<node>
  <nodes>
    <node></node>
    <node></node>
  </nodes>
<node>

这意味着你必须尝试处理这两种情况,这真的很糟糕。

编辑:

所以你可以更近一些,但请注意,如果发生第二个子情况,你可以获得节点名称 get(f2.value, '@') = 'Node',因此我们可以将一些东西放入 IFF 中,在第一种情况下,{{扁平化的 1}} 是 value 因此我们可以硬编码获取父项 -> 节点 -> 节点,因此:

'Node'

给你:

select 
    GET(f1.value, '@Id') as parent_id
    ,iff(get(f2.value, '@')  = 'Node', GET(f2.value, '@Id'), GET(xmlget(xmlget(f1.value,'Nodes'),'Node'), '@Id')) as child_id
from data,
    TABLE(FLATTEN(INPUT=>get(xml,'$'), recursive=>true)) f1,
    TABLE(FLATTEN(INPUT=>GET(xmlget(f1.value,'Nodes'), '$'))) f2
    where get(f1.value, '@') = 'Node'
    and (get(f2.value, '@')  = 'Node' OR f2.value = 'Node')
;

仅缺少您想要的 PARENT_ID CHILD_ID 1 2 1 3 1 8 3 4 3 5 3 7 5 6 9 10 NULL, 1 行。

编辑 2

所以回到我最初的建议,将节点 ID 和路径拉出,然后在节点上使用 QUALIFY 进行 LEFT JOIN 以保持最长匹配可以像这样完成,并提供所需的输出:

NULL, 9

给出:

with data as (
    select parse_xml('<Nodes>
    <Node Id="1">
        <Nodes>
            <Node Id="2">
            </Node>
            <Node Id="3">
                <Nodes>
                    <Node Id="4">
                    </Node>
                    <Node Id="5">
                        <Nodes>
                            <Node Id="6">
                            </Node>
                        </Nodes>
                    </Node>
                    <Node Id="7">
                    </Node>
                </Nodes>
            </Node>
            <Node Id="8">
            </Node>
        </Nodes>
    </Node>
    <Node Id="9">
        <Nodes>
            <Node Id="10">
                     </Node>
        </Nodes>
    </Node>
</Nodes>') as xml
), nodes AS (
select 
    GET(f1.value, '@Id') as id
    ,f1.path as path
    ,len(path) as l_path
from data,
    TABLE(FLATTEN(INPUT=>get(xml,'$'), recursive=>true)) f1
    where get(f1.value, '@') = 'Node'
)
SELECT p.id as parent_id
    ,c.id as child_id
FROM nodes c
LEFT JOIN nodes p
    ON LEFT(c.path,p.l_path) = p.path AND c.id <> p.id
QUALIFY row_number() over (partition by c.id order by p.l_path desc ) = 1
;