仅选择特定列的SUM不为零的行,并按另一列分组SQL Server

qxsslcnc  于 2022-12-17  发布在  SQL Server
关注(0)|答案(1)|浏览(146)

我希望筛选表,以排除“金额”列的总和给予零的行以及按ID分组的行,例如,如果同一ID为“-10,10 and 15”。我希望排除-10 and 10的行,保留第15行
到目前为止,我能够使用GROUP BY过滤表,但我需要获得ID才能在同一个表上再次执行选择,而且我还需要SUM拆分。

CREATE Table #myData 
(
    Id INT, 
    DateInit date, 
    DebtorName varchar(10), 
    Trancode varchar(10), 
    Amount VARCHAR(50), 
    DebtorID varchar(50), 
    ClientRef NVARCHAR(256)
)

INSERT INTO #myData 
VALUES
 (1, '1/1/2022', 'BURNETTE', '13 P', '-53.42', '23916762', 'T081819513107-00009') 
,(2, '1/1/2022', 'BURNETTE', '13 P', '-28.83', '23916761', 'T081819513107-00008') 
,(3, '1/1/2022', 'BURNETTE', '13 P', '-12.08', '23916764', 'T081819513107-00010') 
,(4, '1/1/2022', 'BURNETTE', '1 P', '12.08', '23916764', 'T081819513107-00010') 
,(5, '1/1/2022', 'BURNETTE', '1 P', '28.83', '23916761', 'T081819513107-00008') 
,(6, '1/1/2022', 'BURNETTE', '1 P', '53.42', '23916762', 'T081819513107-00009') 
,(7, '1/1/2022', 'BURNETTE', '2 P', '66.41', '23916762', 'T081819513107-00009') 
,(8, '1/1/2022', 'BURNETTE', '2 P', '71.10', '23916762', 'T081819513107-00009') 
,(9, '1/1/2022', 'BURNETTE', '2 P', '12.08', '23916764', 'T081819513107-00010') 

SELECT 
    DebtorID, 
    SUM(CASE 
            WHEN TRY_PARSE(A.Amount AS float) IS NULL
                THEN (-1) * CAST(SUBSTRING(A.Amount, CHARINDEX('-', A.amount) + 1, LEN(A.Amount)) AS float)
                ELSE CAST(A.Amount AS float)
        END) AS amountSum
FROM 
    #myData A
GROUP BY 
    DebtorID
HAVING 
    SUM(CASE 
            WHEN TRY_PARSE(A.Amount AS float) IS NULL
                THEN (-1) * CAST(SUBSTRING(A.Amount, CHARINDEX('-', A.Amount) + 1, LEN(A.Amount)) AS float)
                ELSE CAST(A.Amount AS float)
        END) != 0

