Отфильтруйте запрос «один ко многим», требуя, чтобы все многие соответствовали критериям - PullRequest
4 голосов
/ 27 января 2009

Представьте себе следующие таблицы:

создать поля таблицы (id int, имя, текст, ...);

создание табличных вещей в ящиках (id int, box_id int, thing enum ('apple,' banana ',' orange ');

А таблицы выглядят так:

Boxes:
id | name
1  | orangesOnly
2  | orangesOnly2
3  | orangesBananas
4  | misc

thingsinboxes:
id | box_id | thing
1  |  1     | orange
2  |  1     | orange
3  |  2     | orange
4  |  3     | orange
5  |  3     | banana
6  |  4     | orange
7  |  4     | apple
8  |  4     | banana

Как выбрать поля, содержащие хотя бы один апельсин и ничего, кроме апельсина?

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

Я бы хотел сохранить все это в SQL, если это возможно, вместо постобработки результирующего набора скриптом.

Я использую и postgres, и mysql, поэтому подзапросы, вероятно, плохие, учитывая, что mysql не оптимизирует подзапросы (во всяком случае, до версии 6).

Ответы [ 2 ]

5 голосов
/ 27 января 2009
SELECT b.*
FROM boxes b JOIN thingsinboxes t ON (b.id = t.box_id)
GROUP BY b.id
HAVING COUNT(DISTINCT t.thing) = 1 AND SUM(t.thing = 'orange') > 0;

Вот еще одно решение, которое не использует GROUP BY:

SELECT DISTINCT b.*
FROM boxes b
  JOIN thingsinboxes t1 
    ON (b.id = t1.box_id AND t1.thing = 'orange')
  LEFT OUTER JOIN thingsinboxes t2 
    ON (b.id = t2.box_id AND t2.thing != 'orange')
WHERE t2.box_id IS NULL;

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

2 голосов
/ 27 января 2009

Я думаю, что запрос Билла Карвина - это нормально, однако, если относительно небольшая часть коробок содержит апельсины, вы сможете ускорить процесс, используя индекс в поле thing:

SELECT b.*
FROM boxes b JOIN thingsinboxes t1 ON (b.id = t1.box_id)
WHERE t1.thing = 'orange'
AND NOT EXISTS (
    SELECT 1
    FROM thingsinboxes t2
    WHERE t2.box_id = b.id
    AND t2.thing <> 'orange'
)
GROUP BY t1.box_id

Подзапрос WHERE NOT EXISTS будет запускаться только один раз для апельсина, поэтому он не слишком дорогой, если в нем мало апельсинов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...