【问题标题】:How to get distinct elements with all values from all arrays XML in T-SQL如何从 T-SQL 中的所有数组 XML 中获取具有所有值的不同元素
【发布时间】:2018-09-27 02:29:06
【问题描述】:

我需要一些帮助来尝试导入以下 XML 文档

<ROOT>
<MeasuresList>
  <Measure ID="164">       
    <Names>
    <Name ID="1072687" langid="33"/>
    <Name ID="1017672" langid="13">miljoner skär</Name>
   </Names>
  </Measure>
  <Measure ID="362">       
   <Names>
    <Name ID="1072687" langid="33"/>
    <Name ID="1017672" langid="13">miljoner skär</Name>
    <Name ID="4068857" langid="19">hyller</Name>
    <Name ID="3330057" langid="18">ράφια</Name>
    <Name ID="3291105" langid="20">raflar</Name>
    <Name ID="2813622" langid="10"/>         
   </Names>       
  </Measure>
  <Measure ID="162">       
   <Names>
    <Name ID="1072687" langid="33"/>
    <Name ID="1017672" langid="13">miljoner skär</Name>
    <Name ID="1072427" langid="36"/>
    <Name ID="438237" langid="20">sayfa başına geçen dakika</Name>         
   </Names>       
  </Measure>
</MeasuresList>

进入 dbo.table (ID int PRIMARY KEY, langid int FK, Value NVARCHAR(MAX)) 我正在使用

ALTER PROCEDURE [dbo].[AddNewMeasuresList] 
(
@XmlData XML
)

AS

DECLARE @doc int;
EXEC sp_xml_preparedocument @doc OUTPUT, @XmlData

BEGIN TRANSACTION T1
Insert Measure_Name
SELECT *
FROM OPENXML(@doc,'//ROOT/MeasuresList/Measure/Names/Name') WITH Measure_Name AS mes
WHERE NOT EXISTS
(   
    SELECT 1 FROM dbo.Measure_Name WHERE ID=mes.ID
)
COMMIT TRANSACTION T1
EXEC sp_xml_removedocument @doc

但是我的 c# 应用程序出现异常

System.Data.SqlClient.SqlException (0x80131904): 
Violation of PRIMARY KEY constraint 'PK_Measure_Name'. 
Cannot insert duplicate key in object 'dbo.Measure_Name'. The duplicate key value is (1072687)

dbo.Measure_Name 表为空,但在其他具有相同名称 ID 的 Measure XML 节点中存在重复条目。

由于我的 SELECT 语句返回所有导致重复主键的“名称”,我如何更改选择语句以返回所有值但只有 DISTINCT/UNIQUE ID?

