0

让我们假设有两个表: TableA 保存来自不同站点的各种数据测量值。TableB 包含有关 TableA 中使用的列的元数据。

表 A 有:

stationID int not null, pk
entryDate datetime not null, pk
waterTemp float null,
waterLevel float null ...etc

表 B 有:

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

这是一个简化的例子。实际上,表 A 可能包含近 20 个不同的数据列,表 b 具有近 10 个元数据列。

我正在尝试设计一个视图,它将输出如下结果:

StationID |      entryDate   | water temperature |  water level |
    1     | 2013-01-01 00:00 |     2.4 celcius   |   3.5 meters |

所以两个问题:

  1. 除了为每列指定来自 TableB (..."where colname='XXX'") 的子选择,这似乎非常不足(更不用说...手册 :P ),有没有办法获得我之前提到的结果在 colname 上自动匹配?
  2. 我有一种预感,这可能是数据库上的错误设计。是这样吗?如果是,什么是更优化的设计?(记住我前面提到的数据结构的复杂性)
4

4 回答 4

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 中添加新的列和单元,它将在 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

见下面的结果。

在此处输入图像描述

于 2013-10-12T04:54:07.663 回答
0

我会设计这个数据库,如下所示:

MEASUREMENT_DATAPOINT包含测量数据点的表格。它将具有列ID, measurement_id, value, unit, name
一个条目是1, 1, 2.4, 'celcius', 'water temperature'. MEASUREMENTS包含测量本身数据的表。列:ID, station_ID, entry_date

于 2013-10-02T11:34:33.290 回答
0

您可能想查看名为 PIVOT/UNPIVOT 的 MS-SQL 函数

http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx

您可以使用此命令获取列名并将它们放在行中,反之亦然。

一旦您在列本身中获得了列名,您就可以将该列从 tableA 连接到 tableB。然后以您想要的方式恢复数据。(警告我可能会交换使用 pivot 和 unpivot :))

不过,明智的说法是,如果您正在处理大型表,则数据透视并不是最快的操作。

于 2013-10-09T15:37:48.983 回答
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
于 2013-10-09T15:53:26.753 回答