从当前查询中减去子查询中的行数
Posted
技术标签:
【中文标题】从当前查询中减去子查询中的行数【英文标题】:Subtracting count of rows in subquery from current query 【发布时间】:2010-09-24 17:46:26 【问题描述】:在 SQL Server 2005 中,给定以下结果集
ID | InstanceNumber | IsArchived
5000 | 1 | True
8347 | 2 | True
9343 | 3 | False
11048 | 4 | False
我想返回的是:
ID | InstanceNumber | IsArchived
9343 | 1 | False
11048 | 2 | False
返回“IsArchived”为 false 的行,但从结果集中减去最大 InstanceNumber 列。
这是一个示例 SQL 语句,它返回我正在寻找的行为:
DECLARE @tbl TABLE
(ID INT NOT NULL, InstanceNumber INT NOT NULL, IsArchived BIT NOT NULL)
INSERT INTO @tbl VALUES (5000, 1, 1)
INSERT INTO @tbl VALUES (8347, 2, 1)
INSERT INTO @tbl VALUES (9343, 3, 0)
INSERT INTO @tbl VALUES (11048, 4, 0)
SELECT ID, InstanceNumber - (SELECT MAX(InstanceNumber) FROM @tbl WHERE IsArchived = 1), IsArchived
FROM @tbl
WHERE IsArchived = 0
这是执行此操作的最有效方法,还是有其他方法可以实现相同的行为?我有额外的 where 子句需要进入完整的语句(如 5-6 个语句),我想避免必须将它们声明为 2X,一次用于返回已存档的最大实例,并用于结果集过滤。
编辑 为了明确查询的要求,“InstanceNumber”列可以跳过数字。所以可能有一个 InstanceNumber = 6 的记录而不返回一个 5,所以不是所有返回的记录都是连续的。
【问题讨论】:
【参考方案1】:在我的测试中,使用子选择的版本和使用交叉连接的版本之间的解释计划是相同的:
SELECT x.id,
x.instancenumber - y.max_num AS instancenumber,
x.isarchived
FROM @tbl x
CROSS JOIN (SELECT MAX(InstanceNumber) AS max_num
FROM @tbl
WHERE IsArchived = 1) y
WHERE x.isarchived = 0
【讨论】:
【参考方案2】:试试这个:
SELECT
ID,
InstanceNumber = Row_Number() OVER (ORDER BY InstanceNumber),
IsArchived
FROM @tbl
WHERE IsArchived = 0
不过,一个问题是,如果存档数据中存在缺口怎么办?
INSERT INTO @tbl VALUES (5000, 1, 1)
INSERT INTO @tbl VALUES (8347, 2, 0)
INSERT INTO @tbl VALUES (9343, 3, 1)
INSERT INTO @tbl VALUES (11048, 4, 0)
在这种情况下你想要什么结果?您当前的查询将 InstanceNumber 提供为 -1 和 1。我上面的查询给出了 InstanceNumber 1 和 2。另一个可能的答案是返回 InstanceNumber 1 和 3(表示 8347 和 11048 之间的 InstanceNumber 步骤 2)。
更新
因此,如果我正确理解了可能存在的差距,您需要更改查询以处理以下情况:
INSERT INTO @tbl VALUES (5000, 1, 1)
INSERT INTO @tbl VALUES (8347, 2, 1)
INSERT INTO @tbl VALUES (9343, 4, 0)
INSERT INTO @tbl VALUES (11048, 5, 0)
SELECT
ID,
NewInstanceNumber = InstanceNumber + 1
- (SELECT Min(InstanceNumber) FROM @tbl WHERE IsArchived = 0),
IsArchived
FROM @tbl
WHERE IsArchived = 0
所以编号总是从 1 开始。你也可以试试这个:
SELECT
ID,
NewInstanceNumber = InstanceNumber + 1 - Min(InstanceNumber) OVER (),
IsArchived
FROM @tbl
WHERE IsArchived = 0
但我不知道这是否会比您当前的查询更好或更差。
【讨论】:
幸运的是,存档顺序不应该有间隙。较低的数字将始终是潜在的存档记录。我忘了提到记录的一件事是返回的“实例编号”可能有中断。【参考方案3】:SELECT id,
instancenumber -
MAX(CASE IsArchived WHEN true THEN instancenumber ELSE 0 END)
OVER () as NewInstanceNumber,
false AS IsArchived
FROM @tbl
WHERE IsArchived = false
警告。我根本没有测试过。
关于其他回复的一点警告:
在极少数情况下,使用 CROSS JOIN
或子选择可能会导致问题,即在查询中插入 IsArchived=true 的新记录(或现有记录从 IsArchived=false 更改为 IsArchived=true)。
如果首先处理查询的SELECT MAX(InstanceNumber)
部分,则查询的主要选择部分可能会减去一个当时不再是MAX(InstanceNumber)
的值。
使用聚合窗口函数MAX() OVER()
,实际数据只扫描一次,完全避免了这个问题。
【讨论】:
以上是关于从当前查询中减去子查询中的行数的主要内容,如果未能解决你的问题,请参考以下文章