【问题标题】:SQL combine column names and data types with column valuesSQL 将列名和数据类型与列值结合起来
【发布时间】:2013-01-09 22:44:52
【问题描述】:

我正在为我的 DBMS 使用 Sql Server 2012。

在我的数据库中,我有一个与一系列目录表相关的产品表。这些目录表代表各种产品类别。这个想法是,虽然所有产品都有某些共同的属性(我们的内部标识符、供应商、成本等),但产品的细节会有所不同(家具的描述方式与描述数字产品的方式不同) .

此外,每个产品都有一个父实体。一个父级可能有多个子级(在这种情况下是多个产品)。

我的任务是为给定的父 ID 列表选择产品和相关信息,并将该信息填充到 XML 文档中。

我目前正在使用的表是:

  • 产品
  • PRODUCT_DIGITAL
  • PRODUCT_FURNITURE

产品在 Product_Id 上与 PRODUCT_DIGITAL 具有 PK/FK 关系。 PRODUCT 与 PRODUCT_FURNITURE 存在相同类型的关系。

如果产品看起来像这样:

PRODUCT_ID -- PRODUCT_CATEGORY -- PARENT_ID -- PARENT_TYPE -- DELIVERY_IN_DAYS
100           DIG                 1            1              7
101           DIG                 1            1              8
102           DIG                 1            1              1
103           DIG                 2            1              2
104           DIG                 2            1              1

PRODUCT_DIGITAL 看起来像这样:

PRODUCT_ID -- PRODUCT_TYPE -- PRODUCT_NAME -- PRODUCT_MNEMONIC
100           A               IMG1            IMAWTRFL
101           B               SND1            SNDENGRV
102           B               SND2            SNDHRSLF
103           A               IMG2            IMGNBRTO
104           B               SND3            SNDGTWNE

最后我想要这样的结果集:

PRODUCT_CATEGORY -- PRODUCT_ID -- PRODUCT_TYPE -- PARENT_ID -- DELIVERY_IN_DAYS -- PROD_EXTENSION_NAME -- PROD_EXTENSION_TYPE -- PROD_EXTENSION_VALUE
DIG                 100           A               1            7                   PRODUCT_NAME           STRING                 IMG1
DIG                 100           A               1            7                   PRODUCT_MNEMONIC       STRING                 IMAWTRFL
DIG                 101           B               1            8                   PRODUCT_NAME           STRING                 SND1
DIG                 101           B               1            8                   PRODUCT_MNEMONIC       STRING                 SNDENGRV
DIG                 102           B               1            1                   PRODUCT_NAME           STRING                 SND2
DIG                 102           B               1            1                   PRODUCT_MNEMONIC       STRING                 SNDHRSLF
DIG                 103           A               2            2                   PRODUCT_NAME           STRING                 IMG2
DIG                 103           A               2            2                   PRODUCT_MNEMONIC       STRING                 IMGNBRTO
DIG                 104           B               2            1                   PRODUCT_NAME           STRING                 SND3
DIG                 104           B               2            1                   PRODUCT_MNEMONIC       STRING                 SNDGTWNE

我最初的搜索将我带到了 UNPIVOT - 但到目前为止,我还无法理解它。我最终做的是创建一个临时表并在通道中更新它,然后加入到产品表中以进行最终选择:

create table #tbl(product_id int, prod_extension_name varchar(100), prod_extension_type varchar(100), prod_extension_value varchar(1000))

insert into #tbl
select p.product_id, c.column_name, 
case c.data_type
when 'varchar' then 'string'
else data_type end as data_type
, null
from dbo.product p, information_schema.columns c
where c.table_name = 'PRODUCT_DIGITAL'
and c.column_name in ('PRODUCT_NAME','PRODUCT_MNEMONIC')

update #tbl
set prod_extension_value = p.product_name
from dbo.product p
where #tbl.product_id = p.product_id
and #tbl.colname = 'PRODUCT_NAME'

update #tbl
set prod_extension_value = p.product_mnemonic
from dbo.product p
where #tbl.product_id = p.product_id
and #tbl.colname = 'PRODUCT_MNEMONIC'

