【问题标题】:How to select denormalized ingredient data in SQL Server?如何在 SQL Server 中选择非规范化的成分数据?
【发布时间】:2018-04-17 16:58:58
【问题描述】:

我正在制作产品库存并使用以下语句显示数据

SELECT _PRODNAME      AS [Manufacture Product], 
       _BASEPRODNAME  AS [Sub Product], 
       _PRDDEFQTY     AS [Required Qty / Unit], 
       _PURQTY        AS [Purchase Qty], 
       _PURRETQTY     AS [Return Qty], 
       _ISSUEQTY      AS [Issue Qty], 
       _DAMAGEQTY     AS [Damage Qty], 
       _BALQTY        AS [Balance Qty], 
       _MINESTIMATE   AS [Estimate Qty], 
       _SALEQTY       AS [Sale Qty], 
       _MANUDAMAGEQTY AS Damage, 
       _AVAILQTY      AS [Avail Qty] 
FROM   dbo.VIEW_MANUFACTURING 

这个查询返回这个结果:

但我的预期结果是

在我的示例数据中,香草蛋糕是主要产品,奶油、鸡蛋、面粉是子产品,第 3、4、5、6、7、8 列是子产品数据,第 9、10、11、12 列是主要产品。

我的问题是如何单独显示这些数据,我对此没有任何想法。

为赏金编辑

正如您在第一张图片中看到的那样,有两种制造产品 1) Manu 2) Vanila Cake

在这里我们将得到香草蛋糕示例:

香草蛋糕有 3 个子产品 1) 奶油 2) 鸡蛋 3) 面粉

第 3 到 8 列与子产品相关(所需数量/单位列到平衡数量列)

第 9 到 12 列与制造产品相关(估计数量列到可用数量列)

预期结果如图 2 所示

