I’ve got the following two tables (in MySQL):
Phone_book
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 2 | Jane | 222222222222 |
+----+------+--------------+
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 1 | 0945 | 111111111111 |
+----+------+--------------+
| 2 | 0950 | 222222222222 |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
How do I find out which calls were made by people whose phone_number
is not in the Phone_book
? The desired output would be:
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
asked Dec 15, 2008 at 9:33
Philip MortonPhilip Morton
129k38 gold badges88 silver badges97 bronze badges
0
There’s several different ways of doing this, with varying efficiency, depending on how good your query optimiser is, and the relative size of your two tables:
This is the shortest statement, and may be quickest if your phone book is very short:
SELECT *
FROM Call
WHERE phone_number NOT IN (SELECT phone_number FROM Phone_book)
alternatively (thanks to Alterlife)
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
or (thanks to WOPR)
SELECT *
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number = Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
(ignoring that, as others have said, it’s normally best to select just the columns you want, not ‘*
‘)
answered Dec 15, 2008 at 9:35
7
SELECT Call.ID, Call.date, Call.phone_number
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number=Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
Should remove the subquery, allowing the query optimiser to work its magic.
Also, avoid «SELECT *» because it can break your code if someone alters the underlying tables or views (and it’s inefficient).
answered Dec 15, 2008 at 11:51
WOPRWOPR
5,3136 gold badges47 silver badges63 bronze badges
4
The code below would be a bit more efficient than the answers presented above when dealing with larger datasets.
SELECT *
FROM Call
WHERE NOT EXISTS (
SELECT 'x'
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number
);
informatik01
16k10 gold badges72 silver badges104 bronze badges
answered Dec 15, 2008 at 9:41
AlterlifeAlterlife
6,4677 gold badges36 silver badges47 bronze badges
3
SELECT DISTINCT Call.id
FROM Call
LEFT OUTER JOIN Phone_book USING (id)
WHERE Phone_book.id IS NULL
This will return the extra id-s that are missing in your Phone_book table.
answered Jan 16, 2013 at 10:42
VladoVlado
3,4271 gold badge26 silver badges23 bronze badges
I think
SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
answered Feb 15, 2012 at 12:39
A devA dev
9203 gold badges12 silver badges26 bronze badges
1
SELECT t1.ColumnID,
CASE
WHEN NOT EXISTS( SELECT t2.FieldText
FROM Table t2
WHERE t2.ColumnID = t1.ColumnID)
THEN t1.FieldText
ELSE t2.FieldText
END FieldText
FROM Table1 t1, Table2 t2
Ian Nelson
56.6k20 gold badges75 silver badges103 bronze badges
answered Oct 24, 2013 at 14:53
1
Alternatively,
select id from call
minus
select id from phone_number
answered Aug 21, 2017 at 11:55
elifekizelifekiz
1,44613 silver badges26 bronze badges
1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
answered Dec 9, 2015 at 16:17
JoshYates1980JoshYates1980
3,4572 gold badges36 silver badges57 bronze badges
2
Don’t forget to check your indexes!
If your tables are quite large you’ll need to make sure the phone book has an index on the phone_number
field. With large tables the database will most likely choose to scan both tables.
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
You should create indexes both Phone_Book
and Call
containing the phone_number
. If performance is becoming an issue try an lean index like this, with only the phone number:
The fewer fields the better since it will have to load it entirely. You’ll need an index for both tables.
ALTER TABLE [dbo].Phone_Book ADD CONSTRAINT [IX_Unique_PhoneNumber] UNIQUE NONCLUSTERED
(
Phone_Number
)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = ON) ON [PRIMARY]
GO
If you look at the query plan it will look something like this and you can confirm your new index is actually being used. Note this is for SQL Server but should be similar for MySQL.
With the query I showed there’s literally no other way for the database to produce a result other than scanning every record in both tables.
answered Apr 14, 2021 at 23:41
Simon_WeaverSimon_Weaver
138k81 gold badges639 silver badges680 bronze badges
There are basically 4 techniques for this task, all of them standard SQL.
NOT EXISTS
Often fastest in Postgres.
SELECT ip
FROM login_log l
WHERE NOT EXISTS (
SELECT -- SELECT list mostly irrelevant; can just be empty in Postgres
FROM ip_location
WHERE ip = l.ip
);
Also consider:
- What is easier to read in EXISTS subqueries?
LEFT JOIN / IS NULL
Sometimes this is fastest. Often shortest. Often results in the same query plan as NOT EXISTS
.
SELECT l.ip
FROM login_log l
LEFT JOIN ip_location i USING (ip) -- short for: ON i.ip = l.ip
WHERE i.ip IS NULL;
EXCEPT
Short. Not as easily integrated in more complex queries.
SELECT ip
FROM login_log
EXCEPT ALL -- "ALL" keeps duplicates and makes it faster
SELECT ip
FROM ip_location;
Note that (per documentation):
duplicates are eliminated unless
EXCEPT ALL
is used.
Typically, you’ll want the ALL
keyword. If you don’t care, still use it because it makes the query faster.
NOT IN
Only good without null
values or if you know to handle null
properly. I would not use it for this purpose. Also, performance can deteriorate with bigger tables.
SELECT ip
FROM login_log
WHERE ip NOT IN (
SELECT DISTINCT ip -- DISTINCT is optional
FROM ip_location
);
NOT IN
carries a «trap» for null
values on either side:
- Find records where join doesn’t exist
Similar question on dba.SE targeted at MySQL:
- Select rows where value of second column is not present in first column
Ещё можно так:
SET @var = '{2}{3}{5}';
SELECT
SUBSTRING
(
@var := REPLACE
(
(
SELECT @var := REPLACE(@var, concat('{', `item_id`, '}'), '') AS `ids`
FROM `table`
ORDER BY `ids` DESC
LIMIT 0,1
),
'}{',
','
),
2,
length(@var) - 2
)
Строку ‘{2}{3}{5}’, скриптом формируете.
Для одной записи запрос вернёт ‘5’, для нескольких, например ‘5,6,7’.
В MySQL у меня работает.
Ключевой момент вопроса должен установить для столбца "ABC","BBB","TTT"
исходные данные.
эта таблица будет выглядеть так:
|---+
|val|
|---+
|ABC|
|BBB|
|TTT|
Sqlite не поддерживал функцию sqlite
. так что будет немного сложно sqlite
ваш список в таблицу.
Вы можете использовать CTE Recursive
для создания подобной функции sqlite
- Вам нужно использовать функцию
replace
чтобы удалить"
двойные кавычки из ваших исходных данных». - В CTE есть две колонки
-
val
столбцеval
переносятся данные вашего списка - колонке
rest
чтобы запомнить текущую строкуsplite
-
Вы получите таблицу из CTE
как это.
|---+
|val|
|---+
|ABC|
|BBB|
|TTT|
Затем вы можете сравнить данные с table1
.
Не в
WITH RECURSIVE split(val, rest) AS (
SELECT '', replace('"ABC","BBB","TTT"','"','') || ','
UNION ALL
SELECT
substr(rest, 0, instr(rest, ',')),
substr(rest, instr(rest, ',')+1)
FROM split
WHERE rest <> '')
SELECT * from (
SELECT val
FROM split
WHERE val <> ''
) t where t.val not IN (
select t1.code
from table1 t1
)
sqlfiddle: https://sqliteonline.com/#fiddle-5adeba5dfcc2fks5jgd7ernq
Результат Outut:
+---+
|val|
+---+
|BBB|
|TTT|
Если вы хотите показать его в строке, используйте функцию GROUP_CONCAT
.
WITH RECURSIVE split(val, rest) AS (
SELECT '', replace('"ABC","BBB","TTT"','"','') || ','
UNION ALL
SELECT
substr(rest, 0, instr(rest, ',')),
substr(rest, instr(rest, ',')+1)
FROM split
WHERE rest <> '')
SELECT GROUP_CONCAT(val,',') val from (
SELECT val
FROM split
WHERE val <> ''
) t where t.val not IN (
select t1.code
from table1 t1
)
Результат Outut:
BBB, TTT
sqlfiddle: https://sqliteonline.com/#fiddle-5adecb92fcc36ks5jgda15yq
Примечание. Это неразумно для SELECT * from [my list] where table1.code not in [my list]
, потому что этому запросу негде найти table1
чтобы вы не смогли получить столбец table1.code
Вы можете использовать not exists
или JOIN
чтобы оправдать ожидания.
sqlfiddle: https://sqliteonline.com/#fiddle-5adeba5dfcc2fks5jgd7ernq
Пересказ статьи Brent Ozar. How to Find Missing Rows in a Table
Когда кто-то говорит: «Найдите все строки, которые был удалены», — это много проще сделать, когда таблица имеет столбец identity (id). Давайте возьмем таблицу Users в базе данных Stack Overflow:
Имеются id: -1, 1, 2, 3, 4, 5…, но нет 6 или 7. (Или 0). Если кто-нибудь попросит вас найти все id, которые были удалены или пропущены, как это сделать?
Использование GENERATE_SERIES в SQL Server 2022 и новее
Новая функция GENERATE_SERIES делает то, о чем говорит ее название: генерирует последовательность чисел. Мы можем выполнить соединение таблицы Users с этой последовательностью, чтобы найти все значения последовательности, для которых нет совпадающих строк в таблице Users:
DECLARE @FirstId INT, @LastId INT;
SELECT @FirstId = MIN(Id),
@LastId = MAX(Id)
FROM dbo.Users;
SELECT gs.value
FROM GENERATE_SERIES(@FirstId, @LastId, 1) gs
LEFT OUTER JOIN dbo.Users u ON gs.value = u.Id
WHERE u.Id IS NULL;
Поначалу LEFT OUTER JOIN может вам показаться несколько неестественным, но работает отлично:
Что такое, спросите вы? Почему GENERATE_SERIES подчеркнуто красным? Ну, SQL Server Management Studio не обновилась синтаксисом T-SQL, который был в последнем релизе.
К счастью, Microsoft разделила установки приложений для SSMS и самого ядра SQL Server именно по этой причине — долгое время выпуска SSMS не позволяло команде разработчиков ядра ускорить выпуск, поэтому они поместили менее часто обновляемую SSMS в свой собственный установщик.
(Я правильно понял? Простите меня, если это не так.)
Использование таблицы чисел в более старых версиях
Если вы еще не работаете на SQL Server 2022, то можете создать собственную таблицу чисел, используя следующие примеры. Только убедитесь, что ваша таблица чисел содержит по крайней мере столько строк, сколько номеров id вы ищете. Вот пример с таблицей на 100000000 строк:
DROP TABLE IF EXISTS dbo.Numbers;
CREATE TABLE dbo.Numbers (Number INT PRIMARY KEY CLUSTERED);
INSERT INTO dbo.Numbers(Number)
SELECT TOP 10000000 row_number() over(order by t1.number) as N
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
GO
Затем мы будем использовать это в том же ключе, что и GENERATE_SERIES:
DECLARE @FirstId INT, @LastId INT;
SELECT @FirstId = MIN(Id),
@LastId = MAX(Id)
FROM dbo.Users;
SELECT n.Number
FROM dbo.Numbers n
LEFT OUTER JOIN dbo.Users u ON n.Number = u.Id
WHERE u.Id IS NULL
AND n.Number > @FirstId
AND n.Number < @LastId
ORDER BY n.Number;
Это дает похожие результаты, но не идентичные:
В чем отличие? Да, этот метод не включает 0! Когда я наполнял мою таблицу чисел, то строил список только положительных целых. Единственной наиболее часто встречающейся мне ошибкой при использовании таблиц чисел является то, что они не охватывают всех необходимых нам чисел. Убедитесь, что они содержат как самое меньшее, так и самое большее из требуемых вам значений — проблема, которой мы не имеем с GENERATE_SERIES, поскольку там мы просто указываем начальное и конечное значения, а SQL Server делает все остальное.
Я не хочу погружаться глубже в другие способы решения этой проблемы, Itzik Ben-Gan введет вас в курс дела. Что до меня, мне нравится решать эту проблему быстро и легко с помощью GENERATE_SERIES. Кроме того, я ленив.