Расхождения между таблицами
Поиск расхождений между таблицами
Способ 1
Разберем, как найти расхождения между двумя таблицами, которые по смыслу должны соответствовать друг другу.
Есть 2 таблицы: band_extended и album. Эти 2таблицы содержат количество альбомов. И это количество альбомов должно совпадать в обеих таблицах.
SELECT COUNT(*) FROM album; -- 121 918 SELECT SUM(n_albums) FROM band_extended; -- 121 918
Хотя общие суммы совпадают, но на самом деле если взять отдельные номера музыкальных групп, то между этими таблицами есть некоторые расхождения.
SELECT COUNT(*) FROM album WHERE band_id = -100;
Для группы с band_id (-100) в таблице album музыкальных альбомов 0.
SELECT n_albums FROM band_extended WHERE band_id = -100;
А в таблице band_extended количество альбомов 25.
Так и для группы с band_id 93:
SELECT * FROM album WHERE band_id = 93; -- 11
SELECT n_albums FROM band_extended WHERE band_id = 93; -- NULL
SELECT * FROM album WHERE band_id IN ( SELECT band_id FROM band WHERE name = 'Queen'); -- 14
SELECT n_albums FROM band_extended WHERE band_id IN ( SELECT band_id FROM band WHERE name = 'Queen'); -- NO ROWS, 0
Ситуация легкая, потому что мы заранее знаем номера band_id, в которых идет расхождение.
Представим, что мы не знаем, где расхождение. И сейчас пошагово будем это выяснять.
ШАГ 1 - Сделать группировку строк, чтобы найти количество альбомов для каждой музыкальной группы. Это нужно для того, чтобы потом сравнивать 2 таблицы между собой в едином формате.
SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1;
Мы получили результат, где для каждой band_id видно количество альбомов.
ШАГ 2 - Выведем данные в таком же формате из таблицы band_extended. Но так как в таблице band_extended есть группы, у которых нет альбомов - их учитывать не нужно
SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0;
ШАГ 3 - Сделаем сравнение двух таблиц с помощью EXCEPT (вычитание первого запроса из второго запроса)
SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 EXCEPT SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0;
И вот мы получили те строки, в которых есть расхождения:
ШАГ 4 - Сделаем сравнение двух таблиц с помощью EXCEPT (вычитание первого запроса из второго запроса), но только сейчас первую таблицу вычитаем из второй
SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 EXCEPT SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1;
Но это не очень удобный способ т.к. допустим, есть ситуация, когда band_id совпадает и есть в обеих таблицах, но количество альбомов разное. Удобнее, если разместить 2 значения albom_count рядом друг с другом.
Способ 2 (Более удобный, продолжение первого способа)
SELECT * FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id;
Так как нам нужны только расхождения в таблице, дописываем условие WHERE
:
WHERE table_1.album_count <> table_2.album_count
SELECT * FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id WHERE table_1.album_count <> table_2.album_count;
В итоге не получили ни одной строки. Проблема в значениях NULL.
Чтобы избежать проблем с опусканием строк со значениями NULL дописываем условие:
OR (table_1.album_count IS NULL AND table_2.album_count IS NOT NULL) OR (table_1.album_count IS NOT NULL AND table_2.album_count IS NULL)
SELECT * FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id WHERE table_1.album_count <> table_2.album_count OR (table_1.album_count IS NULL AND table_2.album_count IS NOT NULL) OR (table_1.album_count IS NOT NULL AND table_2.album_count IS NULL);
Все вроде здорово, мы получили все 3 расхождения.
Но для band_id = 93
видим, что якобы строки есть только в таблице album
. Но это не так, хотя часть таблицы band_extended
показывает, что значения NULL
.
Произошло это потому что в первом подзапросе WHERE n_albums > 0
. И это условие как раз отбросило строку band_id = 93
для таблицы band_extended
. Потому что для этой строки в колонке n_albums
значение NULL
. И когда мы пишем n_albums > 0
, мы отбрасываем это значение.
Поэтому нужно добавить еще одно условие в условие WHERE n_albums > 0
, а именно:
OR n_albums IS NULL
SELECT * FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 OR n_albums IS NULL ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id WHERE table_1.album_count <> table_2.album_count OR (table_1.album_count IS NULL AND table_2.album_count IS NOT NULL) OR (table_1.album_count IS NOT NULL AND table_2.album_count IS NULL);
Устранение расхождений между таблицами
В этой лекции мы будем устранять расхождения межу таблицами не вручную, а с помощью команд.
Первое, что мы сделаем - это создадим таблицу на основе запроса, который мы делали последним в предыдущем уроке
CREATE TABLE table_comperison AS SELECT table_1.band_id as band_id_band_extended, table_1.album_count as album_count_band_extended, table_2.band_id as band_id_album, table_2.album_count as album_count_album FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 OR n_albums IS NULL ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id WHERE table_1.album_count <> table_2.album_count OR (table_1.album_count IS NULL AND table_2.album_count IS NOT NULL) OR (table_1.album_count IS NOT NULL AND table_2.album_count IS NULL);
Сразу оговорим, что когда 2 таблицы должны соответствовать друг другу, но это соответствие нарушено, зачастую не понятно, в какой из двух таблиц верные данные.
В рамках урока, предположим, что мы провели работу по сверке и выяснили, что данные в таблице album верные, а в таблице band_extended данные с ошибками и нам нужно это исправить.
В данной ситуация вся задача распадается на 3 отдельные подзадачи:
- band_id = 93 - в таблице band_extended есть нужная строка строка со значением 93, но в этой строке неправильное значение в колонке n_albums. Тут будет использоваться команда UPDATE т.к. строка уже существует в таблице band_extended
- band_id = -100. Строка есть в band_extended, но для нее нет ни одной строки в таблице album. Так как мы приняли, что таблица album верная, то в этой ситуации будет использовать DELETE т.к. эту запись нужно просто удалить.
- band_id = 192. Тут нужна команда INSERT т.к. нужно добавить строки в таблицу band_extended.
-- 1. band_id = 93 - UPDATE UPDATE band_extended as band_alias SET n_albums = table_1.album_count FROM ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_1 WHERE band_alias.band_id = table_1.band_id AND band_alias.band_id IN ( SELECT band_id_band_extended FROM table_comperison WHERE band_id_band_extended IS NOT NULL AND band_id_album IS NOT NULL );
Этот запрос SQL выполняет обновление столбца "n_albums" в таблице "band_extended" на основе подсчета количества альбомов для каждой группы.
- Сначала создается временная таблица table_1 с помощью подзапроса, который выбирает band_id из таблицы album и подсчитывает количество альбомов для каждой группы с помощью COUNT(*) в столбце album_count.
- Затем осуществляется обновление столбца "n_albums" в таблице "band_extended" через операцию SET, присваивая ему значение album_count из временной таблицы table_1.
- Далее используется фраза FROM, чтобы указать, что данные для обновления берутся из временной таблицы table_1.
- Условие WHERE связывает таблицы band_extended и table_1 по band_id, чтобы обновление происходило только для соответствующих записей.
- Также есть условие AND для фильтрации только тех записей, где band_id в таблице band_extended присутствует в подзапросе SELECT в скобках, который выбирает band_id_band_extended из таблицы table_comperison, удовлетворяющие определенным условиям: band_id_band_extended не равен NULL и band_id_album не равен NULL.
--2. band_id = -100 - DELETE DELETE FROM band_extended WHERE band_id IN ( SELECT band_id_band_extended FROM table_comperison WHERE band_id_band_extended IS NOT NULL AND band_id_album IS NULL);
Этот запрос SQL удаляет записи из таблицы "band_extended" на основе определенных условий, указанных в подзапросе.
- В подзапросе SELECT в скобках выбираются band_id_band_extended из таблицы table_comperison, удовлетворяющие определенным условиям: band_id_band_extended не равен NULL и band_id_album равен NULL.
- Затем оператор DELETE указывает таблицу "band_extended" как целевую для удаления.
- Условие WHERE связывает записи в таблице "band_extended" с результатом подзапроса, устанавливая критерии для удаления. Только те записи, где band_id соответствует band_id_band_extended из подзапроса, будут удалены из таблицы "band_extended".
--3. band_id = 192 - INSERT INSERT INTO band_extended (band_id, name, year, comment, n_albums, n_songs) SELECT b.band_id, b.name, b.year, b.comment, COALESCE(albums.album_count, 0) as n_albums, COALESCE(songs.songs_count, 0) as n_songs FROM band as b LEFT OUTER JOIN (SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1) as albums ON albums.band_id = b.band_id LEFT OUTER JOIN (SELECT band_id, COUNT(*) as song_count FROM song GROUP BY 1) as songs ON songs.band_id = b.band_id WHERE b.band_id IN ( SELECT band_id_album FROM table_comperison WHERE band_id_band_extended IS NULL AND band_id_album IS NOT NULL);
INSERT INTO band_extended (band_id, name, year, comment, n_albums, n_songs)
Эта часть запроса указывает, что данные будут вставлены в таблицу band_extended
и перечисляет столбцы, в которые будет производиться вставка.
SELECT b.band_id, b.name, b.year, b.comment, COALESCE(albums.album_count, 0) as n_albums, COALESCE(songs.songs_count, 0) as n_songs
Здесь используется оператор SELECT
для выбора данных из таблицы band
(алиас как b) и результатов агрегированных функций COUNT(*)
для таблиц album
и song
. Функция COALESCE
используется для замены NULL
значений на 0.
FROM band as b
Указывает, что выборка данных будет производиться из таблицы band
под псевдонимом b
.
LEFT OUTER JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1) as albums ON albums.band_id = b.band_id
В этой строке используется оператор LEFT OUTER JOIN
для объединения таблицы band
с результатами агрегированной функции COUNT(*)
для таблицы album
. Новая таблица, обозначаемая как albums
, содержит количество альбомов для каждого band_id
из таблицы band
.
LEFT OUTER JOIN ( SELECT band_id, COUNT(*) as song_count FROM song GROUP BY 1) as songs ON songs.band_id = b.band_id
Аналогично, здесь используется оператор LEFT OUTER JOIN
для объединения таблицы band
с результатами агрегированной функции COUNT(*)
для таблицы song
. Новая таблица, обозначаемая как songs
, содержит количество песен для каждого band_id
из таблицы band
.
WHERE b.band_id IN ( SELECT band_id_album FROM table_comperison WHERE band_id_band_extended IS NULL AND band_id_album IS NOT NULL)
Условие WHERE
фильтрует записи из таблицы band
, чтобы вставка происходила только для тех записей, у которых band_id
соответствует определенным условиям из подзапроса.
Таким образом, этот запрос вставляет данные в таблицу band_extended
на основе данных из таблицы band
и результатов агрегированных функций COUNT(*)
для таблиц album
и song
с применением определенных условий фильтрации.
Теперь запустим проверочный запрос из прошлого урока и проверим:
SELECT table_1.band_id as band_id_band_extended, table_1.album_count as album_count_band_extended, table_2.band_id as band_id_album, table_2.album_count as album_count_album FROM ( SELECT band_id, n_albums as album_count FROM band_extended WHERE n_albums > 0 OR n_albums IS NULL ) as table_1 FULL JOIN ( SELECT band_id, COUNT(*) as album_count FROM album GROUP BY 1 ) as table_2 ON table_1.band_id = table_2.band_id WHERE table_1.album_count <> table_2.album_count OR (table_1.album_count IS NULL AND table_2.album_count IS NOT NULL) OR (table_1.album_count IS NOT NULL AND table_2.album_count IS NULL);