从当前查询中减去子查询中的行数

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(),实际数据只扫描一次,完全避免了这个问题。

【讨论】:

以上是关于从当前查询中减去子查询中的行数的主要内容,如果未能解决你的问题,请参考以下文章

计算带有子查询的行数的比率

在 TSQL 中,如何添加一个计数列来计算查询中的行数?

如何提高 SQL Server 查询的性能以选择具有值的行不在子查询中的一次计数

如何使用 ucanaccess 获取查询中的行数

[MySQL]子语句的查询技巧

计算 30 天 bin 中的行数