Команда SQL XQuery для рекурсивного получения потомков того же типа - PullRequest
1 голос
/ 13 декабря 2011

Я новичок в XQuery в SQL, но я понимаю основы получения узлов и значения запросов. Моя проблема сейчас связана с иерархией неизвестной глубины.

Отношение Контракт -> Проект -> Линии ; и выглядит примерно так:

<ZEstimateContract xmlns="http://schemas.datacontract.org/2004/07/Zeller.Gp" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="i1">
  <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">QGPET0000000218</Name>
  <_projects>
    <ZEstimateProject z:Id="i10">
      <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project A</Name>
      <_lines>
        <ZEstimateLine z:Id="i41">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">00008813</Name>
          <_lines>
            <ZEstimateLine z:Id="i43">
              <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">002-2075-4  MAIN COLUMN</Name>
              <_lines />
            </ZEstimateLine>
          </_lines>
        </ZEstimateLine>
        <ZEstimateLine z:Id="i44">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">0.080 Aluminum 2ft x 4ft</Name>
          <_lines />
        </ZEstimateLine>
      </_lines>
      <_projects>
        <ZEstimateProject z:Id="i101">          
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project A1</Name>
          <_lines>
            <ZEstimateLine z:Id="i132">              
              <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy" />
              <_lines />
            </ZEstimateLine>
          </_lines>
          <_projects />
        </ZEstimateProject>
      </_projects>
    </ZEstimateProject>
    <ZEstimateProject z:Id="i189">
      <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project B</Name>
      <_lines>
        <ZEstimateLine z:Id="i205">          
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">#8X8SPOOL</Name>
          <_lines />
        </ZEstimateLine>
      </_lines>
      <_projects />
    </ZEstimateProject>
  </_projects>
  <_rebateSources />
</ZEstimateContract>

Таким образом, контракт может иметь любое количество проектов, которые могут иметь любое количество строк И любое количество подпроектов. Строки могут иметь любое количество сублиней.

Я пишу запрос SQL, чтобы вернуть ВСЕ строки в наборе результатов ( РЕДАКТИРОВАТЬ: среди других данных). Вот что у меня есть:

-- TEST DATA
DECLARE @QuoteDate DATETIME = '12/12/2011'
DECLARE @QuoteNumber VARCHAR(15) = 'QGPET0000000218'
DECLARE @RevLevel VARCHAR(50) = '0'

;WITH XMLNAMESPACES('http://schemas.datacontract.org/2004/07/Zeller.Gp' AS ZC, 
    'http://schemas.datacontract.org/2004/07/Zynergy' AS ZYN,
    'http://schemas.microsoft.com/2003/10/Serialization/' AS Z)

SELECT CM.Contract, CM.RevisionLevel, CM.CustomerGpId,
    p.value('(./ZYN:Name)[1]', 'varchar(50)') as ProjectName,
    l.value('(./ZYN:Name)[1]', 'varchar(50)') as ItemNumber
FROM dbo.tblContractMaster AS CM
CROSS APPLY CM.FullContract.nodes('/ZC:ZEstimateContract/ZC:_projects/ZC:ZEstimateProject') as Proj(p)
CROSS APPLY Proj.p.nodes('./ZC:_lines/ZC:ZEstimateLine') as Line(l) 
WHERE CM.[Contract] = @QuoteNumber AND CM.RevisionLevel = @RevLevel
-- Order by the default "ID" that gets assigned to the XML element.  
-- This is the same order that the object is in when in a collection in GPET
ORDER BY p.value('(./@Z:Id)[1]', 'varchar(50)'), l.value('(./@Z:Id)[1]', 'varchar(50)')

Как вы могли заметить, это даст мне только "уровень 1", но мне нужно углубиться (подумайте, в начале! ... проект в рамках проекта в ... вы поймете точку)

Есть идеи?