【问题讨论】:

    标签: sql sql-server sql-server-2008 select denormalized


    【解决方案1】:

    您可以使用GROUPING SETS (Demo) 更简洁地做到这一点

    SELECT [Manufacture Product] = _PRODNAME,
           [Sub Product] = _BASEPRODNAME,
           [Required Qty / Unit] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_PRDDEFQTY) END,
           [Purchase Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_PURQTY) END,
           [Return Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_PURRETQTY) END,
           [Issue Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_ISSUEQTY) END,
           [Damage Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_DAMAGEQTY) END,
           [Balance Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 0 THEN SUM(_BALQTY) END,
           [Estimate Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 1 THEN SUM(_MINESTIMATE) END,
           [Sale Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 1 THEN SUM(_SALEQTY) END,
           Damage = CASE WHEN GROUPING(_BASEPRODNAME) = 1 THEN SUM(_MANUDAMAGEQTY) END,
           [Avail Qty] = CASE WHEN GROUPING(_BASEPRODNAME) = 1 THEN SUM(_AVAILQTY) END    
    FROM   dbo.VIEW_MANUFACTURING
    GROUP  BY GROUPING SETS ( ( _PRODNAME ), ( _PRODNAME, _BASEPRODNAME ) )
    ORDER  BY [Manufacture Product] ASC,
              GROUPING(_BASEPRODNAME) DESC,
              [Sub Product] ASC 
    

    如果需要,只需添加 WHERE _PRODNAME = 'Vanila Cake'


    或者你也可以去掉重复的CASE表达式

    WITH T
         AS (SELECT [Manufacture Product] = _PRODNAME,
                    [Sub Product] = _BASEPRODNAME,
                    [Required Qty / Unit] = SUM(_PRDDEFQTY),
                    [Purchase Qty] = SUM(_PURQTY),
                    [Return Qty] = SUM(_PURRETQTY),
                    [Issue Qty] = SUM(_ISSUEQTY),
                    [Damage Qty] = SUM(_DAMAGEQTY),
                    [Balance Qty] = SUM(_BALQTY),
                    [Estimate Qty] = SUM(_MINESTIMATE),
                    [Sale Qty] = SUM(_SALEQTY),
                    Damage = SUM(_MANUDAMAGEQTY),
                    [Avail Qty] = SUM(_AVAILQTY),
                    GrpFlag = GROUPING(_BASEPRODNAME)
             FROM   VIEW_MANUFACTURING
             GROUP  BY GROUPING SETS ( ( _PRODNAME ), ( _PRODNAME, _BASEPRODNAME ) ))
    SELECT T.[Manufacture Product],
           T.[Sub Product],
           OA1.*,
           OA2.*
    FROM   T
           OUTER APPLY (SELECT [Required Qty / Unit],[Purchase Qty],[Return Qty],[Issue Qty],[Damage Qty],[Balance Qty]
                        WHERE  GrpFlag = 0) OA1
           OUTER APPLY (SELECT [Estimate Qty],[Sale Qty], Damage, [Avail Qty]
                        WHERE  GrpFlag = 1) OA2
    ORDER  BY [Manufacture Product] ASC,
              GrpFlag DESC,
              [Sub Product] ASC 
    

    【讨论】:

    • 收到了预期的结果,变化不大
    【解决方案2】:

    演示

    http://rextester.com/AWO40913

    SQL

    SELECT _PRODNAME      AS [Manufacture Product],
           NULL           AS [Sub Product], 
           NULL           AS [Required Qty / Unit], 
           NULL           AS [Purchase Qty], 
           NULL           AS [Return Qty], 
           NULL           AS [Issue Qty], 
           NULL           AS [Damage Qty], 
           NULL           AS [Balance Qty], 
           _MINESTIMATE   AS [Estimate Qty], 
           _SALEQTY       AS [Sale Qty], 
           _MANUDAMAGEQTY AS Damage, 
           _AVAILQTY      AS [Avail Qty]
    FROM   dbo.VIEW_MANUFACTURING
    WHERE _PRODNAME = 'Vanila Cake'
    GROUP BY _PRODNAME, _MINESTIMATE, _SALEQTY, _MANUDAMAGEQTY, _AVAILQTY
    UNION ALL
    SELECT NULL      AS [Manufacture Product], 
           _BASEPRODNAME  AS [Sub Product], 
           _PRDDEFQTY     AS [Required Qty / Unit], 
           _PURQTY        AS [Purchase Qty], 
           _PURRETQTY     AS [Return Qty], 
           _ISSUEQTY      AS [Issue Qty], 
           _DAMAGEQTY     AS [Damage Qty], 
           _BALQTY        AS [Balance Qty], 
           NULL           AS [Estimate Qty], 
           NULL           AS [Sale Qty], 
           NULL           AS Damage, 
           NULL           AS [Avail Qty] 
    FROM   dbo.VIEW_MANUFACTURING
    WHERE _PRODNAME = 'Vanila Cake';
    

    备注

    可以使用DISTINCT 代替GROUP BY 甚至是简单的SELECT TOP 1GROUP BY 之所以被选中,是因为它比 DISTINCT 更快,并且会突出显示非规范化数据的任何问题。

    【讨论】:

      【解决方案3】:

      如果我正确理解了您的要求,这就是您的问题的解决方案。

      SELECT CASE WHEN record_type = 'Product' THEN _PRODNAME ELSE NULL END AS [Manufacture Product]
          ,CASE WHEN record_type = 'Sub Product' THEN _BASEPRODNAME ELSE NULL END AS [Sub Product]
          ,CASE WHEN record_type = 'Sub Product' THEN _PRDDEFQTY ELSE NULL END AS [Required Qty / Unit]
          ,CASE WHEN record_type = 'Sub Product' THEN _PURQTY ELSE NULL END AS [Purchase Qty]
          ,CASE WHEN record_type = 'Sub Product' THEN _PURRETQTY ELSE NULL END AS [Return Qty]
          ,CASE WHEN record_type = 'Sub Product' THEN _ISSUEQTY ELSE NULL END AS [Issue Qty]
          ,CASE WHEN record_type = 'Sub Product' THEN _DAMAGEQTY ELSE NULL END AS [Damage Qty]
          ,CASE WHEN record_type = 'Sub Product' THEN _BALQTY ELSE NULL END AS [Balance Qty]
          ,CASE WHEN record_type = 'Product' THEN _MINESTIMATE ELSE NULL END AS [Estimate Qty]
          ,CASE WHEN record_type = 'Product' THEN _SALEQTY ELSE NULL END AS [Sale Qty]
          ,CASE WHEN record_type = 'Product' THEN _MANUDAMAGEQTY ELSE NULL END AS [Damage]
          ,CASE WHEN record_type = 'Product' THEN _AVAILQTY ELSE NULL END AS [Avail Qty]
      FROM (
          SELECT _PRODNAME
              ,NULL AS _BASEPRODNAME
              ,NULL AS _PRDDEFQTY
              ,NULL AS _PURQTY
              ,NULL AS _PURRETQTY
              ,NULL AS _ISSUEQTY
              ,NULL AS _DAMAGEQTY
              ,NULL AS _BALQTY
              ,_MINESTIMATE
              ,_SALEQTY
              ,_MANUDAMAGEQTY
              ,_AVAILQTY
              ,'Product' AS record_type
          FROM (
              SELECT _PRODNAME
                  ,_BASEPRODNAME
                  ,_PRDDEFQTY
                  ,_PURQTY
                  ,_PURRETQTY
                  ,_ISSUEQTY
                  ,_DAMAGEQTY
                  ,_BALQTY
                  ,_MINESTIMATE
                  ,_SALEQTY
                  ,_MANUDAMAGEQTY
                  ,_AVAILQTY
                  ,'Product' AS record_type
                  ,ROW_NUMBER() OVER (
                      PARTITION BY _PRODNAME ORDER BY _PRODNAME
                      ) r_num
              FROM dbo.VIEW_MANUFACTURING
              ) v
          WHERE r_num = 1
          UNION
          SELECT _PRODNAME
              ,_BASEPRODNAME
              ,_PRDDEFQTY
              ,_PURQTY
              ,_PURRETQTY
              ,_ISSUEQTY
              ,_DAMAGEQTY
              ,_BALQTY
              ,_MINESTIMATE
              ,_SALEQTY
              ,_MANUDAMAGEQTY
              ,_AVAILQTY
              ,'Sub Product' AS record_type
          FROM dbo.VIEW_MANUFACTURING
          ) v1
      ORDER BY _PRODNAME  ,
               _BASEPRODNAME;
      

      输出在这里

      【讨论】:

      • 你的回答也完全满足了我的要求,但是那里有 4 个选择语句,由于我的记录数以百万计,所以这给了我性能问题
      • @ImranAliKhan 感谢您的反馈。即使有 4 个选择,也只有 2 个内部选择从数据库中读取数据。我很想知道你看到了什么样的表现,以及你是否设法提高了表现。如果是这样,你是怎么做到的。
      • 查看我接受的答案,它的表现也很干净
      【解决方案4】:

      在此示例中,我采用不是“Manu”(制造商)的独特产品。为了确保所需输出的第一行的顺序,我给该行一个子整数 = 1,用于列 theOrder。我对“Manu”数据进行子查询,假设您需要平均值,这是针对“香草蛋糕”的制造商产品行。

      我将所有这些与子产品详细信息联合起来,给它一个子订单 int = 2,theOrder。在您想要的输出中空白的列我保留为 NULL。

      这整个事情都是子查询的,我用 case 语句来清空列,类似于你想要的输出。这是使用制造商产品名称和theOrder 列的组合排序的,因此应首先列出主要产品,然后是子产品。

      DECLARE @temp TABLE ([Manufacture Product] varchar(100), [Sub Product] varchar(100), [Required Qty / Unit] decimal(16,2), [Purchase Qty] decimal(16,2)
                          , [Return Qty] decimal(16,2)
                          ,[Issue Qty] decimal(16,2), [Damage Qty] decimal(16,2), [Balance Qty] decimal(16,2), [Estimate Qty] decimal(16,2)
                          ,[Sale Qty] decimal(16,2), [Damage] decimal(16,2), [Avail Qty] decimal(16,2))
      
      INSERT INTO @temp
      VALUES ('manu',         '2 GOOD'     , 34.00, 502.00, 0.00, 0.00, 0.00, 502.00, 14.71, 0.00, 0.00, 14.71)
            ,('manu',         'CHOCO AL...', 34.00, 500.00, 0.00, 0.00, 0.00, 500.00, 14.71, 0.00, 0.00, 14.71)
            ,('Vanila Cake', 'Butter Cream', 10.00, 600.00, 0.00, 72.00, 0.00, 528.00, 52.80, 0.00, 0.00, 52.80)            
            ,('Vanila Cake', 'Eggs'        ,  2.00,1000.00, 0.00, 37.00, 0.00, 963.00, 52.80, 0.00, 0.00, 52.80)
            ,('Vanila Cake', 'Flour'       ,  5.00,   0.00, 0.00,  0.00, 0.00, 500.00, 52.80, 0.00, 0.00, 52.80)
      
      
      
      SELECT CASE WHEN theOrder = 1  THEN [Manufacture Product] ELSE '' END [Manufacture Product]
            ,CASE WHEN theOrder = 2 THEN [Sub Product] ELSE '' END [Sub Product]
            ,[Required Qty / Unit]
            ,[Purchase Qty]
            ,[Return Qty]
            ,[Issue Qty]
            ,[Damage Qty]
            ,[Balance Qty]
            ,[Estimate Qty]
            ,[Sale Qty]
            ,Damage
            ,[Avail Qty]
        FROM (
              SELECT DISTINCT 
                     1 [theOrder]
                    ,T.[Manufacture Product]
                    ,'' [Sub Product]
                    ,NULL [Required Qty / Unit]
                    ,NULL [Purchase Qty]
                    ,NULL [Return Qty]
                    ,NULL [Issue Qty]
                    ,NULL [Damage Qty]
                    ,NULL [Balance Qty]
                    ,(SELECT AVG(T2.[Estimate Qty]) FROM @temp T2 WHERE T2.[Manufacture Product] = 'Manu') [Estimate Qty]
                    ,(SELECT AVG([Sale Qty]) FROM @temp T2 WHERE T2.[Manufacture Product] = 'Manu') [Sale Qty]
                    ,(SELECT AVG([Damage]) FROM @temp T2 WHERE T2.[Manufacture Product] = 'Manu') [Damage]
                    ,(SELECT AVG([Avail Qty]) FROM @temp T2 WHERE T2.[Manufacture Product] = 'Manu') [Avail Qty]
      
                FROM @temp T 
               WHERE T.[Manufacture Product] <> 'Manu'
      
              UNION ALL
      
                SELECT 2 [theOrder]
                    ,T.[Manufacture Product]
                    ,T.[Sub Product]
                    ,T.[Required Qty / Unit]
                    ,T.[Purchase Qty]
                    ,T.[Return Qty]
                    ,T.[Issue Qty]
                    ,T.[Damage Qty]
                    ,T.[Balance Qty]
                    ,NULL [Estimate Qty]
                    ,NULL [Sale Qty]
                    ,NULL [Damage]
                    ,NULL [Avail Qty]
                FROM @temp T
               WHERE T.[Manufacture Product] <> 'Manu'
              ) AS dT
      ORDER BY dT.[Manufacture Product], dT.theOrder, dT.[Sub Product]
      

      这会产生与您所要求的类似的输出。 NULLS 可由报表工具处理。

      仅供参考,我在 SSRS 等报告工具中更容易做到这一点。如果是 SSRS,我会为 Manufacture Product 创建一个父列。

      【讨论】:

        【解决方案5】:

        你真的应该在应用层做这种类型的操作。为什么? SQL 表和结果集代表无序 集——除非您特别指定了排序。您尚未指定订单。

        其次,SQL 查询的所有列都应具有相同的列数。您似乎想要为不同的行使用不同的数字。

        一种部分解决方案是仅将名称放在“第一”行:

        select (case when row_number() over (partition by _prodname order by _baseprodname) = 1
                    then _prodname
                end) as [Manufacture Product],
               . . .
        from dbo.VIEW_MANUFACTURING
        order by _prodname, _baseprodname;
        

        要将它们放在不同的行上,您可以这样做:

        select v.[Manufacture Product], v.[Sub Product], . . .
        from (select vm.*,
                     row_number() over (partition by _prodname order by _baseprodname) as seqnum
              from dbo.VIEW_MANUFACTURING vm
             ) vm outer apply
             (values (1, _ProdName, NULL, NULL, . . .),
                     (2, NULL, _BaseProdName, . . .)
             ) v(seqnum, [Manufacture Product], [Sub Product], . . .)
        where vm.seqnum = 1 or v.seqnum = 2
        order by v.[Manufacture Product], v.seqnum, v.[Sub Product];
        

        【讨论】:

        • 感谢您的回复,我检查了您的样品,但问题是它没有划分主要和子产品信息,请参阅我更新的问题,我发布了所需的图像
        • @ImranAliKhan 。 . .第二个版本可以。
        • 我删除了所有 , . . .来自代码,但它给了我 Msg 10709, Level 16, State 1, Line 8 表值构造函数中每一行的列数必须相同。
        • @ImranAliKhan 。 . .您必须为“1”填写NULLs,以便它们与“2”的列匹配。
        • 最好在钻取层或应用层做。您是否尝试过使用 union all 将 null 值替换为空字符串?
        【解决方案6】:

        我假设这可以作为空值替换为空字符串,并用于演示您希望在 Image2 中看到的方式。

        通过Case statement订购可能会得到结果集,请检查并告诉我

        WITH cte
        AS (
            SELECT _PRODNAME AS [Manufacture Product]
                ,NULL [Sub Product]
                ,NULL [Required Qty / Unit]
                ,NULL [Purchase Qty]
                ,NULL [Return Qty]
                ,NULL [Issue Qty]
                ,NULL [Damage Qty]
                ,SUM(_BALQTY) AS [Balance Qty]
                ,SUM(_MINESTIMATE) AS [Estimate Qty]
                ,SUM(_SALEQTY) AS [Sale Qty]
                ,SUM(_MANUDAMAGEQTY) AS Damage
                ,SUM(_AVAILQTY) AS [Avail Qty]
            FROM dbo.VIEW_MANUFACTURING
            GROUP BY _PRODNAME
        
            UNION ALL
        
            SELECT NULL AS [Manufacture Product]
                ,_BASEPRODNAME AS [Sub Product]
                ,NULL AS [Required Qty / Unit]
                ,NULL AS [Purchase Qty]
                ,NULL AS [Return Qty]
                ,NULL AS [Issue Qty]
                ,NULL AS [Damage Qty]
                ,NULL AS [Balance Qty]
                ,SUM(_MINESTIMATE) AS [Estimate Qty]
                ,SUM(_SALEQTY) AS [Sale Qty]
                ,SUM(_MANUDAMAGEQTY) AS Damage
                ,SUM(_AVAILQTY) AS [Avail Qty]
            FROM dbo.VIEW_MANUFACTURING
            GROUP BY _BASEPRODNAME
            )
        SELECT ISNULL([Manufacture Product], '') [Manufacture Product]
            ,ISNULL([Sub Product], '') [Sub Product]
            ,ISNULL([Required Qty / Unit], '') [Required Qty / Unit]
            ,ISNULL([Purchase Qty], '') [Purchase Qty]
            ,ISNULL([Return Qty], '') [Return Qty]
            ,ISNULL([Issue Qty], '') [Issue Qty]
            ,ISNULL([Damage Qty], '') [Damage Qty]
            ,ISNULL([Balance Qty], '') [Balance Qty]
            ,ISNULL([Estimate Qty], '') [Estimate Qty]
            ,ISNULL([Sale Qty], '') [Sale Qty]
            ,ISNULL(Damage, '') Damage
            ,ISNULL([Avail Qty], '') [Avail Qty]
        FROM cte
         ORDER BY 
           CASE WHEN [Sub Product] is null and [Manufacture Product] is not null  then [Manufacture Product] END DESC
                 , CASE WHEN [Manufacture Product] IS NULL THEN [Sub Product] END
        

        【讨论】:

        • 与你的解决方案[制造产品]细节在最后一行,它应该是第一行,只有当我有一个[制造产品]时才适合,如果我有多个[制造产品] 然后根据您的解决方案,所有子产品都排在首位,所有 [制造产品] 排在最后
        猜你喜欢
        • 1970-01-01
        • 2010-11-20
        • 2013-11-21
        • 2011-06-14
        • 2012-11-18
        • 2016-05-27
        • 2013-05-23
        • 2020-05-21
        • 2014-12-23
        相关资源
        最近更新 更多