【问题标题】:SQL Server 2005 - SUM'ing one field, but only for the first occurence of a second fieldSQL Server 2005 - SUM'ing 一个字段,但仅适用于第二个字段的第一次出现
【发布时间】:2012-12-13 21:15:27
【问题描述】:

平台:SQL Server 2005 Express

免责声明:我是 SQL 的新手,所以如果您乐于帮助解决一个可能非常简单的问题,那么如果您说话缓慢并使用小词,我不会被冒犯 :-)

我有一个表,我想对多行的内容求和。但是,我只想对另一列中第一次出现的文本求和一列。

Table schema for table 'tblMain'
fldOne {varchar(100)}    Example contents: “Dandelion“
fldTwo {varchar(8)}      Example contents: “01:00:00” (represents hh:mm:ss)
fldThree {numeric(10,0)} Example contents: “65”

表格内容:

行号 fldOne fldTwo fldThree ------------------------------------------------ 1 蒲公英 01:00:00 99 2 雏菊 02:15:00 88 3 蒲公英 00:45:00 77 4 蒲公英 00:30:00 10 5 蒲公英 00:15:00 200 6 玫瑰 01:30:00 55 7 雏菊 01:00:00 22 等等 ad nausium

如果我使用:

Select * from tblMain where fldTwo < ’05:00:00’ order by fldOne, fldTwo desc

然后所有行都正确返回,由 fldOne 和 fldTwo 降序排序(尽管在我展示的示例数据中,所有数据都已经按正确的顺序排列!)

我想做的是获取每个 fldThree 的 SUM,但只能从每个 fldOne 的第一次出现。

所以,求和我遇到的第一个蒲公英、黛西和玫瑰。例如。 99+88+55

目前,我正在以编程方式执行此操作;从上面的 Select 语句返回一个 RecordSet,并 MoveNext 遍历每个返回的行,如果我从未见过 中的文本,则仅将 fldThree 添加到我的“总数”中fldOne 之前。它可以工作,但大多数 Select 查询返回超过 100k 行,因此速度很慢(慢是一个相对术语——我的设置大约需要 50 秒)。

实际的 select 语句(从 1.5m 总行中选择大约 100k 行)在一秒钟内完成,这很好。当前的程序循环非常小且紧凑,只是通过 RecordSet 的循环数需要时间。我在打开记录集时使用了 adOpenForwardOnly 和 adLockReadOnly。

这是一个基本上随着更多数据的添加而连续运行的例程,而且 fldTwo 'times' 会有所不同,所以我不能更具体地使用 Select 语句。

到目前为止,我用 SQL 本地完成的所有事情似乎都运行得很快,我希望我可以将逻辑(和工作)从我的程序中剥离出来,让 SQL 来承担压力。

提前致谢

【问题讨论】:

  • 嗨,Kaf - 是的,MS SQL 2005 Express。
  • 人们已经回答了,所以我不必回答。我知道您是新手,因此请尝试使用必填标签标记您的问题,这在回答时会有所帮助。作为回报,您会很快得到相关的答案。
  • 嗨@Kaf。我很感激人们在有人刚加入论坛时表现出的耐心,如果我错过了一些观点,我会道歉。我使用了标签“SQL”、“SUM”和“CONDITIONAL”并尝试了“2005”,但作为新用户,我无法创建新标签。我的错误是缺少“sql-server”标签(现已修复)。我确实在帖子的开头添加了一个标题,旨在澄清事情,但感谢它不如适当的标签好。现在所有标签都已排序,这将有助于其他从哈姆雷特和戈登那里寻找王牌答案的人。问候,伊恩。
  • 嘿,没关系。这只是一些有用的提示。这里有很多好人愿意提供知识和建议。

标签: sql sql-server sum conditional


【解决方案1】:

解决此问题的最佳方法是使用窗口函数。这些使您可以枚举组中的行。但是,您需要一些方法来识别第一行。 SQL 表本质上是无序的,因此您需要一个列来指定排序。这里有一些想法。

如果你有一个 id 列,它被定义为identity,所以它是自动递增的:

select sum(fldThree)
from (select m.*,
            row_number() over (partition by fldOne order by id) as seqnum
      from tblMain m
     ) m
where seqnum = 1

要获得任意行,您可以使用:

select sum(fldThree)
from (select m.*,
            row_number() over (partition by fldOne order by (select NULL as noorder)) as seqnum
      from tblMain m
     ) m
where seqnum = 1

或者,如果 FldTwo 具有相反顺序的值:

select sum(fldThree)
from (select m.*,
            row_number() over (partition by fldOne order by FldTwo desc) as seqnum
      from tblMain m
     ) m
where seqnum = 1

【讨论】:

  • 嗨,戈登,感谢您提供的答案和显示不同输出的方式。我可以添加一个自动递增的 ID 列,然后我会尝试您建议的语法。既然您和哈姆雷特已经很好地给了我答案,我现在可以花时间研究如何它们是如何工作的(对下一次非常有用)。我一直在阅读表级别的分区作为将我的数据分成更小的块的一种方式(但必须等待我的 Ent. License!)。我不知道你可以像那样动态地使用它。再次感谢。
  • @EyePeaSea - 表级别的分区不同于像这样的语句中的分区。在表级别上,它实际上是一种处理文件大小(和相关访问时间)的方法。当在这样的语句中使用时,它在概念上更接近GROUP BY('每次在 fldOne 中看到一个新值,重新开始......')。 PARTITION BY 将是(我假设)标准中指定的关于它如何返回数据的内容。表分区是一个实现/优化细节。
  • @Clockwork-Muse - 谢谢。这是我需要阅读的内容:-)
【解决方案2】:

也许是这个?

SELECT SUM(fldThree) as ExpectedSum
FROM
    (SELECT *, ROW_NUMBER() OVER (PARTITION BY fldOne ORDER BY fldTwo DSEC) Rn
        FROM tblMain) as A
WHERE Rn = 1

【讨论】:

  • 哈姆雷特 - 太好了,谢谢。它完全符合我的要求,并且使用我提供的示例表,它运行良好。我现在需要在真实的桌子上试一试,这显然有点复杂(所以出现了桌面计算器来使用真实的数据)。
猜你喜欢
  • 2017-01-07
  • 2014-03-17
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 2020-08-08
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多