2011-01-12 4 views
5

Я очень ищу способ отфильтровать строки из таблицы SELECT одной таблицы на основе определенных значений в строках другой таблицы.удаление строк из SELECT на основе столбцов в другой таблице

Я экспериментирую с примером структуры ниже. У меня есть таблица содержимого блога (одна строка за сообщение в блоге) и еще одна таблица метаданных о сообщениях (одна строка для пары ключ-значение, каждая строка со столбцом, связанная с записью в блоге, много строк в Сообщение блога). Я хочу вытащить строку posts только в том случае, если в metadata нет строк, где metadata.pid=posts.pid AND metadata.k='optout'. То есть, для структуры примера ниже, я просто хочу вернуть строку posts.id=1.

(на основе того, что я пробовал) JOIN s не до конца удаления сообщения, которые имеют некоторые метаданные где metadata.k='optout', потому что другая строка метаданных для этого pid означает, что делает его в результаты.

mysql> select * from posts; 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 1 | Foo | Some content | 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
3 rows in set (0.00 sec) 

mysql> select * from metadata; 
+------+-----+--------+-----------+ 
| mdid | pid | k  | v   | 
+------+-----+--------+-----------+ 
| 1 | 1 | date | yesterday | 
| 2 | 1 | thumb | img.jpg | 
| 3 | 2 | date | today  | 
| 4 | 2 | optout | true  | 
| 5 | 3 | date | tomorrow | 
| 6 | 3 | optout | true  | 
+------+-----+--------+-----------+ 
6 rows in set (0.00 sec) 

подзапрос может дать мне обратное, что я хочу:

mysql> select posts.* from posts where pid = any (select pid from metadata where k = 'optout'); 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
2 rows in set (0.00 sec) 

... но используя pid != any (...) дает мне все 3 строк в постах, вызывают каждый pid имеет ряд метаданных где k!='optout'.

ответ

8

Похоже, вы хотите сделать LEFT JOIN, а затем проверить результаты, в которых значение объединенной таблицы равно NULL, что указывает на отсутствие такой объединенной записи.

Например:

SELECT * FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

Это будет выбрать любую строку из таблицы posts, для которых не существует никакой соответствующих metadata строки со значением k = 'optout'.

Редактировать: Стоит отметить, что это ключевое свойство левого соединения и не будет работать с регулярным соединением; левое соединение всегда будет возвращать значения из первой таблицы, даже если в объединенной таблице (таблицах) не существует совпадающих значений, что позволяет выполнять выбор в зависимости от отсутствия этих строк.

редактировать 2: Поясним, что здесь происходит относительно LEFT JOIN по сравнению с JOIN (которое я ссылаюсь в качестве INNER JOIN для ясности, но это взаимозаменяемыми в MySQL).

Предположим, что вы один из этих двух запросов:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON posts.pid = metadata.pid; 

или

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON posts.pid = metadata.pid; 

Оба запроса производят следующий набор результатов:

+-----+-------+--------------+------+-------+-----------+ 
| pid | title | content  | mdid | k  | v   | 
+-----+-------+--------------+------+-------+-----------+ 
| 1 | Foo | Some content | 1 | date | yesterday | 
| 1 | Foo | Some content | 2 | thumb | img.jpg | 
+-----+-------+--------------+------+-------+-----------+ 

Теперь, давайте предположим, что мы изменяем запрос, чтобы добавить дополнительные критерии для «optout», которые были упомянуты.Во-первых, INNER JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

Как и ожидалось, это не возвращает никаких результатов:

Empty set (0.00 sec) 

Теперь, меняя что к LEFT JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

Это производит набор результатов:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

Разница между INNER JOIN и LEFT JOIN заключается в том, что INNER JOIN вернет результат только в том случае, если строки из объединенных таблиц BOTH совпадают. В LEFT JOIN совпадающие строки из первой таблицы будут ВСЕГДА возвращаться, независимо от того, найдено ли что-либо для соединения. Во многих случаях не имеет значения, какой из них вы используете, но важно выбрать правильный, чтобы не получить неожиданные результаты по линии.

Таким образом, в этом случае предложенный запрос о:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

Вернется тот же набор результатов, как описано выше:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

Будем надеяться, что очищает его! Объединиться - отличная вещь, о которой нужно узнать, имея полное представление о том, когда использовать, какая из них очень хорошая.

+0

Итак, позвольте мне увидеть, если я получаю это ... на должность с OptOut подзапрос соответствует ряд метаданных, поэтому metadata.mdid не равно нулю, поэтому он не выбирается. Но пост без optout, подзапрос не соответствует строке, поэтому правая сторона заполняется нулями, поэтому предложение where истинно. – alxndr

+1

Я добавил еще один раздел ответа, связанного с тем, как работает соединение, которое должно очистить от него любые серые области. Надеюсь, это поможет! – futureal

3

Вы можете попробовать что-то вроде

select p.* 
from posts p 
where NOT EXISTS (
         select pid 
         from metadata 
         where k = 'optout' 
         and  pid = p.pid 
        ) 
+0

Ничего себе, спасибо. Собираюсь читать на НЕ СУЩЕСТВУЮЩИХ. – alxndr

+0

FYI, дающий галочку другому ответу, вызывает 36000 строк, а левое соединение - на 0,1 с быстрее ... – alxndr

+1

На небольшом результирующем наборе два запроса должны выполняться примерно одинаково. Однако, когда вы используете 'EXISTS' или' NOT EXISTS' с подзапросом, ему нужно будет вычислить таблицу подзапросов и скопировать ее во временную таблицу. Так как ваши результирующие наборы растут, это может стать большим узким местом производительности. Я бы не избегал этого полностью, иногда это делает вещи намного легче читать/понимать, чем сложное соединение - вам просто нужно знать тип набора результатов, который он может в конечном итоге произвести. – futureal

Смежные вопросы