具有不同子级的Sql XML Path

时间:2010-04-01 14:18:04

标签: sql tsql sqlxml

我已经做了很多XML PATH语句,但是这个语句让我感到厌烦,甚至可能不会有多个不同的孩子。

最终结果应如下所示

<Process>
<TaskList>
<SqlTask Name="Get Report Parameters">
    <StoredProcName>GetReportParameters</StoredProcName>
        <ConnectionName>Local</ConnectionName>
        <DataTableName>DistributionList</DataTableName>
        <Parameters>
              <Parameter>
            <Name>ReportName</Name>
            <Value>TheReprot</Value>
            <Type>String</Type>
              </Parameter>
        </Parameters>
  </SqlTask>
  <LoopTask Name="Loop Report Creation" ContainerKey="DistributionList">
  <TaskList>
        <ReportTask Name="Report In Loop">   
    </ReportTask>
</TaskList>
  </LoopTask>
  <SqlTask Name="Get Email Addresses">
    <StoredProcName>GetMailingAddress</StoredProcName>
        <ConnectionName>Local</ConnectionName>
        <DataTableName>EmailList</DataTableName>

  </SqlTask>
  <LoopTask Name="Loop Mail Creation" ContainerKey="EmailList">
<TaskList>
        <MailTask Name="Send Email In Loop">       
        </MailTask>
</TaskList>
  </LoopTask>
</TaskList>
</Process>

下面是我到目前为止的一些测试表和数据。问题实际上是如何在同一根下显示不同的子节点。我可以从列值中获取标记名称吗?

CREATE TABLE #TASK (
    TaskId INT IDENTITY(1,1)
,    ProcessId INT
,    TaskType VARCHAR(255)
,    TaskName VARCHAR(255)
,    ContainerKey VARCHAR(255)
,    ParentTaskId INT
)

CREATE TABLE #TASK_PARAMETERS 

(
    TaskId INT
,    Name VARCHAR(255)
,    Value VARCHAR(MAX)
,    [Type] VARCHAR(128)
)

CREATE TABLE #TASK_DETAILS
(
    TaskId INT
,    DetailName VARCHAR(255)
,    DetailValue VARCHAR(MAX)
)

DECLARE @TaskId AS INT
DECLARE @ParentTaskId AS INT


INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'SqlTask'
,    'Get Report Parameters'
,    NULL
,    NULL
)

SET @TaskId = @@IDENTITY

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'StoredProceName'
,    'GetReportParamters'
)

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'ConnectionName'
,    'Local'
)

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'DataTableName'
,    'DistributionList'
)

INSERT INTO #TASK_PARAMETERS 

(
    TaskId
,    Name
,    Value
,    [Type]
)
VALUES 
(
    @TaskId
,    'ReportName'
,    'TheReprot'
,   'String'
)


INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'LoopTask'
,    'Loop Report Creation'
,    'DistributionList'
,    NULL
)

SET @ParentTaskId = @@IDENTITY


INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'ReportTask'
,    'Report In Loop'
,    NULL
,    @ParentTaskId
)


INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'SqlTask'
,    'Get Email Addresses'
,    NULL
,    NULL
)

SET @TaskId = @@IDENTITY

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'StoredProceName'
,    'GetMailingAddress'
)

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'ConnectionName'
,    'Local'
)

INSERT INTO #TASK_DETAILS
(
    TaskId
,    DetailName
,    DetailValue
)
VALUES 
(
    @TaskId
,    'DataTableName'
,    'EmailList'
)


INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'LoopTask'
,    'Loop Mail Creation'
,    'EmailList'
,    NULL
)

SET @ParentTaskId = @@IDENTITY

INSERT INTO #TASK 
(
    ProcessId
,    TaskType
,    TaskName
,    ContainerKey
,    ParentTaskId
)
VALUES 
(
0
,    'MailTask'
,    'Send Email In Loop'
,    NULL
,    @ParentTaskId
)


SELECT *
FROM #TASK

SELECT *
FROM #TASK_PARAMETERS 

SELECT *
FROM  #TASK_DETAILS

1 个答案:

答案 0 :(得分:1)

是的,您的样本需要解决许多问题!

首先,我会给你答案,但请注意,为了正确地进行层次处理,它必须是递归函数,因此您提供的测试数据必须在永久表中创建而不是临时(更简单) 然后我会指出一些有用的技巧,我用它来解决问题。

ALTER FUNCTION GetTasks (@ParentId varchar(255)= NULL) 
RETURNS
XML
BEGIN
DECLARE @ReturnXML XML

