假设我有一个这样的表:
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
的正文,因为一切都必须是通用的。
我的想法是这些步骤:
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
或其他东西的解决方案?
答案 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))