带有 PIVOT 的动态 SQL 就是答案。虽然它在调试方面很脏,或者说让一些新开发人员理解代码,但它会给你预期的结果。
检查以下查询。
在这我们需要动态地准备两件事。一是在结果集中列出列,二是值列表将出现在 PIVOT 查询中。请注意结果中的 Column3、Column5 和 Column6 没有 NULL 值。
SET NOCOUNT ON
IF OBJECT_ID('TableA','u') IS NOT NULL
DROP TABLE TableA
GO
CREATE TABLE TableA
(
stationID int not null IDENTITY (1,1)
,entryDate datetime not null
,waterTemp float null
,waterLevel float NULL
,Column3 INT NULL
,Column4 BIGINT NULL
,Column5 FLOAT NULL
,Column6 FLOAT NULL
)
GO
IF OBJECT_ID('TableB','u') IS NOT NULL
DROP TABLE TableB
GO
CREATE TABLE TableB
(
id int not null IDENTITY(1,1)
,colname varchar(50) NOT NULL
,unit varchar(50) NOT NULL
)
INSERT INTO TableA( entryDate ,waterTemp ,waterLevel,Column4)
SELECT '2013-01-01',2.4,3.5,101
INSERT INTO TableB( colname, unit )
SELECT 'WaterTemp','celcius'
UNION ALL SELECT 'waterLevel','meters'
UNION ALL SELECT 'Column3','unit3'
UNION ALL SELECT 'Column4','unit4'
UNION ALL SELECT 'Column5','unit5'
UNION ALL SELECT 'Column6','unit6'
DECLARE @pvtInColumnList NVARCHAR(4000)=''
,@SelectColumnist NVARCHAR(4000)=''
, @SQL nvarchar(MAX)=''
----getting the list of Columnnames will be used in PIVOT query list
SELECT @pvtInColumnList = CASE WHEN @pvtInColumnList=N'' THEN N'' ELSE @pvtInColumnList + N',' END
+ N'['+ colname + N']'
FROM TableB
--PRINT @pvtInColumnList
----lt and rt are table aliases used in subsequent join.
SELECT @SelectColumnist= CASE WHEN @SelectColumnist = N'' THEN N'' ELSE @SelectColumnist + N',' END
+ N'CAST(lt.'+sc.name + N' AS Nvarchar(MAX)) + SPACE(2) + rt.' + sc.name + N' AS ' + sc.name
FROM sys.objects so
JOIN sys.columns sc
ON so.object_id=sc.object_id AND so.name='TableA' AND so.type='u'
JOIN TableB tbl
ON tbl.colname=sc.name
JOIN sys.types st
ON st.system_type_id=sc.system_type_id
ORDER BY sc.name
IF @SelectColumnist <> '' SET @SelectColumnist = N','+@SelectColumnist
--PRINT @SelectColumnist
----preparing the final SQL to be executed
SELECT @SQL = N'
SELECT
--this is a fixed column list
lt.stationID
,lt.entryDate
'
--dynamic column list
+ @SelectColumnist +N'
FROM TableA lt,
(
SELECT * FROM
(
SELECT colname,unit
FROM TableB
)p
PIVOT
( MAX(p.unit) FOR p.colname IN ( '+ @pvtInColumnList +N' ) )q
)rt
'
PRINT @SQL
EXECUTE sp_executesql @SQL
这是结果
回答你的第二个问题。上面的设计甚至没有提供性能和灵活性。如果用户想要添加新的元数据(列和单元),而这些元数据(列和单元)无法通过更改 TableA 的表定义来完成。如果我们可以编写动态 SQL 以赋予用户灵活性,我们可以重新设计 TableA,如下所示。TableB 中没有任何变化。我会将其转换为键值对表。请注意,StationID 不再是 IDENTITY。相反,对于给定的 StationID,将有 N 行,其中 N 是为该 StationID 提供值的列数。通过这种设计,明天如果用户在 TableB 中添加新的列和单元,它将在 TableA 中添加新的行。无需更改表定义。
SET NOCOUNT ON
IF OBJECT_ID('TableA_New','u') IS NOT NULL
DROP TABLE TableA_New
GO
CREATE TABLE TableA_New
(
rowID INT NOT NULL IDENTITY (1,1)
,stationID int not null
,entryDate datetime not null
,ColumnID INT
,Columnvalue NVARCHAR(MAX)
)
GO
IF OBJECT_ID('TableB_New','u') IS NOT NULL
DROP TABLE TableB_New
GO
CREATE TABLE TableB_New
(
id int not null IDENTITY(1,1)
,colname varchar(50) NOT NULL
,unit varchar(50) NOT NULL
)
GO
INSERT INTO TableB_New(colname,unit)
SELECT 'WaterTemp','celcius'
UNION ALL SELECT 'waterLevel','meters'
UNION ALL SELECT 'Column3','unit3'
UNION ALL SELECT 'Column4','unit4'
UNION ALL SELECT 'Column5','unit5'
UNION ALL SELECT 'Column6','unit6'
INSERT INTO TableA_New (stationID,entrydate,ColumnID,Columnvalue)
SELECT 1,'2013-01-01',1,2.4
UNION ALL SELECT 1,'2013-01-01',2,3.5
UNION ALL SELECT 1,'2013-01-01',4,101
UNION ALL SELECT 2,'2012-01-01',1,3.6
UNION ALL SELECT 2,'2012-01-01',2,9.9
UNION ALL SELECT 2,'2012-01-01',4,104
SELECT * FROM TableA_New
SELECT * FROM TableB_New
SELECT *
FROM
(
SELECT lt.stationID,lt.entryDate,rt.Colname,lt.Columnvalue + SPACE(3) + rt.Unit AS ColValue
FROM TableA_New lt
JOIN TableB_new rt
ON lt.ColumnID=rt.ID
)t1
PIVOT
(MAX(ColValue) FOR Colname IN ([WaterTemp],[waterLevel],[Column1],[Column2],[Column4],[Column5],[Column6]))pvt
见下面的结果。