但此查询返回:
| 借方ID|金额|
| - ------|- ------|
| 小行星239|一百三十七点五一|
| 小行星239|十二时零八分|
最终目标是过滤这些结果并将它们保存在与原始表相同的另一个表中(#myData)。
这就是我想要输入#myData的结果:
| 身份证|初始化日期|债务人姓名|代码转换|数量|借方ID|客户参考|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|
| 六个|2022年1月1日|伯内特|2名专业人员|六十六点四一分|小行星239|编号:T081819513107-00009|
| 八个|2022年1月1日|伯内特|2个p|七十一点一零|小行星239|编号:T081819513107-00009|
| 八个|2022年1月1日|伯内特|2个p|十二时零八分|小行星239|编号:T081819513107-00010|
我将感激任何帮助,谢谢!

更新2 -新场景

谢谢你的帮助,但是这个场景必须有相同的输出。
| 身份证|初始化日期|债务人姓名|代码转换|数量|借方ID|客户参考|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|
| 1个|2022年1月1日|伯内特|13名专业人员| -53.42 |小行星239|编号:T081819513107-00009|
| 第二章|2022年1月1日|伯内特|13名专业人员| -12.08 |小行星239|编号:T081819513107-00010|
| 三个|2022年1月1日|伯内特|1个p|十二时零八分|小行星239|编号:T081819513107-00010|
| 四个|2022年1月1日|伯内特|1个p|二十八点八三分|小行星239|编号:T081819513107-00008|
| 五个|2022年1月1日|伯内特|1个p|六十六点四一分|小行星239|编号:T081819513107-00009|
| 六个|2022年1月1日|伯内特|13个| -28.83 |小行星239|编号:T081819513107-00008|
| 七|2022年1月1日|伯内特|1个p|五十三点四二|小行星239|编号:T081819513107-00009|
| 八个|2022年1月1日|伯内特|1个p|七十一点一零|小行星239|编号:T081819513107-00009|
| 九|2022年1月1日|伯内特|2个p|十二时零八分|小行星239|编号:T081819513107-00010|
预期结果输出:
| 身份证|初始化日期|债务人姓名|代码转换|数量|借方ID|客户参考|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|
| 五个|2022年1月1日|伯内特|1个p|六十六点四一分|小行星239|编号:T081819513107-00009|
| 八个|2022年1月1日|伯内特|1个p|七十一点一零|小行星239|编号:T081819513107-00009|
| 九|2022年1月1日|伯内特|2个p|十二时零八分|小行星239|编号:T081819513107-00010|

b4wnujal

b4wnujal1#

如果您需要原始数据集,但又想包含一个聚合,或者想根据聚合进行过滤或排序,那么 Window Functions 可以帮您大忙。

**更新:**问题的性质略有变化,但原始建议仍然相同,我们不想使用GROUP BY,因为我们丢失了详细信息行,如果必须在sing表达式中执行此操作,则 * 窗口函数 * 提供了简单的语法解决方案

在这个解决方案中,我使用CTE来区分金额列的格式和聚合的计算,但是您可以使用CROSS APPLY来进行格式设置。
注意,在这个解决方案中,我们根本没有应用SUM,我们只是匹配值,这意味着对于非数字值的工作是相同的解决方案
在小提琴上,我试着详细描述了一些步骤,但最终,我们按以下顺序处理数据:
1.将Amount列格式化为数值

  • 这实际上是一个冗余操作,但简化了以后可能执行的其他聚合操作

1.使用ROW_NUMBER() window function标识唯一的AmountDebtorID值对
1.过滤出匹配的唯一对
1.返回已处理的数据,这些数据与您可能需要的任何其他列或引用联接在一起。

WITH FormattedData as (
  SELECT ID, DebtorID, COALESCE(TRY_PARSE(amount as float)
                       , (-1)*CAST(SUBSTRING(amount,CHARINDEX('-',amount)+1,LEN(amount)) AS float)
                   ) as Amount
  FROM #myData
)
, DataSequenced as (
 SELECT a.ID, a.DebtorID, a.Amount, ROW_NUMBER() OVER (PARTITION BY a.DebtorID, a.Amount ORDER BY a.ID) as RN
 FROM FormattedData a
)
, MatchesRemoved as (
  SELECT a.ID, a.DebtorID, A.Amount
  FROM DataSequenced a
  WHERE NOT EXISTS (SELECT lkp.ID 
                    FROM DataSequenced lkp 
                    WHERE lkp.DebtorID = a.DebtorID
                      AND lkp.Amount = -a.Amount
                      AND lkp.RN = a.RN)
)

SELECT myData.ID, DateInit, DebtorName, Trancode, a.Amount, myData.DebtorID
  , myData.ClientRef
 FROM MatchesRemoved a
 INNER JOIN #myData myData ON myData.ID = a.ID
ORDER BY ID

看看这把小提琴:https://dbfiddle.uk/XY8h2uvG
因为我们没有专门对Amount列应用聚合,所以它不需要是数字,我们实际上可以使用简单的字符串比较,您必须在您的环境中分析它,但下面的查询是类似的:

WITH DataSequenced as (
 SELECT a.ID, a.DebtorID, a.Amount
  , ROW_NUMBER() OVER (PARTITION BY a.DebtorID, a.Amount ORDER BY a.ID) as RN
  , CASE
        WHEN LEFT(a.Amount,1) = '-' THEN RTRIM(RIGHT(a.Amount, LEN(a.Amount) - 1)) 
        ELSE RTRIM(a.Amount)
    END as AbsAmount
 FROM #myData a
)
, MatchesRemoved as (
  SELECT a.ID, a.DebtorID, A.Amount
  FROM DataSequenced a
  WHERE NOT EXISTS (SELECT lkp.ID 
                    FROM DataSequenced lkp 
                    WHERE lkp.DebtorID = a.DebtorID
                      AND lkp.AbsAmount = a.AbsAmount
                      AND lkp.ID <> a.ID
                      AND lkp.RN = a.RN)
)
SELECT myData.ID, DateInit, DebtorName, Trancode, a.Amount, myData.DebtorID
  , myData.ClientRef
 FROM MatchesRemoved a
 INNER JOIN #myData myData ON myData.ID = a.ID
ORDER BY ID

根据数据源的复杂性和可能可用(或NOT可用)的索引,在对临时表的多个查询中处理这个问题可能更有意义。
以下是以前的尝试,以解决原来的要求。我选择离开这些在由于模糊的OP标题,虽然过程和解决方案(包括小提琴)可能会证明有助于其他人谁发现自己在这一页。

  • 查找债务人的SUM为零的交易 *:这被解释为返回没有零余额的债务人的所有交易。

https://dbfiddle.uk/l-64hpZF

WITH FormattedData as (
  SELECT ID, DebtorID, COALESCE(TRY_PARSE(amount as float)
                       , (-1)*CAST(SUBSTRING(amount,CHARINDEX('-',amount)+1,LEN(amount)) AS float)
                   ) as Amount
  FROM #myData
)
, DataWithSum as (
 SELECT ID, DebtorID, Amount,  SUM(Amount) OVER( Partition By DebtorID) AS SumAmount
 FROM FormattedData
)
SELECT myData.ID, myData.DebtorID, D.Amount, D.SumAmount
  , myData.ClientRef
 FROM DataWithSum D
 -- join back if you need additional columns
 INNER JOIN #myData myData ON myData.ID = D.ID
 WHERE SumAmount > 0

| 识别号|借方ID|金额|合计金额|客户参考|
| - ------|- ------|- ------|- ------|- ------|
| 1个|小行星239| -53.42 |一百三十七点五一|编号:T081819513107-00009|
| 三个|小行星239| -12.08 |十二时零八分|编号:T081819513107-00010|
| 四个|小行星239|十二时零八分|十二时零八分|编号:T081819513107-00010|
| 六个|小行星239|五十三点四二|一百三十七点五一|编号:T081819513107-00009|
| 七|小行星239|六十六点四一分|一百三十七点五一|编号:T081819513107-00009|
| 八个|小行星239|七十一点一|一百三十七点五一|编号:T081819513107-00009|
| 九|小行星239|十二时零八分|十二时零八分|编号:T081819513107-00010|

  • 我希望您能够理解,如果将数值存储在数值类型的列中,这种逻辑会变得容易得多。

如果HAVING子句要删除***LOT***的记录,您还可以将GROUP BY的结果联接回原始集合,那么这可能比 *Window函数 * 更有效

更新:运行余额-仅显示自零余额以来的最近交易

如果我们需要平衡交易,那么一个简单的解决方案是,我们可以用相同的DebtorID来计算前面所有行的运行余额。https://dbfiddle.uk/DVRoMoNd

更新#2:按〉0过滤是不够的

我发现,即使前面的fiddle返回了正确的答案,但如果事务历史记录先前为负数,然后又回到零,那么它就不起作用了。下面的查询查找运行余额 * 小于或等于 * 零的最后一次时间,并返回该时间之后的所有事务:https://dbfiddle.uk/exwm9OwA

-- if we only want the transactions since the last time the account was in balance
WITH FormattedData as (
  SELECT ID, DebtorID, COALESCE(TRY_PARSE(amount as float)
                       , (-1)*CAST(SUBSTRING(amount,CHARINDEX('-',amount)+1,LEN(amount)) AS float)
                   ) as Amount
  FROM #myData
)
, DataWithRunningBalance as (
 SELECT a.ID, a.DebtorID, a.Amount, (
                                     SELECT SUM(Amount) 
                                     FROM FormattedData lkp
                                     WHERE lkp.DebtorID = a.DebtorID
                                     AND lkp.ID <= a.ID
                                    ) AS SumAmount
 FROM FormattedData a
)
SELECT myData.ID, myData.DebtorID, A.Amount
  , myData.ClientRef
 FROM FormattedData A
 INNER JOIN #myData myData ON myData.ID = A.ID
 WHERE A.ID > (
               SELECT TOP 1 ID 
               FROM DataWithRunningBalance B 
               WHERE B.DebtorID = myData.DebtorID 
                 AND SumAmount <= 0 
               ORDER BY ID DESC
              )
ORDER BY ID

| 识别号|借方ID|金额|客户参考|
| - ------|- ------|- ------|- ------|
| 七|小行星239|六十六点四一分|编号:T081819513107-00009|
| 八个|小行星239|七十一点一|编号:T081819513107-00009|
| 九|小行星239|十二时零八分|编号:T081819513107-00010|
数据的 * 序列 * 对于SumAmount列很重要,在本例中,我无法使用时间戳,因为值的粒度不够,ID数据集 * 的自然选择。在您的生产代码中,可能有更合适的基于时间的候选项。

  • 负 * 值与 * 正 * 值的顺序无关紧要,SumAmount是关于 * 输入时间 * 的简单 * 动态余额 *。

相关问题