使用 T-SQL 聚合相邻的记录
我有(针对示例进行了简化)一个包含以下数据的表。
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-04 01 38
4 2008-10-04 2008-10-05 01 23
5 2008-10-05 2008-10-06 03 14
6 2008-10-06 2008-10-07 02 3
7 2008-10-07 2008-10-08 02 8
8 2008-10-08 2008-11-08 03 19
日期代表一个时间段,ID 是系统在该时间段内所处的状态,金额是与该状态相关的值。
我想要做的是将具有相同 ID 号的相邻行的金额进行聚合,但保持相同的整体顺序,以便可以组合连续的运行。 因此,我希望最终得到如下数据:
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-05 01 61
4 2008-10-05 2008-10-06 03 14
5 2008-10-06 2008-10-08 02 11
6 2008-10-08 2008-11-08 03 19
我正在寻找可以放入 SP 中的 T-SQL 解决方案,但是我不知道如何通过简单查询来做到这一点。 我怀疑它可能需要某种迭代,但我不想走这条路。
我想要进行此聚合的原因是,该过程的下一步是执行 SUM() 和 Count() 按序列中出现的唯一 ID 进行分组,以便我的最终数据看起来像这样:
ID Counts Total
-- ------ -----
01 2 71
02 2 31
03 2 33
但是,如果我做了一个简单的
SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID
在原始表上我得到类似的东西
ID Counts Total
-- ------ -----
01 3 71
02 3 31
03 2 33
这不是我想要的。
I have (simplified for the example) a table with the following data
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-04 01 38
4 2008-10-04 2008-10-05 01 23
5 2008-10-05 2008-10-06 03 14
6 2008-10-06 2008-10-07 02 3
7 2008-10-07 2008-10-08 02 8
8 2008-10-08 2008-11-08 03 19
The dates represent a period in time, the ID is the state a system was in during that period and the amount is a value related to that state.
What I want to do is to aggregate the Amounts for adjacent rows with the same ID number, but keep the same overall sequence so that contiguous runs can be combined. Thus I want to end up with data like:
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-05 01 61
4 2008-10-05 2008-10-06 03 14
5 2008-10-06 2008-10-08 02 11
6 2008-10-08 2008-11-08 03 19
I am after a T-SQL solution that can be put into a SP, however I can't see how to do that with simple queries. I suspect that it may require iteration of some sort but I don't want to go down that path.
The reason I want to do this aggregation is that the next step in the process is to do a SUM() and Count() grouped by the unique ID's that occur within the sequence, so that my final data will look something like:
ID Counts Total
-- ------ -----
01 2 71
02 2 31
03 2 33
However if I do a simple
SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID
On the original table I get something like
ID Counts Total
-- ------ -----
01 3 71
02 3 31
03 2 33
Which is not what I want.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您阅读 RT Snodgrass 的《用 SQL 开发面向时间的数据库应用程序》一书(其 pdf 格式可从他的网站上的出版物中找到),直到第 165-166 页上的图 6.25,您会发现不平凡的 SQL,可在当前示例中使用它来对各个行进行分组相同的ID值和连续的时间间隔。
下面的查询开发接近正确,但最后发现了一个问题,其根源在于第一个 SELECT 语句。 没有查出为什么给出了错误的答案。 [如果有人可以在他们的 DBMS 上测试 SQL 并告诉我第一个查询是否在那里正常工作,这将是一个很大的帮助!]
我还 类似于:
该查询的输出是:
已编辑:倒数第二行有问题 - 它不应该在那里。 我还不清楚它来自哪里。
现在,我们需要将该复杂表达式视为另一个 SELECT 语句的 FROM 子句中的查询表达式,它将对与上面显示的最大范围重叠的条目上给定 ID 的金额值进行求和。
这给出:
已编辑:这几乎是执行原始问题所要求的 COUNT 和 SUM 聚合的正确数据集,因此最终答案是:
评论:
哦! 糟糕...3 的条目的“数量”是应有的两倍。 之前“编辑过”的部分表明了问题开始出现的地方。 看起来好像第一个查询有点错误(也许它是针对不同的问题),或者我正在使用的优化器行为不当。 尽管如此,应该有一个与此密切相关的答案,它将给出正确的值。
郑重声明:已在 Solaris 10 上的 IBM Informix Dynamic Server 11.50 上进行测试。但是,在任何其他适度符合标准的 SQL DBMS 上应该可以正常工作。
If you read the book "Developing Time-Oriented Database Applications in SQL" by R T Snodgrass (the pdf of which is available from his web site under publications), and get as far as Figure 6.25 on p165-166, you will find the non-trivial SQL which can be used in the current example to group the various rows with the same ID value and continuous time intervals.
The query development below is close to correct, but there is a problem spotted right at the end, that has its source in the first SELECT statement. I've not yet tracked down why the incorrect answer is being given. [If someone can test the SQL on their DBMS and tell me whether the first query works correctly there, it would be a great help!]
It looks something like:
The output from that query is:
Edited: There's a problem with the penultimate row - it should not be there. And I'm not clear (yet) where it is coming from.
Now we need to treat that complex expression as a query expression in the FROM clause of another SELECT statement, which will sum the amount values for a given ID over the entries that overlap with the maximal ranges shown above.
This gives:
Edited: This is almost the correct data set on which to do the COUNT and SUM aggregation requested by the original question, so the final answer is:
Review:
Oh! Drat...the entry for 3 has twice the 'amount' that it should have. Previous 'edited' parts indicate where things started to go wrong. It looks as though either the first query is subtly wrong (maybe it is intended for a different question), or the optimizer I'm working with is misbehaving. Nevertheless, there should be an answer closely related to this that will give the correct values.
For the record: tested on IBM Informix Dynamic Server 11.50 on Solaris 10. However, should work fine on any other moderately standard-conformant SQL DBMS.
可能需要创建一个游标并循环遍历结果,跟踪您正在使用的 id 并一路累积数据。 当 id 更改时,您可以将累积的数据插入到临时表中,并在过程结束时返回该表(从中选择全部)。 基于表的函数可能会更好,因为您可以在执行过程中将其插入到返回表中。
Probably need to create a cursor and loop through the results, keeping track of which id you are working with and accumulating the data along the way. When the id changes you can insert the accumulated data into a temporary table and return the table at the end of the procedure (select all from it). A table-based function might be better as you can then just insert into the return table as you go along.
我认为这是您必须采取的路线,使用光标填充表变量。 如果您有大量记录,您可以使用永久表来存储结果,然后当您需要检索数据时,您可以只处理新数据。
我将向源表添加一个默认值为 0 的位字段,以跟踪已处理的记录。 假设没有人在表上使用 select *,添加具有默认值的列不会影响应用程序的其余部分。
如果您需要帮助编码解决方案,请在本文中添加评论。
I think that's the route you'll have to take, use a cursor to populate a table variable. If you have a large number of records you could use a permanent table to store the results then when you need to retrieve the data you could process only the new data.
I would add a bit field with a default of 0 to the source table to keep track of which records have been processed. Assuming no one is using select * on the table, adding a column with a default value won't affect the rest of your application.
Add a comment to this post if you want help coding the solution.
好吧,我决定使用连接和游标的混合来走迭代路线。 通过将数据表与自身连接起来,我可以创建一个仅包含那些连续记录的链接列表。
然后我可以通过用游标迭代列表来展开列表,并对数据表进行更新以进行调整(并从数据表中删除现在无关的记录)
这一切都有效,并且对于我正在使用的典型数据具有可接受的性能。
我确实发现上面的代码有一个小问题。 最初我是通过游标在每个循环上更新数据表。 但这没有用。 似乎您只能对一条记录进行一次更新,而多次更新(为了不断添加数据)将恢复为读取记录的原始内容。
Well I decided to go down the iteration route using a mixture of joins and cursors. By JOINing the data table against itself I can create a link list of only those records that are consecutive.
Then I can unwind the list by iterating over it with a cursor, and doing updates back to the data table to adjust (And delete the now extraneous records from the Data table)
This all works and has acceptable performance for typical data that I am using.
I did find one small issue with the above code. Originally I was updating the Data table on each loop through the cursor. But this didn't work. It seems that you can only do one update on a record, and that multiple updates (in order to keep adding data) revert back to the reading the original contents of the record.