根据列元数据创建视图

时间:2013-10-02 11:27:26

标签: sql sql-server-2008 metadata

我们假设有两个表: 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 |

所以有两个问题:

  1. 除了指定TableB中的子选择外(...“其中     每列的colname ='XXX'“),看起来非常不足     (更不用说......手册:P),有没有办法得到结果我     前面提到的colname自动匹配?
  2. 我有预感         这可能是数据库上糟糕的设计。是这样吗?如是,         什么是更优化的设计? (请记住复杂性         我前面提到的数据结构)

4 个答案:

答案 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

这是结果

enter image description here

回答你的第二个问题。 上面的设计甚至没有提供性能和灵活性。如果用户想要添加无法完成的新元数据(列和单元),而无需更改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

见下面的结果。

enter image description here

答案 1 :(得分:0)

我会像下面那样设计这个数据库:

包含测量数据点的表MEASUREMENT_DATAPOINT。它将包含IDmeasurement_idvalueunitname列。 一个条目是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