CROSS JOIN表的一列及其数据

时间:2017-04-01 23:23:11

标签: sql sql-server tsql

带示例的问题描述

假设我有一个这样的表:

CREATE TABLE MyData(
    Id INT PRIMARY KEY,
    NotUniqueCol INT,
    ColA NVARCHAR(128),
    ColB NVARCHAR(128)
)
INSERT INTO MyData (Id, NotUniqueCol, ColA, ColB)
VALUES (1, 1, 'a', 'A')
     , (2, 1, 'b', 'B')
     , (3, 2, 'c', 'C')

一个存储过程,它带有表名列名列值

CREATE PROCEDURE GetData
(
    @tableName NVARCHAR(128),
    @columnName NVARCHAR(128),
    @id INT,
)

我执行它

EXEC GetData 'MyData', 'NotUniqueCol', 1

应该返回这样的内容:

 RowNumber  │  ColumnName    │ ColumnValue
────────────┼────────────────┼──────────────────
 1          │ 'Id'           │ 1
 1          │ 'NotUniqueCol' │ 1
 1          │ 'ColA'         │ 'a'
 1          │ 'ColB'         │ 'A'
 2          │ 'Id'           │ 2
 2          │ 'NotUniqueCol' │ 1
 2          │ 'ColA'         │ 'b'
 2          │ 'ColB'         │ 'B'

大纲解决方案

问题是GetData的正文,因为一切都必须是通用的。

我的想法是这些步骤:

  1. 获取所有表名并将它们连接到列并过滤结果。
  2. 执行动态查询,从表中选择匹配的数据并对行进行计算。
  3. 执行CROSS JOIN,结果为2. und 3.
  4. 片段

    第1步

    SELECT c.[name]
    FROM sys.tables AS t
    INNER JOIN sys.columns AS c ON c.[object_id] = t.[object_id]
    WHERE t.[name] = @tableName
    

    第2步

    DECLARE @query NVARCHAR(MAX) 
        = ' SELECT ROW_NUMBER() OVER(ORDER BY myTable.' + QUOTENAME(@columnName) + ' DESC) AS [RowNumber], myTable.*'
        + ' FROM ' + QUOTENAME(@tableName) + ' AS myTable'
        + ' WHERE myTable.' + QUOTENAME(@columnName) + ' = ' + CAST(@id AS NVARCHAR(16))
    PRINT @query
    EXEC sp_sqlexec @query
    

    使用游标

    获取结果

    此代码有效,但由于光标,我不喜欢它。

    -- The parameters from the SP
    DECLARE  @tableName NVARCHAR(128)='MyData',
             @columnName NVARCHAR(128)='NotUniqueCol',
             @id INT=1
    
    --The body of the SP
    CREATE TABLE #result (RowNumber INT, ColumnName NVARCHAR(128), ColumnValue SQL_VARIANT)
    
    DECLARE @column NVARCHAR(128);
    DECLARE myCursor CURSOR
    FOR 
        SELECT c.[name]
        FROM sys.tables AS t
        INNER JOIN sys.columns AS c ON c.[object_id] = t.[object_id]
        WHERE t.[name] = @tableName
    OPEN myCursor  
    FETCH NEXT FROM myCursor 
    INTO @column
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        DECLARE @query NVARCHAR(MAX) 
            = ' SELECT ROW_NUMBER() OVER(ORDER BY myTable.' + QUOTENAME(@columnName) + ' DESC) AS [RowNumber], ''' + @column + ''' AS [ColumnName], myTable.' + QUOTENAME(@column)
            + ' FROM ' + QUOTENAME(@tableName) + ' AS myTable'
            + ' WHERE myTable.' + QUOTENAME(@columnName) + ' = ' + CAST(@id AS NVARCHAR(16))
        PRINT @query
    
        INSERT INTO #result(RowNumber, ColumnName, ColumnValue)
        EXEC sp_sqlexec @query
    
        FETCH NEXT FROM myCursor 
        INTO @column
    END
    CLOSE myCursor;  
    DEALLOCATE myCursor;  
    
    SELECT * FROM #result
    ORDER BY RowNumber, ColumnName
    
    DROP TABLE #result
    

    最终问题

    对于不使用游标的第3步,我不太了解。它效果很好,但有没有使用CROSS JOIN或其他东西的解决方案?

1 个答案:

答案 0 :(得分:2)

这是一个Table-Valued-Function,它几乎可以将任何表,查询或行转换为EAV结构。

查询(或表格)中的第一列将是ENTITY,请注意它将从值中排除(可选)

当然,UNPIVOT性能更高,但这不需要动态SQL,也没有数据类型冲突,性能非常可观。

我应该补充一下,将排除NULL值。我对如何包含它们感到茫然。

示例1

Select * From [dbo].[udf-EAV]((Select * From MyData for XML RAW))

<强>返回

Entity  Attribute       Value
1       NotUniqueCol    1      --<< Notice ID is NOT a row
1       ColA            a
1       ColB            A
2       NotUniqueCol    1
2       ColA            b
2       ColB            B
3       NotUniqueCol    2
3       ColA            c
3       ColB            C

示例2 - 使用Row_Number()作为ENTITY

Select * From [dbo].[udf-EAV]((Select RowNumber=Row_Number() over (Order By ID),* From MyData for XML RAW))

<强>返回

Entity  Attribute       Value
1       Id              1      --<< Notice ID IS a row ... 1st column is Row_Number()
1       NotUniqueCol    1
1       ColA            a
1       ColB            A
2       Id              2
2       NotUniqueCol    1
2       ColA            b
2       ColB            B
3       Id              3
3       NotUniqueCol    2
3       ColA            c
3       ColB            C

示例3与CROSS APPLY - 特别是对于较大的表

Select B.*
 From  MyData A
 Cross Apply [dbo].[udf-EAV]((Select A.* for XML RAW)) B

<强>返回

Entity  Attribute       Value
1       NotUniqueCol    1
1       ColA            a
1       ColB            A
2       NotUniqueCol    1
2       ColA            b
2       ColB            B
3       NotUniqueCol    2
3       ColA            c
3       ColB            C

感兴趣的UDF

CREATE FUNCTION [dbo].[udf-EAV](@XML xml)
Returns Table 
As
Return (
    with cteKey(k) as (Select Top 1 xAtt.value('local-name(.)','varchar(100)') From @XML.nodes('/row') As A(xRow) Cross Apply A.xRow.nodes('./@*') As B(xAtt))    

    Select Entity    = xRow.value('@*[1]','varchar(50)')
          ,Attribute = xAtt.value('local-name(.)','varchar(100)')
          ,Value     = xAtt.value('.','varchar(max)') 
    From  @XML.nodes('/row') As A(xRow)
    Cross Apply A.xRow.nodes('./@*') As B(xAtt)
    Where xAtt.value('local-name(.)','varchar(100)') Not In (Select k From cteKey)
)
-- Notes:  First Field in Query will be the Entity
-- Select * From [dbo].[udf-EAV]((Select UTCDate=GetUTCDate(),* From sys.dm_os_sys_info for XML RAW))