Вычитание количества строк в подзапросе из текущего запроса - PullRequest
1 голос
/ 24 сентября 2010

В 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», который является ложным, являютсявозвращается, но вычитая столбец 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, один раз для возврата максимального экземпляра, который заархивирован, и для фильтрации набора результатов.

РЕДАКТИРОВАТЬ Чтобы уточнить требование запроса, столбец «InstanceNumber» может пропускать числа.поэтому может быть запись для InstanceNumber = 6 без возврата одной для 5, поэтому не все возвращаемые записи будут последовательными.

Ответы [ 3 ]

1 голос
/ 24 сентября 2010

В моем тесте план объяснения был идентичен для вашей версии с использованием подвыбора и моей версии с использованием 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
0 голосов
/ 25 сентября 2010
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() фактические данные сканируются только один раз, что полностью предотвращает эту проблему.

0 голосов
/ 25 сентября 2010

Попробуйте это:

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 (представляющий шаг InstanceNumber, равный 2 между 8347 и 11048).

Обновление

Так что, если я правильно понимаю, о возможности пробелов, вам нужно изменить свой запрос для обработки следующего случая:

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

Но я не знаю, будет ли это лучше или хуже, чем ваш текущий запрос.

...