【问题标题】:SQL Server - Sum based on another column, and combine with STUFF/XML PATH?SQL Server - 基于另一列求和,并与 STUFF/XML PATH 结合?
【发布时间】:2019-04-25 15:50:41
【问题描述】:

我不确定这是否令人难以置信的复杂,所以提前道歉。

CREATE TABLE TestOrder
(
[OrderLineID] INT NOT NULL IDENTITY PRIMARY KEY,
[Item] NVARCHAR(30) NULL,
[OrderQty] INT NULL,
[Status1] INT NULL, 
[Quantity_at_sts1] INT NULL,
[Status2] INT NULL, 
[Quantity_at_sts2] INT NULL,
[Status3] INT NULL, 
[Quantity_at_sts3] INT NULL,
[Status4] INT NULL, 
[Quantity_at_sts4] INT NULL,
[Status5] INT NULL, 
[Quantity_at_sts5] INT NULL,
[OrderRef] NVARCHAR(10) NULL
)

INSERT INTO TestOrder
values
('TSHIRT','1','100','1','0','0','0','0','0','0','0','0','Ord.1'),
('SOCKS','4','50','4','0','0','0','0','0','0','0','0','Ord.2'),
('SHIRT','5','10','1','50','2','0','0','0','0','0','0','Ord.3'),
('SHIRT','5','100','2','0','0','0','0','0','0','0','0','Ord.3'),
('SOCKS','10','10','4','50','2','0','0','0','0','0','0','Ord.4'),
('SOCKS','10','50','3','100','1','0','0','0','0','0','0','Ord.4')

.

| OrderLineID | Item | OrderQty | Status1 | Quantity_at_sts1 | Status2 | Quantity_at_sts2 | Status3 | Quantity_at_sts3 | Status4 | Quantity_at_sts_4 | Status5 | Quantity_at_sts5 | OrderRef |
|-------------|------|----------|---------|------------------|---------|------------------|---------|------------------|---------|-------------------|---------|------------------|----------|
|    1        |TSHIRT|   1      |    100  |       1          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.1  |
|    2        |SOCKS |   4      |    50   |       4          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.2  |
|    3        |SHIRT |   5      |    10   |       1          |   50    |    2             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.3  |
|    4        |SHIRT |   5      |    100  |       2          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.3  |
|    5        |SOCKS |   10     |    10   |       4          |   50    |    2             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.4  |
|    6        |SOCKS |   10     |    50   |       3          |   100   |    1             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.4  |

所以这个表实际上是一个订单表,所有的“status1、quantity_at_sts1、status2...”等列实际上都是该订单的进度。想象一下,您有 10 到 100 之间的各种状态,10 表示“未开始”,100 表示“完成并已发送”。 “OrderRef”列实际上是这些感兴趣订单的关键标识符;例如'Ord.3' 和 'Ord.4' 的 4 行实际上只是每个订单上的一个项目,但每行具有不同的状态。

到目前为止,我一直在使用 case 语句将所有这些状态放入一个更易于阅读的列中(忽略所有强制转换,因为它们仅在实际数据上需要):

