SQL将列名和数据类型与列值组合在一起

时间:2013-01-09 22:44:52

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

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

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

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

我的任务是为给定的父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

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

2 个答案:

答案 0 :(得分:4)

我认为这会做你想要的。这实现了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。除了动态之外,这将产生与原始版本相同的结果。

答案 1 :(得分:0)

我可能会开枪,但你说你的最终输出应该是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