Почему этот запрос возвращает результаты только с непустыми дочерними таблицами? - PullRequest
0 голосов
/ 09 мая 2009

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

Основная таблица имеет две дочерние таблицы:

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

Ответы [ 3 ]

4 голосов
/ 09 мая 2009

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

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

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

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

Чтобы исправить это:

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

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, где они у вас изначально были.)

2 голосов
/ 09 мая 2009

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

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

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

2 голосов
/ 09 мая 2009

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

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

...