【问题讨论】:

    标签: xml tsql xpath xquery sql-server-2017-express


    【解决方案1】:

    您自己的解决方案可能有效,但可以做得更好...

    FROM OPENXML 与 SP 一起准备和删除文档已过时,不应再使用。而是使用native XML support

    试试这个并根据您的需要进行调整:

    你的 XML

    DECLARE @xml XML=
    N'<ROOT>
      <MeasuresList>
        <Measure ID="164">
          <Names>
            <Name ID="1072687" langid="33" />
            <Name ID="1017672" langid="13">miljoner skär</Name>
          </Names>
        </Measure>
        <Measure ID="362">
          <Names>
            <Name ID="1072687" langid="33" />
            <Name ID="1017672" langid="13">miljoner skär</Name>
            <Name ID="4068857" langid="19">hyller</Name>
            <Name ID="3330057" langid="18">ράφια</Name>
            <Name ID="3291105" langid="20">raflar</Name>
            <Name ID="2813622" langid="10" />
          </Names>
        </Measure>
        <Measure ID="162">
          <Names>
            <Name ID="1072687" langid="33" />
            <Name ID="1017672" langid="13">miljoner skär</Name>
            <Name ID="1072427" langid="36" />
            <Name ID="438237" langid="20">sayfa başına geçen dakika</Name>
          </Names>
        </Measure>
      </MeasuresList>
    </ROOT>';
    

    --我的目标表将包含&lt;Measure&gt;中的@ID,不知道你是否需要这个...

    DECLARE @target TABLE(MeasureID int, NameID int, [langid] int, [Value] NVARCHAR(MAX))
    

    --数据将使用表@target作为暂存表
    --查询使用.nodes()APPLY.value从你的XML中读取值

    INSERT INTO @target(MeasureID,NameID,[langid],[Value]) 
    SELECT m.value(N'@ID',N'int') AS MeasureID
          ,n.value(N'@ID',N'int') AS NameID
          ,n.value(N'@langid',N'int') AS [langid]
          ,n.value(N'text()[1]',N'nvarchar(max)') AS [Value]
    FROM @xml.nodes(N'/ROOT/MeasuresList/Measure') AS A(m)
    OUTER APPLY A.m.nodes(N'Names/Name') AS B(n);
    

    --这会返回完整的(非规范化的)内容

    SELECT * FROM @target;
    

    在此之后,您可以使用任何类型的 GROUP BY、聚合(无论您可能需要什么)将其转移到您的目标表中。

    我建议一个至少包含两个相关表的结构,以反映您的 1:n 相关 Measure/Name 关系。

    一些解释

    使用FROM @xml.nodes(N'/ROOT/MeasuresList/Measure') AS A(m) 将生成所有(重复)&lt;Measure&gt; 元素的派生表。此“表”称为A,而“列”称为m(用于“测量”)。此列是一个 XML 片段,表示每行一个 &lt;Measure&gt;

    对于OUTER APPLY A.m.nodes(N'Names/Name') AS B(n),我使用这个派生表A,使用它的列m,然后再次使用.nodes(),深入到(重复的)&lt;Name&gt;元素。

    实际值采用.value()。此方法首先采用XPath/XQuery,其次采用目标类型。

    希望这现在很清楚... :-D

    【讨论】:

    • 谢谢我上次评论时没有看到你的答案,不确定它是否在那里。我开始使用内置的 xml,但是我无法绕过它并回到 openxml。我会在接下来的几天里尝试一下。
    • @EddieTedCrocombe 好吧,我添加了一些解释......你看,我真的很努力说服你:-D
    • 是的,这更有意义。在 value() 中,何时应该在 XPah 中使用 @ 和 [1]?我注意到您将它用于 text() 而不是用于属性?
    • @EddieTedCrocombe, .value() 需要 singleton 保证。虽然永远不会再次找到属性(名称必须是唯一的),但可能存在具有相同名称的元素。 text() 只是其包含元素下方的一个特殊 node。想象一下&lt;x&gt;text1&lt;y /&gt;text2&lt;/x&gt;。一个元素可能有多个text()。因此[1].
    • 干杯,你扭伤了我的手臂,从现在开始我将使用 xmlnodes,再次感谢您的时间@Shnugo
    【解决方案2】:

    发布我找到的答案后,我添加了一个新的 WHERE 子句,它将当前 ID 与 DISTINCT ID 的列表/数组/表进行比较

    ALTER PROCEDURE [dbo].[AddNewMeasuresList] 
    (
    @XmlData XML
    )
    
    AS
    
    DECLARE @doc int;
    EXEC sp_xml_preparedocument @doc OUTPUT, @XmlData
    
    BEGIN TRANSACTION T1
    Insert Measure_Name
    SELECT  *
    FROM OPENXML(@doc,'//ROOT/MeasuresList/Measure/Names/Name') 
    WITH 
    (
        ID int,
        langid int,
        Value nvarchar(MAX) 'text()'
    ) AS mn
    WHERE 
    (
        mn.ID in (SELECT DISTINCT ID FROM OPENXML(@doc,'////ROOT/MeasuresList/Measure/Names/Name'))
    )
    AND NOT EXISTS
    (   
        SELECT 1 FROM dbo.Measure_Name WHERE ID=mn.ID
    )
    COMMIT TRANSACTION T1
    EXEC sp_xml_removedocument @doc
    

    【讨论】:

    • 你能提供一个替代的@Shnugo吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2012-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多