Бывают ситуации, когда есть список значений, и нужно проверить, каких значений из списка нет в БД. Например, есть список: ['5-123', 'zak222', '65_1', '258a', '3456'], нужно найти значения, которых нет в БД.
В таблице 4 строки:

Таблица с заказами, не хватает заказа 258a.
Таблица с заказами, не хватает заказа 258a.

Нужно найти строку, которого нет в БД - это 258a.

SQL-запросом без временной таблицы найти отсутствующие значения невозможно, так как из базы нельзя получить значения, которых там нет. При небольшом количестве строк разницу можно заметить визуально, но с тысячами значений это превращается в утомительный ручной труд. Классическое решение — экспорт списка из БД в Excel с последующим ВПР (VLOOKUP) для сравнения с полным списком. Результат: где ВПР возвращает ошибку #Н/Д — это и есть те значения, которых нет в БД, но это неудобно и долго.

Можно было бы создать временную таблицу, но не у всех есть права для этого, а задачу надо решить. В SQL для решения таких проблем можно использовать литеральную таблицу (или конструктор табличных значенийtable value constructor) — это способ создания временного набора данных прямо в SQL‑запросе без обращения к постоянным таблицам. Вы буквально перечисляете строки и столбцы «руками» прямо в коде, и SQL воспринимает их как таблицу.

Синтаксис

  1. Основной способ для многих СУБД, проверено на PostgreSQL, Oracle, MS SQL:
    SELECT * FROM (VALUES
    ('5-123', 'Анна'),
    ('zak222', 'Иван'),
    ('65_1', 'Мария'),
    ('258a', 'Петр'),
    ('3456', 'Владимир')
    ) AS temp(zakaz, name);

    В MySQL тоже работает, но синтаксис чуть другой, подробнее об этом будет ниже.

2. PostgreSQL
Для поиска нам достаточно одного поля:
SELECT * FROM (VALUES ('5-123'), ('zak222'), ('65_1'), ('258a'), ('3456')) AS temp(zakaz);

Но когда нужен только один столбец, в PostgreSQL самый простой способ это unnest()— это функция, которая разворачивает массив в отдельные строки.
Из ARRAY[1, 2, 3] делает три строки: 123. Идеальна для создания литеральных таблиц прямо в запросе вместо VALUES:

SELECT * FROM unnest(ARRAY['5-123', 'zak222', '65_1', '258a', '3456']) AS zakaz;

Результат выполнения двух последних запросов одинаковый
Результат выполнения двух последних запросов одинаковый

В дальнейшем эти данные можно использовать как обычную таблицу.

Для нахождения значения, которых еще нет в таблице, воспользуемся оператором EXIST:

SELECT * FROM unnest(ARRAY['5-123', 'zak222', '65_1', '258a', '3456']) AS zakaz
WHERE not EXISTS (
SELECT 1 FROM order o WHERE o.nomer = zakaz

);

В результате получаем номер заказа, которого нет в таблице:

Номер заказа, которого еще нет в таблице
Номер заказа, которого еще нет в таблице

Такой же результат можно получить с помощью JOIN и not NULL.

3. В старых версиях Oracle основной способ не работает, но есть функция с коллекциями TABLE()— разворачивает коллекцию в строки таблицы. Из TABLE(sys.ODCIVarchar2List('A','B')) делает две строки: 'A''B':

SELECT
COLUMN_VALUE
FROM TABLE(sys.ODCIVarchar2List('a', 'b', 'c')); - для строк

SELECT
COLUMN_VALUE
FROM TABLE(sys.ODCINumberList(1, 2, 3));- для чисел

Для двух столбцов в Oracle:
select
objectschema id,
objectname name
from table(sys.ODCIObjectList(
sys.odciobject('5-123', 'Anna'),
sys.odciobject('zak222', 'Ivan'))
);
Если нужно больше столбцов, то уже нужно создать кастомный объектный тип.

4. В MS SQL работает основной способ, и начиная с версии SQL Server 2016, может быть применена функция STRING_SPLIT():
SELECT value FROM STRING_SPLIT('5-123,zak222,65_1,258a,3456', ',');

Функция STRING_SPLIT в MS SQL Server — это очень удобная встроенная функция, которая разбивает строку с разделителями на отдельные подстроки и возвращает их в виде таблицы.
На входе даете функции исходную строку и указываете разделитель, а выходе получаете таблицу из одной колонки с именем value.

5. В MySQL основной способ работает, но синтаксис чуть другой, перед значением нужно добавить ROW:
SELECT * FROM (
VALUES
ROW('5-123', 'Анна'),
ROW('zak222', 'Иван'),
ROW('65_1', 'Мария'),
ROW('258a', 'Петр'),
ROW('3456', 'Владимир')
) AS t(id, name);

Еще в MySQL для таких целей есть JSON_TABLE():
Для одного столбца удобнее так, особенно, если значения в виде списка:
SELECT jt.order_id
FROM JSON_TABLE(
'["5-123", "zak222", "65_1", "258a", "3456"]',
'$[*]' COLUMNS (order_id VARCHAR(20) PATH '$')
) jt

Возможно даже для нескольких столбцов будет так удобнее, если значения в виде списка:
SELECT orders.order_id, customers.customer_id
FROM JSON_TABLE(
'["5-123", "zak222", "65_1", "258a", "3456"]',
'$[*]' COLUMNS (
order_id VARCHAR(20) PATH '$',
row_num FOR ORDINALITY
)
) AS orders,
JSON_TABLE(
'["Анна", "Иван", "Мария", "Петр", "Владимир"]',
'$[*]' COLUMNS (
customer_id VARCHAR(20) PATH '$',
row_num FOR ORDINALITY
)
) AS customers
WHERE orders.row_num = customers.row_num;

Это уже для произвольного количества столбцов, но данные в JSON:
SELECT *
FROM JSON_TABLE(
'[{"order_id":"5-123","name":"Анна", "price": 123},
{"order_id":"zak222","name":"Иван", "price": 234},
{"order_id":"65_1","name":"Мария", "price": 345},
{"order_id":"258a","name":"Петр", "price": 456},
{"order_id":"3456","name":"Владимир", "price": 567}]',
'$[*]' COLUMNS (
order_id VARCHAR(20) PATH '$.order_id',
name VARCHAR(20) PATH '$.name',
price INT PATH '$.price')
) AS jt;

Через VALUES и JSON_TABLE — основные способы в MySQL для литеральных таблиц. JSON_TABLE - синтаксис мощный, но громоздкий и хорош только когда данные уже в JSON.

Выводы
Используя эти функции для создания литеральных таблиц, можно генерировать временные таблицы прямо в запросе без создания реальных объектов БД. Это особенно удобно, когда нет прав для создания объектов в БД, для тестовых данных, списков ID и задач с EXISTS/NOT EXISTS. Многие БД поддерживают основной способ через VALUES, но каждая СУБД еще имеет свой «родной» способ.