Как выбрать строки без соответствующей записи в другой таблице?


323

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

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

Я нашел несколько примеров такого рода запросов в Интернете, но все они, кажется, дают примеры, а не объяснения, и я не понимаю, почему они работают.

Может ли кто-нибудь объяснить мне, как построить запрос, который возвращает все строки без совпадений в другой таблице, и что он делает, чтобы я мог сделать эти запросы самостоятельно, вместо того чтобы переходить к SO для каждой таблицы в этом беспорядке, которая имеет нет ограничений FK?

Ответы:


614

Вот простой запрос:

SELECT t1.ID
FROM Table1 t1
    LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

Ключевые моменты:

  1. LEFT JOINиспользуется; это вернет ВСЕ строки из Table1, независимо от того, есть ли соответствующая строка в Table2.

  2. WHERE t2.ID IS NULLПоложение; это ограничит результаты, возвращаемые только теми строками, для которых возвращаемый идентификатор Table2равен NULL - другими словами, для этого конкретного идентификатора из НЕТ нет записи . будет возвращено как NULL для всех записей, из которых идентификатор не совпадает .Table2Table1Table2.IDTable1Table2


4
Сбой, если идентификатор равен NULL
Майкл

169
@ Майкл - Если NULLв вашей схеме есть действительный идентификатор, у вас могут быть большие проблемы, вы не согласны? :)
Риного

1
будет ли это работать, даже если в table1 больше записей, чем в table2? если table1 имеет 100 записей, а table2 имеет 200 записей (100, которые соответствуют / объединяются и 100, которые не соответствуют / объединяются), мы бы вернули все 200 записей?
Хуан Велес

1
Мне часто нравится оборачивать левое соединение как подзапрос / встроенное представление, чтобы гарантировать отсутствие взаимодействия между предложением WHERE и левым соединением.
Эндрю Вулф

1
@Jas Ключевой пункт 1 ответа, ВСЕ строки из первой таблицы, даже те, которые не соответствуют условию t1.ID = t2.ID левого соединения. Если вы измените первую строку на SELECT t1.ID, t2.IDи удалите строку WHERE, вы лучше поймете, как это работает.
Питер Лабош

97

Я бы использовал EXISTSвыражение, так как оно более мощное, то есть вы можете более точно выбрать строки, к которым хотите присоединиться, в случае, если LEFT JOINвам нужно взять все, что находится в объединенной таблице. Его эффективность, вероятно, такая же, как в случае LEFT JOINс нулевым тестом.

SELECT t1.ID
FROM Table1 t1
WHERE NOT EXISTS (SELECT t2.ID FROM Table2 t2 WHERE t1.ID = t2.ID)

Что-то такое простое легко обрабатывается оптимизатором запросов для лучшего выполнения.
Эндрю Вульф

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

1
Просто, элегантно и это решило мою проблему! Хороший!
MikeMighty

2
На самом деле скорость одного запроса снизилась с 7 до 200 мс ... (по сравнению с WHERE t2.id IS NULL) Спасибо.
Моти Корец

4
@MotiKorets ты имеешь в виду увеличение скорости :)
Ондрей Бозек

14
SELECT id FROM table1 WHERE foreign_key_id_column NOT IN (SELECT id FROM table2)

В таблице 1 есть столбец, к которому вы хотите добавить ограничение внешнего ключа, но значения в foreign_key_id_columnне все совпадают со значением idв таблице 2.

  1. Первоначальный выбор списков idиз таблицы 1. Это будут строки, которые мы хотим удалить.
  2. Предложение NOT INв операторе where ограничивает запрос только строками, значения foreign_key_id_columnкоторых в списке нет в таблице 2 ids.
  3. SELECTЗаявление в скобках будет получить список всех idс , которые находятся в таблице 2.

@ zb226: Ваша ссылка на связана с ограничениями в INпредложении со списком литеральных значений. Это не относится к использованию INпредложения с результатом подзапроса. Этот принятый ответ на этот вопрос фактически решает проблему с помощью подзапроса. (Большой список литеральных значений проблематичен, поскольку он создает огромное выражение SQL. Подзапрос работает нормально, потому что, даже если результирующий список большой, само выражение SQL мало.)
Каннан Гундан,

@KannanGoundan Ты абсолютно прав. Снятие некорректного комментария.
zb226

8

Где T2находится таблица, к которой вы добавляете ограничение:

SELECT *
FROM T2
WHERE constrained_field NOT
IN (
    SELECT DISTINCT t.constrained_field
    FROM T2 
    INNER JOIN T1 t
    USING ( constrained_field )
)

И удалите результаты.


4

Пусть у нас есть следующие 2 таблицы (зарплата и работник) введите описание изображения здесь

Теперь я хочу те записи из таблицы сотрудников, которых нет в зарплате. Мы можем сделать это тремя способами:

  1. Использование внутреннего соединения
select * from employee
where id not in(select e.id from employee e inner join salary s on e.id=s.id)

введите описание изображения здесь

  1. Использование левого внешнего соединения
select * from employee e 
left outer join salary s on e.id=s.id  where s.id is null

введите описание изображения здесь

  1. Использование полного соединения
select * from employee e
full outer join salary s on e.id=s.id where e.id not in(select id from salary)

введите описание изображения здесь


2

От подобного вопроса здесь MySQL Внутренний Запрос Соединения, чтобы Получить Записи, Не Представленные в Другой Таблице, я получил это, чтобы работать

SELECT * FROM bigtable 
LEFT JOIN smalltable ON bigtable.id = smalltable.id 
WHERE smalltable.id IS NULL

smalltableгде у вас есть пропущенные записи, bigtableгде у вас есть все записи. В списке запросов перечислены все записи, которые не существуют, smalltableно существуют в bigtable. Вы можете заменить idлюбым другим подходящим критерием.


0

Вы можете выбрать Просмотры , как показано ниже:

CREATE VIEW AuthorizedUserProjectView AS select t1.username as username, t1.email as useremail, p.id as projectid, 
(select m.role from userproject m where m.projectid = p.id and m.userid = t1.id) as role 
FROM authorizeduser as t1, project as p

а затем поработайте в представлении для выбора или обновления:

select * from AuthorizedUserProjectView where projectid = 49

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

[Result of select on the view][1]

0

Я не знаю, какой из них оптимизирован (по сравнению с @AdaTheDev), но этот, кажется, быстрее, когда я использую (по крайней мере, для меня)

SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2

Если вы хотите получить любой другой конкретный атрибут, вы можете использовать:

SELECT COUNT(*) FROM table_1 where id in (SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2);


-2

Вы можете сделать что-то вроде этого

   SELECT IFNULL(`price`.`fPrice`,100) as fPrice,product.ProductId,ProductName 
          FROM `products` left join `price` ON 
          price.ProductId=product.ProductId AND (GeoFancingId=1 OR GeoFancingId 
          IS NULL) WHERE Status="Active" AND Delete="No"

-6

Как выбрать строки без соответствующей записи в таблице «Оба»?

    выберите * из [dbo]. [EmppDetails] e
     Право присоединиться [Сотрудник]. [Пол] d на e.Gid = d.Gid
    где e.Gid равен нулю

    союз 
    выберите * из [dbo]. [EmppDetails] e
     осталось присоединиться [Сотрудник]. [Пол] d на e.Gid = d.Gid
    где d.Gid равен нулю

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.