РЕДАКТИРОВАТЬ Добавлено частичное решение. Это дает мне подпроекты (обратите внимание на «//»), но не подстроки:

CROSS APPLY CM.FullContract.nodes('/ZC:ZEstimateContract//ZC:_projects/ZC:ZEstimateProject') as Proj(p) 
CROSS APPLY Proj.p.nodes('./ZC:_lines/ZC:ZEstimateLine') as Line(l) LEFT OUTER JOIN 

РЕДАКТИРОВАТЬ: Вот лучший образец:

<ZEstimateContract xmlns="http://schemas.datacontract.org/2004/07/Zeller.Gp" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="i1">
  <_key xmlns="http://schemas.datacontract.org/2004/07/Zynergy">ZynergyDefault</_key>
  <_dataStoreGuid xmlns="http://schemas.datacontract.org/2004/07/Zynergy">88381fa0-5901-4513-9ccb-b2f576341db1</_dataStoreGuid>
  <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">QGPET0000000218</Name>
  <_projects>
    <ZEstimateProject z:Id="i10">
      <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project A</Name>
      <Parent i:nil="true" />
      <_lines>
        <ZEstimateLine z:Id="i41">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">00008813</Name>
          <Description>Epic circular connector w/ 12 inserts.</Description>
          <Parent i:nil="true" />
          <_lines>
            <ZEstimateLine z:Id="i43">
              <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">002-2075-4  MAIN COLUMN</Name>
              <Description>CUSTOM JBOX, PER DWG REV -, PUNCHED, TAPPED, CUT OUTS, PBT4-70003 TEXTURE BLACK INSIDE AND OUTSIDE</Description>
              <Parent z:Id="i44">
                <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">00008813</Name>
                <Description>Epic circular connector w/ 12 inserts.</Description>
                <Parent i:nil="true" />
                <_lines>
                  <ZEstimateLine z:Id="i46">
                    <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">002-2075-4  MAIN COLUMN</Name>
                    <Description>CUSTOM JBOX, PER DWG REV -, PUNCHED, TAPPED, CUT OUTS, PBT4-70003 TEXTURE BLACK INSIDE AND OUTSIDE</Description>
                    <Parent z:Ref="i44" />
                    <_lines />
                  </ZEstimateLine>
                </_lines>
              </Parent>
              <_lines />
            </ZEstimateLine>
          </_lines>
        </ZEstimateLine>
        <ZEstimateLine z:Id="i47">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">AG30</Name>
          <Description>480V 30A CLASS G FUSE</Description>
          <Parent i:nil="true" />
          <_lines />
        </ZEstimateLine>
      </_lines>
      <_projects>
        <ZEstimateProject z:Id="i105">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project A1</Name>
          <_lines>
            <ZEstimateLine z:Id="i136">
              <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">0026153</Name>
              <Description>Olflex 810 16awg 7cond</Description>
              <_lines />
            </ZEstimateLine>
          </_lines>
          <_projects>
            <ZEstimateProject z:Id="i193">
              <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">&lt;BOM&gt;</Name>
              <Parent z:Ref="i105" />
              <_lines>
                <ZEstimateLine z:Id="i224">
                  <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">PROSINE 1000</Name>
                  <Parent i:nil="true" />
                  <_lines />
                </ZEstimateLine>
              </_lines>
              <_projects />
              <_savedBudgets />
            </ZEstimateProject>
          </_projects>
          <_savedBudgets />
        </ZEstimateProject>
      </_projects>
    </ZEstimateProject>
    <ZEstimateProject z:Id="i281">
      <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">Project B</Name>
      <Parent i:nil="true" />
      <_lines>
        <ZEstimateLine z:Id="i297">
          <Name xmlns="http://schemas.datacontract.org/2004/07/Zynergy">#8X8SPOOL</Name>
          <Description>1-8"x14.05" flg x flg. DIP, Black</Description>
          <Parent i:nil="true" />
          <_lines />
        </ZEstimateLine>
      </_lines>
      <_projects />
    </ZEstimateProject>
  </_projects>
</ZEstimateContract>

1 Ответ

2 голосов
/ 13 декабря 2011

Вы очень близки.Я думаю, все, что вам нужно сделать, это использовать обновленную версию из вашего комментария и изменить свой второй CROSS APPLY на следующее:

CROSS APPLY Proj.p.nodes('.//ZC:_lines/ZC:ZEstimateLine') as Line(l) 

При этом используется тот же дополнительный слеш, который вы обнаружили ранее, чтобы откатить и найти все строки..

Возможно, вы захотите проверить это на своих данных, но с вашим образцом оно работает корректно.

РЕДАКТИРОВАТЬ :

Хорошо, я думаю, что у меня есть это сейчас.Это существенно сложнее, и хотя я пытался сделать это с помощью рекурсивных CTE, я не мог найти способ напрямую связать два рекурсивных CTE и не мог сделать это с помощью одного.Более опытный гуру мог бы улучшить это.

В результате я получил два табличных UDF, один для рекурсивного уничтожения всех ваших проектов, а другой для взятия каждого из этих проектов.и рекурсивно уничтожить все строки:

CREATE FUNCTION fn_explode_projects (@xdata xml)
RETURNS TABLE
AS
RETURN
WITH XMLNAMESPACES('http://schemas.datacontract.org/2004/07/Zeller.Gp' AS ZC, 
    'http://schemas.datacontract.org/2004/07/Zynergy' AS ZYN,
    'http://schemas.microsoft.com/2003/10/Serialization/' AS Z),
ProjList (ProjectName, ChildProjects, ChildLines)
AS
(
    SELECT CAST(NULL as varchar(50)) AS ProjectName,
        @xdata.query('/ZC:ZEstimateContract/ZC:_projects') AS ChildProjects,
        CAST(NULL AS xml) AS ChildLines

    UNION ALL

    SELECT CP.ChildProject.value('(./ZYN:Name)[1]', 'varchar(50)') AS ProjectName,
        CP.ChildProject.query('./ZC:_projects') AS ChildProjects,
        CP.ChildProject.query('./ZC:_lines') AS ChildLines

    FROM ProjList
        CROSS APPLY ProjList.ChildProjects.nodes('/ZC:_projects/ZC:ZEstimateProject') AS CP(ChildProject)
)
SELECT ProjectName, ChildProjects, ChildLines
FROM ProjList

GO


CREATE FUNCTION fn_explode_lines (@xdata xml)
RETURNS TABLE
AS
RETURN
WITH XMLNAMESPACES('http://schemas.datacontract.org/2004/07/Zeller.Gp' AS ZC, 
    'http://schemas.datacontract.org/2004/07/Zynergy' AS ZYN,
    'http://schemas.microsoft.com/2003/10/Serialization/' AS Z),
ItemList (ItemNumber, ChildLines)
AS
(
    SELECT CAST(NULL as varchar(50)) AS ItemNumber,
        @xdata.query('/ZC:_lines') AS ChildLines

    UNION ALL

    SELECT CL.ChildLine.value('(./ZYN:Name)[1]', 'varchar(50)') AS ItemNumber,
        CL.ChildLine.query('./ZC:_lines') AS ChildLines

    FROM ItemList
        CROSS APPLY ItemList.ChildLines.nodes('/ZC:_lines/ZC:ZEstimateLine') AS CL(ChildLine)
)
SELECT ItemNumber, ChildLines
FROM ItemList
GO

Затем вы можете использовать их для выполнения вашего запроса:

SELECT CM.Contract, CM.RevisionLevel, CM.CustomerGpId,
    Proj.ProjectName,
    Line.ItemNumber
FROM dbo.tblContractMaster AS CM
    CROSS APPLY dbo.fn_explode_projects(CM.FullContract) Proj
    CROSS APPLY dbo.fn_explode_lines(Proj.ChildLines) Line
WHERE Proj.ProjectName IS NOT NULL AND Line.ItemNumber IS NOT NULL

Это предполагает, что контракт не содержит строк напрямую - всестроки должны содержаться проектом или другой строкой.

...