Нет ошибок при выполнении запроса с неправильным именем столбца - PullRequest
1 голос
/ 07 мая 2020

Я столкнулся со странным поведением, по крайней мере, на SQL сервере (мне все еще нужно проверить другие SQL движки) при попытке удалить некоторые записи.

Я тестировал следующее на SQL Экземпляр серверов:

  • Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) 24 сентября 2019 13:48:23 Авторские права (C) 2019 Microsoft Corporation Developer Edition (64- bit) на Windows 10 Enterprise 2016 LTSB 10.0 (сборка 14393:)
  • Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) 28 октября 2016 г. 18:17:30 Авторские права (c) Microsoft Corporation Standard Edition (64-разрядная версия) на Windows Server 2012 R2 Standard 6.3 (сборка 9600:) (гипервизор)

Вот фрагмент кода SQL. Это упрощенная версия того, что я пытался сделать, это, безусловно, можно обсудить, почему делать запрос таким образом, но моя точка зрения другая - почему это происходит.

    drop table if exists #A
    drop table if exists #B

    create table #B (id char, foo char) -- I use different types for id columns just for the sake of readability
    create table #A (id int, b_id char) -- b_id is a link to #A, _not_ specified as FK

    insert into #B values('x', 'l')
    insert into #B values('y', 'm')

    insert into #A values(0, 'x')
    insert into #A values(1, 'z')
    insert into #A values(2, 'z')
    insert into #A values(3, 'y')
    insert into #A values(4, 'y')
    insert into #A values(5, 'y')

    -- there are 2 parent records in #B and 3 child records for each of them in #A
    select * from #A -- just to check, all good the data is there, all as expected, no problem

    -- now the fun part

    --(X) the following query, rightfully gives an error, as column b_id does not exist in #B
    -- select b_id from #B where foo='l'

    --(Y) the following query gives empty result, whereas I would expect an error:
    select * from #A where b_id in (select b_id from #B where foo='l')
    -- I can imagine that this has something to do with the fact that b_id exists in table #A in outer query

    --(Z) the following query deletes(!) all data in table #A:
    delete from #A where b_id in (select b_id from #B where foo='l')
    select * from #A
    -- once again, I can imagine that there is no error message "Invalid column name 'b_id'." because b_id exists in table #A in outer query

Итак, вот мои вопросы:

  1. Почему в запросах (Y) и (Z) нет сообщения об ошибке о недопустимом столбце? Мне было бы интересно узнать подробности
  2. В зависимости от ответа (1) было бы интересно узнать, почему запрос (Y) дает пустой результат. Понятно, что если внутренний выбор пуст, то внешний должен быть пуст, но дьявол скрывает подробности
  3. Почему query (Z) удаляет все записи из таблицы #A? Я ожидал, что затронутые записи (возвращенные в случае (Y) и удаленные в случае (Z)) должны быть одинаковыми

Ответы [ 3 ]

1 голос
/ 07 мая 2020

Вопрос 1 (Y):

План выполнения (текущий и фактический) показывает, что ваше воображение было правильным: показаны выходные данные каждого узла, включая префикс таблицы - путаница невозможна: узел 3 возвращает следующие два столбца: [#A].[id], [#A].[b_id], а узлу 4 нечего возвращать, кроме NULL

/*

         id b_id
----------- ----
          0 x
          1 z
          2 z
          3 y
          4 y
          5 y

(6 rows affected)

Table '#B__________________________________________________________________________________________________________________000000000335'. Scan count 1, logical reads 6, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table '#A__________________________________________________________________________________________________________________000000000336'. Scan count 1, logical reads 1, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

Rows  Executes StmtText                                                                             NodeId Parent PhysicalOp   LogicalOp      Argument                                                       DefinedValues            EstimateRows    EstimateIO   EstimateCPU  AvgRowSize TotalSubtreeCost OutputList              Warnings Type      EstimateExecutions
----  -------- ------------------------------------------------------------------------------------ ------ ------ ------------ -------------- -------------------------------------------------------------- ----------------------- ------------- ------------- ------------- ----------- ---------------- ----------------------- -------- --------- ------------------
   6         1 select * from #A where b_id in (select b_id from #B where foo='l')                        1      0 NULL         NULL           NULL                                                           NULL                                6          NULL          NULL        NULL       0,00701002 NULL                    NULL     SELECT                  NULL
   6         1   |--Nested Loops(Left Semi Join)                                                         2      1 Nested Loops Left Semi Join NULL                                                           NULL                                6             0     2,508E-05          12       0,00701002 [#A].[id], [#A].[b_id]  NULL     PLAN_ROW                   1
   6         1        |--Table Scan(OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id]))       3      2 Table Scan   Table Scan     OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id])  [#A].[id], [#A].[b_id]              6      0,003125     0,0001636          12        0,0032886 [#A].[id], [#A].[b_id]  NULL     PLAN_ROW                   1
   6         6        |--Table Scan(OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l'))                4      2 Table Scan   Table Scan     OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l')           NULL                                1     0,0032035      8,07E-05           9        0,0036877 NULL                    NULL     PLAN_ROW                   6

(4 rows affected)

*/

Execution Plan - Node 4

Вопрос 2: запрос возвращает результаты.

Вопрос 3:

SELECT * FROM #A WHERE b_id IN (SELECT b_id    FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE  EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE  EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'asdafadsf');

Я думаю, что запрос OP можно переписать в эквивалентный запрос EXISTS.

b_id всегда будет равно b_id, за исключением случаев, когда результаты из части IN () не возвращаются.

Query3-EXISTS-vs-IN

0 голосов
/ 07 мая 2020

Вы правы, что виноват # A.b_id. Запрос

select * from #A where b_id in (select b_id from #B where foo='l')

преобразуется в

select * from #A where b_id in (select #A.b_id from #B where #B.foo='l')

Предположим, что #B не имеет строки с foo = 'l'. Тогда подзапрос не возвращает строк, и весь запрос не возвращает ни одной строки, потому что условие становится where b_id in (<nothing>) и не выполняется.

Если, с другой стороны, #B содержит строки с foo = ' l ', подзапрос возвращает # A.b_id для каждой такой строки. В этом случае запрос возвращает все строки, потому что условие становится where b_id in (b_id, b_id, b_id, ...) и выполняется.

Итак, при попытке (Y) кажется, что не было строки #B с foo = 'l', но когда попытка (Z) была.

0 голосов
/ 07 мая 2020

Запрос

select * from #A where b_id in (select b_id from #B where foo='l')

использует b_id из #A в подзапросе.

Если вы хотите вызвать ошибку, вам нужно будет добавить имя таблицы:

select * from #A where b_id in (select #B.b_id from #B where foo='l')

Это даст вам: «Недопустимое имя столбца 'b_id'».

Я не могу воспроизвести ваш случай «Y» с помощью данного сценария. Оба запроса возвращают одинаковый результат:

select * from #A
select * from #A where b_id in (select b_id from #B where foo='l')
...