如何 select 在 SQL 服务器中对成分数据进行非规范化?

How to select denormalized ingredient data in SQL Server?

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

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 列与子产品相关(Required Qty / Unit Column To Balance Qty Column)

第 9 至 12 列与制造产品相关(Estimate Qty Column To Avail Qty Column)

预期结果如图 2 所示

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

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

一个部分解决方案是只将名称放在 "first" 行:

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];

我假设这可以作为用空字符串替换的空值,并用于演示您希望在 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

在这个例子中,我使用了非 'Manu'(制造商)的独特产品。为了确保所需输出的第一行的顺序,我为该行指定了一个子顺序整数 = 1,用于列 theOrder。我子查询 'Manu' 数据,假设您想要平均值,这是针对 'Vanilla Cake'.

的制造商产品行

I UNION ALL this with the sub product details,给它一个子订单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 创建一个父列。

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

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;

输出在这里

演示

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';

备注

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

您可以更简洁地使用 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