SELECT @ReturnXML = 
(
    SELECT
    (
        SELECT 
            CONVERT(XML,
                --Main task start tag
                '<'+master_t.TaskType+' Name="'+master_t.TaskName+'">'+ 
                    CONVERT(VARCHAR(MAX),
                        (

                            SELECT
                            dbo.GetTasks(master_t.TaskId),
                            (
                                SELECT 
                                    CONVERT(XML,'<'+DetailName+'>'+DetailValue+'</'+DetailName+'>')
                                FROM
                                    TASK_DETAILS t 
                                WHERE
                                    TaskId = master_t.TaskId
                                FOR XML PATH(''),Type
                            ),
                            (
                                SELECT Name,Value,Type FROM TASK_PARAMETERS t 
                                WHERE TaskId=master_t.TaskId
                                FOR XML PATH('Parameter'),Type
                            ) 'Parameters'
                            FOR XML PATH(''),Type 
                        )
                    )
                    +
                --Main task end tag
                '</'+master_t.TaskType+'>'
            )
        FROM 
            TASK master_t
        WHERE 
            --Effectively ignore the parentId field if it is not passed.
            ISNULL(ParentTaskId,'') = CASE WHEN @ParentId IS NULL THEN '' ELSE @ParentId END


        FOR XML PATH(''),Type
    ) 'TaskList'  FOR XML PATH(''),Type
) 

RETURN @ReturnXML
END
GO

像这样调用此函数:

SELECT dbo.GetTasks(NULL)

我认为值得注意的技巧是:

a)您可以通过简单地从字符串构建xml节点来手动创建xml节点 - 如果节点名称在表中,这将非常有用。你唯一需要注意的是,要在一个块周围放置一个打开和关闭的标签,你可能必须首先将块转换为字符串,粘贴标签,然后将整个事物转换为xml(零碎的)不会工作,因为convert-to-xml函数会期望你提供格式良好的XML。

b)你有时必须在括号中嵌套东西以在所有子标签周围实现标记... 一个例子使这更清楚:

 SELECT 
    TaskName
    FROM TASK t
    FOR XML PATH('SomeRoot')

会产生:

<SomeRoot>
  <TaskName>Get Report Parameters</TaskName>
</SomeRoot>
<SomeRoot>
  <TaskName>Loop Report Creation</TaskName>
</SomeRoot>
<SomeRoot>
  <TaskName>Report In Loop</TaskName>
</SomeRoot>
<SomeRoot>
  <TaskName>Get Email Addresses</TaskName>
</SomeRoot>
<SomeRoot>
  <TaskName>Loop Mail Creation</TaskName>
</SomeRoot>
<SomeRoot>
  <TaskName>Send Email In Loop</TaskName>
</SomeRoot>

要让“SomeRoot”出现在它周围,你可以这样做:

SELECT 
(
    SELECT 
        TaskName
    FROM TASK t
    FOR XML PATH(''),Type
) 
FOR XML PATH('SomeRoot')

如果节点名称是静态的(请注意XML PATH(''),键入,这基本上确保XML路径返回XML类型数据以供进一步处理,并且不会将其转义)

如果节点名称不是静态的,那么你就会遇到类似这样的事情,需要转换为字符串和从字符串转换才能使其正常工作。

SELECT 
    CONVERT(XML,
        '<'+DynamicName+'>' + 
        CONVERT(VARCHAR(MAX),
                (
                    SELECT 
                        TaskName
                    FROM TASK t
                    FOR XML PATH(''),Type
                )
            ) +
            '</'+DynamicName+'>'  
    )
FROM
    (SELECT 'Test' as DynamicName) a

c)关于让不同的子标签出现在同一级别上的问题,这是非常简单的,你只需要记住,多层选择的常见问题不再是xml作为xml选择的问题只返回一个xml对象。然后,您可以使用XML PATH将这些结果组合到树中。

e.g。

SELECT 
    (SELECT top 1 * FROM TASK FOR XML PATH(''),Type),
    (SELECT top 1 * FROM TASK_DETAILS FOR XML PATH(''),Type)

将返回包含两列的单行,但如果您再应用XML PATH('')整体上,您将它们组合在同一级别上

SELECT 
    (SELECT top 1 * FROM TASK FOR XML PATH(''),Type),
    (SELECT top 1 * FROM TASK_DETAILS FOR XML PATH(''),Type)
FOR XML PATH('Root')    

d)如果通过XML PATH,列名称将转换为节点。属性非常简单,因为您只需为列提供一个别名,即适当的xsl路径 例如'MyNodeName \ @MyAttributeName'显然排除了动态命名的属性。为此,在这个例子中,我只是再次从字符串构建xml。顺便提一下,这就是动态节点名称如此糟糕的原因 - 您基本上允许您的例程通过表中的数据创建新的属性名称和节点名称...这意味着您无法为您的例程创建一个合适的模式你事先并不知道表格中可能包含哪些数据......

继续前进:)

所以,给定这些构建块,最简单的方法是从最深层开始,逐块构建,然后像上面那样组合。

我为你的查询做了那个,并最终意识到要让它分层次地工作(即n嵌套级别)我必须编写的是一个返回XML的函数,这被称为将parentnode传递给它(这样函数知道将结果集过滤到的内容。如果你的等级制度不完整和循环,这将会导致可怕的死亡。

好的 - 希望你可以在那里工作。这纯粹是面向XML PATH()的解决方案 - 有其他XML方法可以在不同的情况下使用。