【问题标题】:Complex select query for SQL ServerSQL Server 的复杂选择查询
【发布时间】:2015-11-04 12:26:38
【问题描述】:

我在 SQL Server 中有一个临时表,如下所示

╔═══════╦═════════╦══════╦═════════╗
║Serial ║ Account ║ Due  ║ Balance ║
║1      ║ A1      ║ 1000 ║ 3100    ║
║2      ║ A1      ║ 1000 ║ 3100    ║
║3      ║ A1      ║ 1000 ║ 3100    ║
║4      ║ A1      ║ 1000 ║ 3100    ║
║1      ║ A2      ║ 100  ║ 3100    ║
║2      ║ A2      ║ 100  ║ 3100    ║    
║1      ║ B1      ║ 1000 ║ 1100    ║
║2      ║ B1      ║ 1000 ║ 1100    ║
║1      ║ B2      ║ 100  ║ 1100    ║
║2      ║ B2      ║ 100  ║ 1100    ║
╚═══════╩═════════╩══════╩═════════╝

我想确定应收集的行。 A1 和 A2 到期将从 3100 收取,B1 和 B2 到期将从 1100 收取。

首先我使用累积到期如下

╔═══════╔═════════╦══════╦════════════╦═════════╦
║Serial ║ Account ║ Due  ║ Cumulative ║ Balance ║
║1      ║ A1      ║ 1000 ║  1000      ║    3100 ║
║2      ║ A1      ║ 1000 ║  2000      ║    3100 ║
║3      ║ A1      ║ 1000 ║  3000      ║    3100 ║
║4      ║ A1      ║ 1000 ║  4000      ║    3100 ║
║1      ║ A2      ║ 100  ║   100      ║    3100 ║
║2      ║ A2      ║ 100  ║   200      ║    3100 ║
║1      ║ B1      ║ 1000 ║  1000      ║    1100 ║
║2      ║ B1      ║ 1000 ║  2000      ║    1100 ║
║1      ║ B2      ║ 100  ║   100      ║    1100 ║
║2      ║ B2      ║ 100  ║   200      ║    1100 ║
╚═══════╚═════════╩══════╩════════════╩═════════╝

现在我想选择以下行作为输出

╔═══════╔═════════╦══════╦════════════╦═════════╦
║Serial ║ Account ║ Due  ║ Cumulative ║ Balance ║  
║1      ║ A1      ║ 1000 ║  1000      ║    3100 ║
║2      ║ A1      ║ 1000 ║  2000      ║    3100 ║ 
║3      ║ A1      ║ 1000 ║  3000      ║    3100 ║  
║1      ║ A2      ║ 100  ║   100      ║    3100 ║
║1      ║ B1      ║ 1000 ║  1000      ║    1100 ║
║1      ║ B2      ║ 100  ║   100      ║    1100 ║
╚═══════╚═════════╩══════╩════════════╩═════════╩

这是我卡住的地方。如何在不使用游标或循环的情况下选择这些行。我只想用 select 语句和窗口函数来做这件事。 谢谢。

可能的解决方案:如果表格可以更新如下,那么问题就解决了。

╔═══════╔═════════╦══════╦═══════════════════╦
║Serial ║ Account ║ Due  ║ Balance Remaining ║
║1      ║ A1      ║ 1000 ║  3100             ║
║2      ║ A1      ║ 1000 ║  2100             ║
║3      ║ A1      ║ 1000 ║  1100             ║
║4      ║ A1      ║ 1000 ║   100             ║
║1      ║ A2      ║ 100  ║   100             ║
║2      ║ A2      ║ 100  ║     0             ║
║1      ║ B1      ║ 1000 ║  1100             ║
║2      ║ B1      ║ 1000 ║   100             ║
║1      ║ B2      ║ 100  ║   100             ║
║2      ║ B2      ║ 100  ║     0             ║
╚═══════╚═════════╩══════╩═══════════════════╩

Balance Remaining 等于/大于 Due 的情况,我们会更新差异,否则它将保持原样。问题在于通过 A 和 B 之间的分区来更新行。

更新 我正在提供与新数据集的链接,以更清楚地表达我的要求。 new dataset

【问题讨论】:

  • SQL Server 的哪个版本?
  • SQL SERVER 2012 或更高版本。
  • 问题是即使SQL Server 2012提供了强大的窗口函数,也没有条件累加的好方法。换句话说,根据之前的总额是否大于余额,有条件地为总和做出贡献。基于光标的解决方案可能是最简单和最有效的 - 为什么不想要基于光标的解决方案?
  • 对于大数据集游标/while循环都变慢了。
  • Yes cursors/while 循环在 99% 的情况下是最糟糕的解决方案。但是,在您需要计算运行总计或余额的情况下,基于集合并不总是答案。事实上,由于条件(不允许余额低于 0),我什至不确定是否可以使用基于集合/窗口的函数(或者如果是,它实际上可能慢得多 不是光标)。这将是 1% 的情况之一,其中游标可能是最佳解决方案(请参阅 sqlperformance.com/2012/07/t-sql-queries/running-totalsstackoverflow.com/a/11313533/116614)。

