Почему этот подзапрос не работает? - PullRequest
5 голосов
/ 23 марта 2012

Прежде всего, я не ищу переписать.Это было представлено мне, и я не могу понять, является ли это ошибкой вообще или какой-то синтаксической глупостью, которая возникает из-за особенностей сценария.Хорошо с тем, что сказано с настройкой:

  • Microsoft SQL Server Standard Edition (64-разрядная версия)

  • Версия 10.50.2500.0

В таблице, расположенной в общей базе данных, определяемой как:

CREATE TABLE [dbo].[Regions](
    [RegionID] [int] NOT NULL,
    [RegionGroupID] [int] NOT NULL,
    [IsDefault] [bit] NOT NULL,
 CONSTRAINT [PK_Regions] PRIMARY KEY CLUSTERED
(
    [RegionID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

вставить некоторые значения:

INSERT INTO [dbo].[Regions]
([RegionID],[RegionGroupID],[IsDefault])
VALUES
(0,1,0),
(1,1,0),
(2,1,0),
(3,2,0),
(4,2,0),
(5,2,0),
(6,3,0),
(7,3,0),
(8,3,0)

Теперь запустите запрос (чтобы выбрать один из каждой группы, не забудьте переписать предложения!):

SELECT RXXID FROM (
   SELECT
       RXX.RegionID as RXXID,
       ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
   FROM Regions as RXX
) AS tmp
WHERE tmp.RXXNUM = 1

Вы должны получить:

RXXID
-----------
0
3
6

Теперь вставьте это внутри оператора обновления (с предустановкой 0 и выбором все после):

UPDATE Regions SET IsDefault = 0

UPDATE Regions
SET IsDefault = 1
WHERE RegionID IN (
    SELECT RXXID FROM (
       SELECT
           RXX.RegionID as RXXID,
           ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
       FROM Regions as RXX
    ) AS tmp
    WHERE tmp.RXXNUM = 1
)


SELECT * FROM Regions
ORDER BY RegionGroupID

и получите эторезультат:

RegionID    RegionGroupID IsDefault
----------- ------------- ---------
0           1             1
1           1             1
2           1             1
3           2             1
4           2             1
5           2             1
6           3             1
7           3             1
8           3             1

zomg wtf lamaz?

Хотя я не претендую на звание гуру SQL, это не кажется ни правильным, ни правильным.И чтобы сделать все более сумасшедшим, если вы уроните первичный ключ, он, кажется, будет работать:

Удалите первичный ключ:

IF  EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Regions]') AND name = N'PK_Regions')
ALTER TABLE [dbo].[Regions] DROP CONSTRAINT [PK_Regions]

И повторитеОбновление набора операторов, результат:

RegionID    RegionGroupID IsDefault
----------- ------------- ---------
0           1             1
1           1             0
2           1             0
3           2             1
4           2             0
5           2             0
6           3             1
7           3             0
8           3             0

Разве это не ab?

Кто-нибудь знает, что здесь происходит?Я предполагаю, что это какое-то кэширование подзапроса, и это ошибка?Это не похоже на то, что SQL должен делать?

Ответы [ 2 ]

9 голосов
/ 23 марта 2012

Просто обновите как CTE напрямую:

WITH tmp AS (
SELECT 
       RegionID as RXXID,
       RegionGroupID,
       IsDefault,
       ROW_NUMBER() OVER (PARTITION BY RegionGroupID ORDER BY RegionID) AS RXXNUM
   FROM Regions

) 
UPDATE tmp SET IsDefault = 1 WHERE RXXNUM = 1
select * from Regions

Добавлено больше столбцов для иллюстрации.Вы можете увидеть это на http://sqlfiddle.com/#!3/03913/9

Не уверен на 100%, что происходит в вашем примере, но поскольку вы разбиваете и заказываете по одному столбцу, вы не уверены, что получите тот же самый заказ обратно,так как они все связаны.Разве вы не должны заказывать по RegionID или какому-либо другому столбцу, как я это сделал на sqlfiddle?


Вернуться к вашему вопросу:

Если вы измените свой UPDATE (с кластеризованным индексом) наВЫБЕРИТЕ, вы получите все 9 строк назад.Если вы уроните PK и выполните SELECT, вы получите только 3 строки.Вернуться к вашему заявлению об обновлении.Проверка планов выполнения показывает, что они немного различаются:

First (PK) Execution plan Second (No PK) Execution plan

Здесь вы можете видеть, что в первом (с PK) запросе вы будете сканировать кластеризованныйиндекс для внешней ссылки, обратите внимание, что он не имеет псевдоним RXX.Затем для каждого ряда в верхней части найдите RXX.И да, из-за вашего порядка номеров строк каждый RegionID может быть row_number () 1 для каждого RegionGroupID.Я полагаю, что SQL Server будет знать это на основе вашего PK и может сказать, что для каждого RegionID этот RegionID может быть строкой номер 1. Следовательно, оператор довольно действителен.

Во втором запросе нетиндекс, и вы получаете сканирование таблицы по регионам, затем он создает таблицу зондов, используя RXX, и присоединяется по-разному (один проход, ROW_NUMBER () может быть только 1 для одной строки на regiongroupid сейчас).Таким образом, в этом сканировании каждый RegionID имеет только один ROW_NUMBER (), хотя вы не можете быть на 100% уверены, что он будет одинаковым каждый раз.

Это означает: использование подзапроса, который не имеет детерминированногоДля каждого выполнения следует избегать использования типа соединения с несколькими проходами (NESTED LOOP), но с соединением с одним проходом (MERGE ИЛИ HASH).

Чтобы исправить это без изменения структуры запроса, добавьте OPTION (HASH JOIN) или OPTION (MERGE JOIN) для первого ОБНОВЛЕНИЯ:

Итак, вам понадобится следующий оператор обновления (если у вас есть PK):

UPDATE Regions SET IsDefault = 0

UPDATE Regions 
SET IsDefault = 1 
WHERE RegionID IN (
    SELECT RXXID FROM (
       SELECT 
           RXX.RegionID as RXXID,
           ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
       FROM Regions as RXX
    ) AS tmp
    WHERE tmp.RXXNUM = 1 
)
OPTION (HASH JOIN)

SELECT * FROM Regions
ORDER BY RegionGroupID

Вотпланы выполнения с использованием этих двух типов соединения (обратите внимание на фактическое количество строк: 3 в свойствах):

Using MERGE JOIN Using HASH JOIN

3 голосов
/ 24 марта 2012

Ваш запрос на простом языке выглядит примерно так:
Для каждой строки в Regions проверьте, существует ли RegionID в каком-либо подзапросе.Это означает, что подзапрос выполняется для каждой строки в Regions.(Я знаю, что это не так, но это семантика запроса).

Поскольку вы используете RegionGroupID как порядок и раздел, вы действительно не представляете, что будет возвращено RegionID, так что это можеточень хорошо быть новым идентификатором при каждой проверке подзапроса.

Обновление:

Выполнение обновления с объединением противпроизводная таблица вместо использования в изменяет семантику запроса, а также изменяет результат.

Это работает, как и ожидалось:

UPDATE R 
SET IsDefault = 1
FROM Regions as R
  inner join 
      (
        SELECT RXXID FROM (
           SELECT 
               RXX.RegionID as RXXID,
               ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
           FROM Regions as RXX
        ) AS tmp
        WHERE tmp.RXXNUM = 1 
      ) as C
    on R.RegionID = C.RXXID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...