select p.product_category, p.product_id, pd.product_category, #tbl.prod_extension_name, #tbl.prod_extension_type, #tbl.prod_extension_value
from dbo.product p inner join dbo.product_digital pd on p.product_id = pd.product_id
inner join #tbl on p.product_id = #tbl.product_id
order by product_id

有人能指出一个更好的方法吗?看来我应该能够更快地做到这一点,而无需进行多次更新等。

【问题讨论】:

    标签: sql-server tsql sql-server-2012 unpivot


    【解决方案1】:

    我认为这会满足您的需求。这实现了UNPIVOT函数然后从information_schema.columns视图中获取列信息得到结果:

    select product_id,
      product_category,
      parent_id,
      delivery_in_days,
      PROD_EXTENSION_NAME,
      case when c.data_type = 'varchar' 
          then 'STRING' else null end as PROD_EXTENSION_TYPE,
      PROD_EXTENSION_VALUE
    from
    (
      select 
        p.product_id,
        p.product_category,
        p.parent_id,
        p.delivery_in_days,
        PRODUCT_NAME,
        PRODUCT_MNEMONIC
      from product p
      left join product_digital pd
        on p.product_id = pd.product_id
    ) src
    unpivot
    (
      PROD_EXTENSION_VALUE
      for PROD_EXTENSION_NAME in (PRODUCT_NAME, PRODUCT_MNEMONIC)
    ) up
    inner join 
    (
      select c.column_name, c.data_type
        from information_schema.columns c
        where c.table_name = 'PRODUCT_DIGITAL'
          and c.column_name in ('PRODUCT_NAME','PRODUCT_MNEMONIC')
    ) c
      on up.PROD_EXTENSION_NAME = c.column_name           
    

    SQL Fiddle with Demo

    结果是:

    | PRODUCT_ID | PRODUCT_CATEGORY | PARENT_ID | DELIVERY_IN_DAYS | PROD_EXTENSION_NAME | PROD_EXTENSION_TYPE | PROD_EXTENSION_VALUE |
    -----------------------------------------------------------------------------------------------------------------------------------
    |        100 |              DIG |         1 |                7 |        PRODUCT_NAME |              STRING |                 IMG1 |
    |        100 |              DIG |         1 |                7 |    PRODUCT_MNEMONIC |              STRING |             IMAWTRFL |
    |        101 |              DIG |         1 |                8 |        PRODUCT_NAME |              STRING |                 SND1 |
    |        101 |              DIG |         1 |                8 |    PRODUCT_MNEMONIC |              STRING |             SNDENGRV |
    |        102 |              DIG |         1 |                1 |        PRODUCT_NAME |              STRING |                 SND2 |
    |        102 |              DIG |         1 |                1 |    PRODUCT_MNEMONIC |              STRING |             SNDHRSLF |
    |        103 |              DIG |         2 |                2 |        PRODUCT_NAME |              STRING |                 IMG2 |
    |        103 |              DIG |         2 |                2 |    PRODUCT_MNEMONIC |              STRING |             IMGNBRTO |
    |        104 |              DIG |         2 |                1 |        PRODUCT_NAME |              STRING |                 SND3 |
    |        104 |              DIG |         2 |                1 |    PRODUCT_MNEMONIC |              STRING |             SNDGTWNE |
    

    编辑 #1:如果您想动态执行这种类型的转换,那么您将需要使用动态 SQL。为此,您将使用以下内容。

    首先,您需要将列列表获取到UNPIVOT

    select @colsUnpivot = stuff((select ','+quotename(C.name)
             from sys.columns as C
             where C.object_id = object_id('PRODUCT_DIGITAL') and
                   C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE') -- include the items you DO NOT want to unpivot
             for xml path('')), 1, 1, '')
    

    最终的脚本将是:

    DECLARE @colsUnpivot AS NVARCHAR(MAX),
        @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @colsUnpivot = stuff((select ','+quotename(C.name)
             from sys.columns as C
             where C.object_id = object_id('PRODUCT_DIGITAL') and
                   C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE')
             for xml path('')), 1, 1, '')
    
    select @cols = stuff((select ', '''+C.name + ''''
             from sys.columns as C
             where C.object_id = object_id('PRODUCT_DIGITAL') and
                   C.name not in ('PRODUCT_ID', 'PRODUCT_TYPE')
             for xml path('')), 1, 1, '')
    
    set @query 
      = '  select
              product_id,
              product_category,
              parent_id,
              delivery_in_days,
              PROD_EXTENSION_NAME,
              case when c.data_type = ''varchar'' 
                  then ''STRING'' else null end as PROD_EXTENSION_TYPE,
              PROD_EXTENSION_VALUE
            from 
            (
              select 
                p.product_id,
                p.product_category,
                p.parent_id,
                p.delivery_in_days,
                PRODUCT_NAME,
                PRODUCT_MNEMONIC
              from product p
              left join product_digital pd
                on p.product_id = pd.product_id
            ) src
            unpivot
            (
              PROD_EXTENSION_VALUE
              for PROD_EXTENSION_NAME in ('+ @colsunpivot +')
            ) up
            inner join 
            (
              select c.column_name, c.data_type
              from information_schema.columns c
              where c.table_name = ''PRODUCT_DIGITAL''
                and c.column_name in ('+@cols+')
            ) c
              on up.PROD_EXTENSION_NAME = c.column_name'
    
    
    exec(@query)
    

    SQL Fiddle with Demo。这将产生与原始版本相同的结果,但它是动态的。

    【讨论】:

    • 这对我帮助很大。如果我正在转换的列列表是动态的,您能否提供任何建议?我限制了测试列表,但实际上有 2 到 6 列可以转换。
    • @whipdancer 查看我的编辑,其中包括原始代码的动态 sql 版本。
    • 您的示例非常有帮助 - 我想我终于开始了解 UNPIVOT(更多的阅读和实验要做)。我希望我可以投票+100。谢谢!
    • @whipdancer 欢迎您。 PivotUnpivot 是很棒的功能。只是为了让您知道这也可以使用UNION ALL 而不是UNPIVOT 来完成,他们做同样的事情。 :)
    【解决方案2】:

    我可能会急于求成,但您说您的最终输出应该是 XML。 虽然我没有包括您的所有要求,但这样的东西是否适合您的要求? (我想我可能过度简化了您的要求)

    ;WITH PRODUCT (PRODUCT_ID, PRODUCT_CATEGORY, PARENT_ID, PARENT_TYPE, DELIVERY_IN_DAYS) AS
    (
        SELECT 100, 'DIG', 1, 1, 7  UNION ALL
        SELECT 101, 'DIG', 1, 1, 8  UNION ALL
        SELECT 102, 'DIG', 1, 1, 1  UNION ALL
        SELECT 103, 'DIG', 2, 1, 2  UNION ALL
        SELECT 104, 'DIG', 2, 1, 1
    )
    ,PRODUCT_DIGITAL (PRODUCT_ID, PRODUCT_TYPE, PRODUCT_NAME, PRODUCT_MNEMONIC) AS
    (
        SELECT 100, 'A', 'IMG1', 'IMAWTRFL' UNION ALL
        SELECT 101, 'B', 'SND1', 'SNDENGRV' UNION ALL
        SELECT 102, 'B', 'SND2', 'SNDHRSLF' UNION ALL
        SELECT 103, 'A', 'IMG2', 'IMGNBRTO' UNION ALL
        SELECT 104, 'B', 'SND3', 'SNDGTWNE'
    )
    
    SELECT   P.PRODUCT_CATEGORY
            ,P.PRODUCT_ID
            ,P.PARENT_TYPE
            ,P.PARENT_ID
            ,P.DELIVERY_IN_DAYS
            ,PD.PRODUCT_NAME
            ,PD.PRODUCT_MNEMONIC
    FROM PRODUCT            P
    JOIN PRODUCT_DIGITAL    PD  ON P.PRODUCT_ID = PD.PRODUCT_ID
    FOR XML PATH
    

    【讨论】:

    • 我最初尝试过(作为我示例中的最终选择),但它生成的 xml 不符合 XSD。
    猜你喜欢
    • 1970-01-01
    • 2017-05-17
    • 2021-11-13
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多