Почему этот запрос возвращает результаты только с непустыми дочерними таблицами?

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

У основной таблицы есть две дочерние таблицы:

CREATE TABLE main (id INT PRIMARY KEY, name VARCHAR(8));

CREATE TABLE child1(id INT PRIMARY KEY, main_id int, name VARCHAR(8));
ALTER TABLE child1 add constraint fk_child1_main foreign key (main_id) references main (id);

CREATE TABLE child2(id INT PRIMARY KEY, main_id int, name VARCHAR(8));
ALTER TABLE child2 add constraint fk_child2_main foreign key (main_id) references main (id);

INSERT INTO main (id, name) VALUES (1, 'main');
INSERT INTO child1 (id, main_id, name) VALUES (2, 1, 'child1');

В child2 нет строк, и следующий запрос не возвращает строк, когда он пуст:

SELECT
  main.*
FROM
  main
INNER JOIN
  child1
ON
  main.id = child1.main_id
INNER JOIN
  child2
ON
  main.id = child2.main_id
WHERE
  child1.name = 'child1' OR
  child2.name = 'DOES NOT EXIST';

Если строка добавляется к child2, даже если она не соответствует предложению WHERE, тогда SELECT возвращает строку в основной таблице.

INSERT INTO child2 (id, main_id, name) VALUES (4, 1, 'child2');

Я тестировал это на Derby и SQLite, так что это похоже на что-то общее с базами данных.

Почему это так себя ведет?

Что я могу сделать, чтобы это исправить?

Я мог бы изменить на UNION отдельные SELECT, но это гораздо более многословно, и, кроме того, мы генерируем SQL динамически, и мне не нужно менять наш код.

Еще одно исправление — просто добавить в базу данных тупую строку, но это грязно.

PS Основная таблица — это таблица сеансов в системе управления активами, в которой записываются активы, которые ищут клиенты. Существуют различные типы поиска, и каждый вид получает отдельную дочернюю таблицу, а также есть дочерняя таблица атрибутов для пар ключ/значение для сеанса, в котором можно выполнять поиск.


person Blair Zajac    schedule 09.05.2009    source источник


Ответы (3)


Если в таблице child2 нет строк, запрос не возвращает строк из-за внутреннего соединения с таблицей child2. Если вы выполняете внутреннее соединение с таблицей, в которой нет строк, вы никогда не получите никаких результатов — вместо этого вам придется выполнить внешнее соединение с дочерним элементом2, если вы хотите получить результаты, когда дочерний элемент2 пуст.

Когда у child2 есть строка, причина, по которой ваш запрос возвращает результаты, связана с предложением where:

WHERE
  child1.name = 'child1' OR
  child2.name = 'DOES NOT EXIST';

Внутреннее соединение говорит, что в child2 должно быть что-то с совпадающим идентификатором, но в предложении where есть ИЛИ, поэтому вы получите результаты только потому, что child1.name = 'child1'. После этого базе данных не нужно беспокоиться о таблицах child2.

Починить это:

У меня есть подозрение, что вы хотите вернуть дочерние строки только при выполнении некоторого условия. Вы должны выполнить внешнее соединение с обоими из них и, возможно, также переместить свои дополнительные условия из предложения where в предложение соединения, например:

SELECT
  main.*
FROM
  main
LEFT OUTER JOIN
  child1
ON
  main.id = child1.main_id
  AND child1.name = 'child1'
LEFT OUTER JOIN
  child2
ON
  main.id = child2.main_id
  AND child2.name = 'whatever'
  • Внешние соединения означают, что у вас есть шанс получить результаты, даже если одна таблица пуста.

  • Перемещение дополнительных условий (child1.name = ...) из предложения WHERE во внешнее соединение означает, что вы получаете информацию о таблицах только в том случае, если условие истинно. (Я думаю, что это может быть то, что вы пытаетесь сделать, но, возможно, нет, и в этом случае оставьте условия в предложении WHERE там, где они были у вас изначально.)

person codeulike    schedule 09.05.2009

Он ничего не возвращает, потому что вы используете внутренние соединения.

Измените свои внутренние соединения на левые соединения

person Drevak    schedule 09.05.2009
comment
Тогда почему добавление несоответствующей строки в child2 меняет результат запроса? Согласно этому утверждению, даже после добавления несоответствующей строки в child2 запрос не должен возвращать никаких строк. - person Blair Zajac; 09.05.2009
comment
Неважно, я вижу, что это main.id = child2.main_id не позволяло запросу возвращать какие-либо результаты, даже если child2.name не совпадало. Я просто проигнорировал эту часть запроса. - person Blair Zajac; 09.05.2009

Когда вы говорите INNER JOIN, вы просите запрос вернуть строки, которые имеют результаты на обеих сторонах соединения. Это означает, что все строки, не имеющие соответствующих дочерних строк, будут удалены.

Похоже, что вы ищете LEFT JOIN, который будет включать все строки с левой стороны соединения (основной), даже если у них нет соответствующей записи с правой стороны (child1, child2).

Это стандартное поведение и очень распространенная проблема для людей, не знакомых с SQL. В Википедии есть все подробности, в противном случае быстро Поиск Google выдает множество результатов.

person Generic Error    schedule 09.05.2009
comment
Тогда почему добавление несоответствующей строки в child2 меняет результат запроса? Согласно этому утверждению, даже после добавления несоответствующей строки в child2 запрос не должен возвращать никаких строк. - person Blair Zajac; 09.05.2009
comment
Неважно, я вижу, что это main.id = child2.main_id не позволяло запросу возвращать какие-либо результаты, даже если child2.name не совпадало. Я просто проигнорировал эту часть запроса. - person Blair Zajac; 09.05.2009