标签: sql sql-server


【解决方案1】:

要获得此输出,您可以使用如下简单查询:

╔═══════╔═════════╦══════╦════════════╦═════════╦
║Serial ║ Account ║ Due  ║ Cumulative ║ Balance ║  
║1      ║ A1      ║ 1000 ║  1000      ║    3100 ║
║2      ║ A1      ║ 1000 ║  2000      ║    3100 ║ 
║3      ║ A1      ║ 1000 ║  3000      ║    3100 ║  
║1      ║ A2      ║ 100  ║   100      ║    3100 ║
║1      ║ B1      ║ 1000 ║  1000      ║    1100 ║
║1      ║ B2      ║ 100  ║   100      ║    1100 ║
╚═══════╚═════════╩══════╩════════════╩═════════╩

select Serial,Account,Due,Cumulative,Balance from
(Select *, row_number() over(Partition by Account, order by serial number desc) as r from Temp) t
where t.r>1

【讨论】:

  • 你已经得到了准确的输出......但你没有应用任何实际的逻辑(选择行直到累积>余额)。这很容易被不同的示例数据集打破。
  • 同意.. 对于以下数据集,此查询将不起作用。 link
  • @TanjimRahman 正如我在回答中提到的For getting this output
  • 是的@DhruvJoshi,看来我的数据集没有正确表达我的要求。我正在提供一组新数据。
【解决方案2】:

很简单

select * from account 
where (Balance-(Select sum(ac.Due) from account ac where 
ac.SerialNo<=account.SerialNo and  ac.Account =account.Account )>0)

更新

A1 和 A2 之间没有关系表明余额 3100 将由 A1 和 A2 共享,而不是与 B1 共享。

因此,您必须指定 a1 和 a2 在同一组中的位置
有适合您的建议选项
在您的表中添加 group no 列,并为 A1 和 A2 提供相同的 no,为 B1 和 B2 提供相同的 no。然后添加优先级列,指定 A1 应先扣除到期,如果余额剩余 a2 将获得机会

那么查询将是

    SELECT          *
    FROM            account
    WHERE
        ( Balance - ( SELECT
                        SUM(ac.Due)
                      FROM
                        account ac
                      WHERE
                        ( ac.GroupNo = account.GroupNo
                          AND ( ( ac.Account = account.Account
                                  AND ( ac.SerialNo <= account.SerialNo )
                                )
                                OR ac.Prioirty < account.Prioirty
                              )
                        )
                    ) > 0 )

【讨论】:

  • 您的查询返回如下link,这不是必需的输出。帐户 A2 和 B2 的序列号 2 是假行。
  • 你是对的@VISHMAY ..我只是在想它。也许我无法在数据集中给出我的实际要求。我正在提供一组新数据。link
  • @Tanjim Rahman 此查询也可以在您给定的链接中使用,但它不会给您带来性能,因为它需要在子查询中进行两个级别,而是在代码中获取数据,然后计算剩余并使用否则正确
  • U 的意思是使用光标或当我想的时候。但是对于大型数据集,这不是更昂贵吗???
  • @Tanjim Rahman 我是说你会使用一些编程语言,如 c#、java,因为 c# 将这些数据放入数据表中,然后通过 for 循环对其进行操作,它只包含一个for 循环,因此您可以消除子查询的麻烦它肯定会具有成本效益和快速提供结果,并且将在半小时内创建
【解决方案3】:

最后,通过更新查询解决了这个问题。

UPDATE A
SET
A.Balance = @Balance
, @PreBalance = @Balance
, @Balance = ( CASE WHEN (@Balance IS NULL OR @AccountType <> A.AccountType)
                    THEN 
                        CASE WHEN A.Balance - A.Due >= 0
                            THEN A.Balance
                            ELSE A.Balance + A.Due
                        END
                    ELSE 
                        CASE WHEN @Balance - A.Due >= 0 AND (@Flag = 1 OR @AccountNO <> A.AccountNO)
                              THEN @Balance
                              ELSE @Balance + A.Due
                         END
                END) - A.Due
, A.FLAG = @Flag
, @AccountNO = CASE WHEN A.Flag = 0 THEN A.AccountNO ELSE 'NoDueFoundForAcc' END
, @Flag = CASE WHEN @AccountType = A.AccountType 
                THEN 
                    CASE WHEN @PreBalance = @Balance 
                            THEN 0 
                            ELSE 1 
                        END
                ELSE 
                    CASE WHEN A.Balance - A.Due >= 0
                            THEN 1
                            ELSE 0 
                        END
                END
, @AccountType = A.AccountType
FROM #tempTable A

SELECT * FROM #tempTable A WHERE A.Flag = 1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    相关资源
    最近更新 更多