【问题标题】:How to denormalize this SQL Server table?如何对这个 SQL Server 表进行非规范化?
【发布时间】:2016-08-11 18:13:28
【问题描述】:

我有一个非常宽的表像这样非规范化(如果你可以这么说的话),选项列达到 100+

Year  ProductID ProductName Option1 Option2 Option3 ....Option100
-----------------------------------------------------------------
2016    1        Test1        A1      A1a     A3
2015    1        Test1        A1      A2      A2a

问题是我们有动态查询试图确定选项列,然后在其中找到选项值

@SQL= 'SELECT Option' + @getOptionNum +' FROM ProductMapping

理想情况下,我希望将其转换为类似的东西

Year ProductID ProductName OptionName
-------------------------------------
2016   1         Test1      Option1
2016   1         Test1      Option2
2016   1         Test1      Option3
2015   1         Test1      Option1
2015   1         Test1      Option2
2015   1         Test1      Option3

OptionID OptionName OptionValue  Year
-------------------------------------
  1        Option1     A1        2016
  2        Option2     A1a       2016
  3        Option3     A3        2016
  4        Option1     A1        2015
  5        Option2     A2        2015
  6        Option3     A2a       2015

SELECT * 
FROM ProductMapping map
LEFT JOIN OptionList list ON map.OptionName = list.OptionName
                          AND map.Year = list.Year
                          AND map.OptionName = 'Option1'

我遇到的问题是如何通过查询将该宽表转换为两个表结构,因为它有很多列和行,我无法手动规范所有这些。

是的,我也理解理想情况下,第二个表需要进一步规范化,以将 Option1...Option3 保存在单独的表中,并将 Option1..A1 映射保存在单独的表中,但这是一个开始......

希望这个简单的例子能阐明以下事实

  1. Option1...100 列需要在单独的表中进行规范化
  2. 映射到值的选项列每年都在变化

有什么想法吗?

【问题讨论】:

  • 您是否考虑过使用文档存储数据库而不是关系(表)数据库?可扩展列是传统 RDBMS 的一个弱点。
  • 太多现有进程依赖于这些核心表...我正在尝试我的替代方案作为概念证明来摆脱我们正在进行的所有动态查询
  • 为什么需要一个两表解决方案?这为您带来了什么是规范化的单表无法获得的?只需将OptionValue 添加到您的第一个表中,您就可以将所有内容都放在一个表中,并进行规范化。
  • 立即停止......并解雇设计这个的dba!
  • 为什么不将值放在一个表中?

标签: sql-server denormalized


【解决方案1】:
Declare @YourTable table (Year int,ProductID int,ProductName varchar(50),Option1 varchar(50),Option2 varchar(50),Option3 varchar(50))
Insert into @YourTable values
(2016,1,'Test1','A1','A1a','A3'),
(2015,1,'Test1','A1','A2','A2a')

Declare @XML xml
Set @XML = (Select * from @YourTable for XML RAW)

Select * 
 From  (
        Select Year        = r.value('@Year','int')
              ,ProductID   = r.value('@ProductID','int')
              ,ProductName = r.value('@ProductName','varchar(50)')
              ,OptionName  = Attr.value('local-name(.)','varchar(100)')
            From  @XML.nodes('/row') as A(r)
            Cross Apply A.r.nodes('./@*[local-name(.)!="ProductID"]') as B(Attr)
       ) A
 Where OptionName Like 'Option%'
 Order by Year Desc,OptionName

Select OptionID=Row_Number() over (Order By Year Desc,OptionName),* 
 From (
        Select OptionName  = Attr.value('local-name(.)','varchar(100)')
              ,OptionValue = Attr.value('.','varchar(100)') 
              ,Year        = r.value('@Year','int')
         From  @XML.nodes('/row') as A(r)
         Cross Apply A.r.nodes('./@*[local-name(.)!="ProductID"]') as B(Attr)
         --CROSS APPLY A.r.nodes('./@*') AS B(Attr)
        ) A 
 Where OptionName Like 'Option%'

返回

Year    ProductID   ProductName OptionName
2016    1           Test1       Option1
2016    1           Test1       Option2
2016    1           Test1       Option3
2015    1           Test1       Option1
2015    1           Test1       Option2
2015    1           Test1       Option3

OptionID    OptionName  OptionValue Year
1           Option1     A1          2016
2           Option2     A1a         2016
3           Option3     A3          2016
4           Option1     A1          2015
5           Option2     A2          2015
6           Option3     A2a         2015

编辑

现在如果你想要一个 STRAIGHT 标准化

Declare @YourTable table (Year int,ProductID int,ProductName varchar(50),Option1 varchar(50),Option2 varchar(50),Option3 varchar(50))
Insert into @YourTable values
(2016,1,'Test1','A1','A1a','A3'),
(2015,1,'Test1','A1','A2','A2a')

Declare @XML xml
Set @XML = (Select * from @YourTable for XML RAW)


Select ID    = r.value('@id','int')                             --<<'@id' Should be YOUR PK
      ,Item  = Attr.value('local-name(.)','varchar(100)')
      ,Value = Attr.value('.','varchar(max)') 
 From  @XML.nodes('/row') as A(r)
 Cross Apply A.r.nodes('./@*[local-name(.)!="id"]') as B(Attr)  --<<'id' Should be YOUR PK
 --CROSS APPLY A.r.nodes('./@*') AS B(Attr)

返回(空值通常是您的 PK)

ID      Item        Value
NULL    Year        2016
NULL    ProductID   1
NULL    ProductName Test1
NULL    Option1     A1
NULL    Option2     A1a
NULL    Option3     A3
NULL    Year        2015
NULL    ProductID   1
NULL    ProductName Test1
NULL    Option1     A1
NULL    Option2     A2
NULL    Option3     A2a

【讨论】:

  • 这真是太棒了!工作...但是某些 OptionX 列有 NULL,年份对它们显示为 NULL?
  • @SQLSeeker 让我再试一次
  • @SQLSeeker 刚刚添加了 2017 年的虚拟行,在 Option3 中为空。我看到 2012 年的两行(每组)。也许你可以提供样本
  • @SQLSeeker 不得不问,你今年有空吗?
  • 嗯..可能是..我正在检查
【解决方案2】:

我会将表格规范化为单个表格,每个选项都有行:

select pm.year, pm.productid, pm.productname, v.option, v.optionvalue
from productmapping pm cross apply
     (values ('option1', option1), ('option2', option2), . . .
     ) v(option, optionvalue);

但是,我正在努力将其放入两个表中。我可以想象为选项提供 ID(但不是选项/值对)。

【讨论】:

    猜你喜欢
    • 2013-11-21
    • 2013-03-08
    • 1970-01-01
    • 1970-01-01
    • 2016-11-12
    • 2019-05-05
    • 1970-01-01
    • 2011-12-18
    • 2012-12-31
    相关资源
    最近更新 更多