2017-04-01 130 views
2

问题描述一个示例

假设我有这样一个表:CROSS JOIN一个表的列具有其数据

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.

片段

步骤的结果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或其他的解决方案?

回答

2

这是一个表值函数,它将几乎所有的表,查询或行转换为EAV结构。

查询(或表)的第一列将是实体,请注意,这将从值(可选)

当然被排除在外,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)) 
+0

就是这样。谢谢! – Koopakiller

+0

@Koopakiller对它有帮助 –

相关问题