我们假设有两个表: TableA保存来自各种站的各种数据测量值。 TableB包含有关TableA中使用的列的元数据。
TableA有:
stationID int not null, pk
entryDate datetime not null, pk
waterTemp float null,
waterLevel float null ...etc
TableB有:
id int not null, pk, autoincrement
colname varchar(50),
unit varchar(50) ....etc
例如,来自tableA的一行数据为:
1 | 2013-01-01 00:00 | 2.4 | 3.5
来自tableB的两行:
1| waterTemp | celcius
2| waterLevel | meters
这是一个简化的例子。实际上,tableA可能包含近20个不同的数据列,而表b有近10个元数据列。
我正在尝试设计一个输出结果的视图:
StationID | entryDate | water temperature | water level |
1 | 2013-01-01 00:00 | 2.4 celcius | 3.5 meters |
所以有两个问题:
答案 0 :(得分:1)
使用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中添加新的Column和Unit,它将在TableA中添加新的Row。不需要更改表定义。
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
见下面的结果。
答案 1 :(得分:0)
我会像下面那样设计这个数据库:
包含测量数据点的表MEASUREMENT_DATAPOINT
。它将包含ID
,measurement_id
,value
,unit
,name
列。
一个条目是1, 1, 2.4, 'celcius', 'water temperature'
。
表MEASUREMENTS
包含测量数据本身。专栏:ID, station_ID, entry_date
。
答案 2 :(得分:0)
您可能希望查看名为PIVOT / UNPIVOT
的MS-SQL函数http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
您可以使用此命令获取列名称并将其包含在行中,反之亦然。
在列本身中有列名后,您可以将该列从tableA连接到tableB。然后忽略以便以您希望的方式恢复数据。 (请注意,我可能正在交换使用pivot和unpivot:))
明智的话,如果你正在处理大型表,那么枢轴不是最快的操作。
答案 3 :(得分:0)
我认为你必须将每个指标翻到一行。看看上面的设计:
1 | 2013-01-01 00:00 | 2.4 | 3.5
我如何知道表b中适用于哪一行?
我会尝试这样的事情: 表B:
Metric_Key | Metric
1 | WaterLevel in Meters
2 | Temp in Celcius
...
表A:
StationID | entrydate | Metric_Key | Value
1 2013-01-01 00:00 1 2.4