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

时间:2010-09-24 17:46:26

标签: sql sql-server sql-server-2005 tsql query-optimization

在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的行,但从结果集中减去max 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,一次返回已归档的max实例,以及结果集过滤。

修改的 为了澄清查询的要求,“InstanceNumber”列可以跳过数字。所以可能有一个InstanceNumber = 6的记录而没有返回5,所以返回的所有记录都不是顺序的。

3 个答案:

答案 0 :(得分:1)

在我的测试中,解释计划在您使用子选择的版本与使用CROSS JOIN的版本之间是相同的:

    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

答案 1 :(得分:0)

试试这个:

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

但我不知道这会比你当前的查询更好还是更差。

答案 2 :(得分:0)

SELECT id,
       instancenumber -
           MAX(CASE IsArchived WHEN true THEN instancenumber ELSE 0 END)
           OVER () as NewInstanceNumber,
       false AS IsArchived
FROM @tbl
WHERE IsArchived = false

警告。我根本没有测试过这个。

对其他回复的警告:
在查询中间插入具有IsArchived = true的新记录(或者将现有记录从IsArchived = false更改为IsArchived = true)时,使用CROSS JOIN或子选择可能会导致极少数问题。

如果先查询SELECT MAX(InstanceNumber)部分查询,则查询的主要部分可能会减去当时不再是MAX(InstanceNumber)的值。

使用聚合窗口函数MAX() OVER(),实际数据只扫描一次,这样可以完全避免这个问题。