select
CASE
WHEN status2 = 0 THEN '['+CAST(status1 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts1 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))
WHEN status2 > 0 AND status3 = 0 THEN '['+CAST(status1 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts1 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - '+'['+CAST(status2 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts2 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))
WHEN status3 > 0 AND status4 = 0 THEN '['+CAST(status1 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts1 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - '+'['+CAST(status2 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts2 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - ['+CAST(status3 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts3 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))
WHEN status4 > 0 AND status5 = 0 THEN '['+CAST(status1 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts1 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - '+'['+CAST(status2 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts2 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - ['+CAST(status3 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts3 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10)) + ' - ['+CAST(status4 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts4 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))
when status5 > 0 then '['+CAST(status1 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts1 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - '+'['+CAST(status2 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts2 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))+' - ['+CAST(status3 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts3 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10)) + ' - ['+CAST(status4 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts4 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10)) + ' - ['+CAST(status5 AS NVARCHAR(10))+'] '+CAST(CAST(CAST(quantity_at_sts5 AS DECIMAL(20, 6)) AS FLOAT) AS NVARCHAR(10))
END AS 'all_statuses'
, *
from testorder

.

| all_statuses     |  OrderLineID | Item | OrderQty | Status1 | Quantity_at_sts1 | Status2 | Quantity_at_sts2 | Status3 | Quantity_at_sts3 | Status4 | Quantity_at_sts_4 | Status5 | Quantity_at_sts5 | OrderRef |
|------------------|--------------|------|----------|---------|------------------|---------|------------------|---------|------------------|---------|-------------------|---------|------------------|----------|
| [100] 1          |      1       |TSHIRT|   1      |    100  |       1          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.1  |
| [50] 4           |      2       |SOCKS |   4      |    50   |       4          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.2  |
| [10] 1 - [50] 2  |      3       |SHIRT |   5      |    10   |       1          |   50    |    2             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.3  |
| [100] 2          |      4       |SHIRT |   5      |    100  |       2          |   0     |    0             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.3  |
| [10] 4 - [50] 2  |      5       |SOCKS |   10     |    10   |       4          |   50    |    2             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.4  |
| [50] 3 - [100] 1 |      6       |SOCKS |   10     |    50   |       3          |   100   |    1             |   0     |         0        |    0    |         0         |    0    |         0        |   Ord.4  |

我的理想是按每个唯一的 OrderRef 值汇总,all_statuses 是具有该 OrderRef 值的所有行的总和:

| all_statuses               |  OrderQty  |  OrderRef |
|----------------------------|------------|-----------|
| [100] 1                    |      1     |   Ord.1   |
| [50] 4                     |      4     |   Ord.2   |
| [10] 1 - [50] 2 - [100] 2  |      5     |   Ord.3   |
| [10] 4 - [50] 5 - [100] 1  |      10    |   Ord.4   |

,但是我有 2 个问题正在努力解决:

  1. 将所有状态(甚至跨多行)放到一列中
  2. 汇总具有相同状态的多行的数量(例如,在 Ord.4 的情况下)

我对 STUFF FOR XML PATH 有一点经验,并认为我可以用 STUFF 组合做一些荒谬的 CASE,但我正在努力寻找一种方法。这是不是有点太复杂了?

【问题讨论】:

  • 我很难相信您需要 5 个状态栏。您还需要一个单独的状态表并使用外键加入表

标签: sql sql-server sum


【解决方案1】:

第一步是将列转为行,您可以使用CROSS APPLY 和表值构造函数完成此操作

SELECT  o.OrderRef, x.*
FROM    #TestOrder AS o
        CROSS APPLY
        (VALUES 
            (1, o.Status1, o.Quantity_at_sts1),
            (2, o.Status2, o.Quantity_at_sts2),
            (3, o.Status3, o.Quantity_at_sts3),
            (4, o.Status4, o.Quantity_at_sts4),
            (5, o.Status5, o.Quantity_at_sts5)
        ) x (StatusNumber, StatusID, Quantity)
WHERE   x.StatusID > 0;

这给出了:

OrderRef    StatusNumber    StatusID    Quantity
--------------------------------------------------
Ord.1           1              100          1
Ord.2           1              50           4
Ord.3           1              10           1
Ord.3           2              50           2
Ord.3           1              100          2
Ord.4           1              10           4
Ord.4           2              50           2
Ord.4           1              50           3
Ord.4           2              100          1

然后你需要总结数量,按订单分组,就是一个简单的SUM/GROUP BY。我还添加了一个ROW_NUMBER() 列,以供稍后在串联期间订购商品时使用:

SELECT  o.OrderRef,
        Quantity = SUM(x.Quantity),
        x.StatusID,
        RowNumber = ROW_NUMBER() OVER(PARTITION BY o.OrderRef ORDER BY MIN(o.OrderLineID), MIN(x.StatusNumber))
FROM    #TestOrder AS o
        CROSS APPLY
        (VALUES 
            (1, o.Status1, o.Quantity_at_sts1),
            (2, o.Status2, o.Quantity_at_sts2),
            (3, o.Status3, o.Quantity_at_sts3),
            (4, o.Status4, o.Quantity_at_sts4),
            (5, o.Status5, o.Quantity_at_sts5)
        ) x (StatusNumber, StatusID, Quantity)
WHERE   x.StatusID > 0
GROUP BY x.StatusID, o.OrderRef

OrderRef    Quantity    StatusID    RowNumber
----------------------------------------------
Ord.1           1          100          1
Ord.2           4          50           1
Ord.3           1          10           1
Ord.3           2          50           2
Ord.3           2          100          3
Ord.4           4          10           1
Ord.4           5          50           2   <-- TWO ROWS GROUPED HERE
Ord.4           1          100          3

最后,您可以使用上面的派生表来创建您的字符串,然后使用 SQL Server 的 XML 扩展连接它们:

WITH ALLStatuses AS
(   SELECT  o.OrderRef,
            Quantity = SUM(x.Quantity),
            x.StatusID,
            RowNumber = ROW_NUMBER() OVER(PARTITION BY o.OrderRef ORDER BY MIN(o.OrderLineID), MIN(x.StatusNumber))
    FROM    #TestOrder AS o
            CROSS APPLY
            (VALUES 
                (1, o.Status1, o.Quantity_at_sts1),
                (2, o.Status2, o.Quantity_at_sts2),
                (3, o.Status3, o.Quantity_at_sts3),
                (4, o.Status4, o.Quantity_at_sts4),
                (5, o.Status5, o.Quantity_at_sts5)
            ) x (StatusNumber, StatusID, Quantity)
    WHERE   x.StatusID > 0
    GROUP BY x.StatusID, o.OrderRef
) 
SELECT  all_statuses  = STUFF(( SELECT  CONCAT(' - ', QUOTENAME(S.StatusID), ' ', s.Quantity)
                                FROM    ALLStatuses AS s
                                WHERE   s.OrderRef = o.OrderRef
                                ORDER BY RowNumber
                                FOR XML PATH(''), TYPE
                            ).value('.', 'NVARCHAR(MAX)'), 1, 3, ''),
        o.OrderQty,
        o.OrderRef
FROM    #TestOrder AS o
GROUP BY o.OrderRef, o.OrderQty;

输出

all_statuses                OrderQty      OrderRef
------------------------------------------------
[100] 1                         1           Ord.1
[50] 4                          4           Ord.2
[10] 1 - [50] 2 - [100] 2       5           Ord.3
[10] 4 - [50] 5 - [100] 1       10          Ord.4

【讨论】:

  • 这太棒了。非常清晰的细分和解释。真是太感谢你了。
猜你喜欢
  • 2012-12-16
  • 2015-09-21
  • 2018-08-22
  • 2020-05-31
  • 1970-01-01
  • 2021-12-02
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
相关资源
最近更新 更多