Это может случиться очень легко. Вы адаптируете таблицу, добавляя новый столбец:
ALTER TABLE payments ADD code NUMBER(3);
Вы продолжаете реализовывать свою бизнес-логику — абсолютно никаких проблем. Но затем, позже (возможно, в производстве), некоторые пакетные задания не выполняются, потому что они делают серьезные предположения о типах данных. А именно, предполагается, что две таблицы paymentsи payments_archiveимеют одинаковый тип строки:
CREATE TABLE payments
(
id NUMBER(18) NOT NULL,
account_id NUMBER(18) NOT NULL,
value_date DATE,
amount NUMBER(25, 2) NOT NULL
);
CREATE TABLE payments_archive
(
id NUMBER(18) NOT NULL,
account_id NUMBER(18) NOT NULL,
value_date DATE,
amount NUMBER(25, 2) NOT NULL
);
Имея один и тот же тип строки, вы можете просто переместить строку из одной таблицы в другую, например, используя такой запрос:
INSERT INTO payments_archive SELECT * FROM payments WHERE value_date < SYSDATE - 30;
(не то, чтобы использование приведенного выше синтаксиса было хорошей идеей в целом, на самом деле это плохая идея. но вы поняли)
То, что вы получаете сейчас, это:
ORA-00913: too many values
Исправление очевидно, но, вероятно, бедная душа, которая должна это исправить, — это не вы, а кто-то еще, кто должен выяснить среди, возможно, сотен столбцов, которые не совпадают. Вот как (в Oracle):
Используйте PIVOT для сравнения двух таблиц!
Конечно, вы можете не использовать PIVOTи просто выбрать все столбцы из любой таблицы из представлений словаря:
SELECT table_name, column_name FROM all_tab_cols WHERE table_name LIKE 'PAYMENTS%'
Это даст следующий результат:
TABLE_NAME COLUMN_NAME ------------------ --------------- PAYMENTS ID PAYMENTS ACCOUNT_ID PAYMENTS VALUE_DATE PAYMENTS AMOUNT PAYMENTS CODE PAYMENTS_ARCHIVE ID PAYMENTS_ARCHIVE ACCOUNT_ID PAYMENTS_ARCHIVE VALUE_DATE PAYMENTS_ARCHIVE AMOUNT
Не очень читабельно. Конечно, вы можете использовать операции set и apply INTERSECTи MINUS( EXCEPT), чтобы отфильтровать совпадающие значения. Но намного лучше
SELECT *
FROM (
SELECT
table_name,
column_name
FROM all_tab_cols
WHERE table_name LIKE 'PAYMENTS%'
)
PIVOT (
COUNT(*) AS cnt
FOR (table_name)
IN (
'PAYMENTS' AS payments,
'PAYMENTS_ARCHIVE' AS payments_archive
)
) t;
И вышесказанное теперь производит:
COLUMN_NAME PAYMENTS_CNT PAYMENTS_ARCHIVE_CNT ------------ ------------ -------------------- CODE 1 0 ACCOUNT_ID 1 1 ID 1 1 VALUE_DATE 1 1 AMOUNT 1 1
Теперь очень легко идентифицировать столбец, который отсутствует в PAYMENTS_ARCHIVEтаблице. Как видите, результат исходного запроса дает одну строку на столбец AND на таблицу. Мы взяли этот результат и повернули его «FOR» для имени таблицы, так что теперь мы получим только одну строку на столбец
Как читать PIVOT?
Это просто. Комментарии встроены:
SELECT *
-- This is the table that we're pivoting. Note that
-- we select only the minimum to prevent side-effects
FROM (
SELECT
table_name,
column_name
FROM all_tab_cols
WHERE table_name LIKE 'PAYMENTS%'
)
-- PIVOT is a keyword that is applied to the above
-- table. It generates a new table, similar to JOIN
PIVOT (
-- This is the aggregated value that we want to
-- produce for each pivoted value
COUNT(*) AS available
-- This is the source of the values that we want to
-- pivot
FOR (table_name)
-- These are the values that we accept as pivot
-- columns. The columns names are produced from
-- these values concatenated with the corresponding
-- aggregate function name
IN (
'PAYMENTS' AS payments,
'PAYMENTS_ARCHIVE' AS payments_archive
)
) t;
Вот и все. Не так сложно, правда?
Хорошая вещь в этом синтаксисе состоит в том, что мы можем сгенерировать столько дополнительных столбцов, сколько захотим, очень легко:
SELECT *
FROM (
SELECT
table_name,
column_name,
cast(data_type as varchar(6)) data_type
FROM all_tab_cols
WHERE table_name LIKE 'PAYMENTS%'
)
PIVOT (
COUNT(*) AS cnt,
MAX(data_type) AS type -- new function here
FOR (table_name)
IN (
'PAYMENTS' AS p,
'PAYMENTS_ARCHIVE' AS a
)
) t;
… Производящий (после дополнительного ошибочного DDL)…
COLUMN_NAME P_CNT P_TYPE A_CNT A_TYPE ----------- ---------- ------ ---------- ------ CODE 1 NUMBER 0 ACCOUNT_ID 1 NUMBER 1 NUMBER ID 1 NUMBER 1 NUMBER VALUE_DATE 1 DATE 1 TIMESTAMP AMOUNT 1 NUMBER 1 NUMBER
Таким образом, мы можем обнаружить еще больше недостатков между различными типами строк таблиц. В приведенном выше примере мы использовали MAX(), потому что мы должны предоставить функцию агрегирования, даже если каждый поворотный столбец соответствует ровно одной строке в нашем примере — но это не обязательно.
Что если я не использую Oracle?
SQL Server также поддерживает PIVOT, но другие базы данных не поддерживают. Вы всегда можете подражать, PIVOTиспользуя GROUP BYи CASE. Следующее утверждение эквивалентно предыдущему:
SELECT
t.column_name,
count(CASE table_name
WHEN 'PAYMENTS' THEN 1 END) p_cnt,
max (CASE table_name
WHEN 'PAYMENTS' THEN data_type END) p_type,
count(CASE table_name
WHEN 'PAYMENTS_ARCHIVE' THEN 1 END) a_cnt,
max (CASE table_name
WHEN 'PAYMENTS_ARCHIVE' THEN data_type END) a_type
FROM (
SELECT
table_name,
column_name,
data_type
FROM all_tab_cols
WHERE table_name LIKE 'PAYMENTS%'
) t
GROUP BY
t.column_name;
Этот запрос теперь будет давать тот же результат для всех других баз данных.
Разве это не …?
Да это так! Вышеупомянутое использование агрегатных функций в сочетании с CASEможет быть сокращено еще больше, используя стандартное FILTERпредложение SQL , о котором мы недавно писали в блоге .
Итак, в PostgreSQL вы можете написать следующий запрос:
SELECT
t.column_name,
count(table_name)
FILTER (WHERE table_name = 'payments') p_cnt,
max(data_type)
FILTER (WHERE table_name = 'payments') p_type,
count(table_name)
FILTER (WHERE table_name = 'payments_archive') a_cnt,
max(data_type)
FILTER (WHERE table_name = 'payments_archive') a_type
FROM (
SELECT
table_name,
column_name,
data_type
FROM information_schema.columns
WHERE table_name LIKE 'payments%'
) t
GROUP BY
t.column_name;
дальнейшее чтение
В восторге? Да. В различных базах данных есть более удивительные возможности SQL. Читайте о:
- Awesome PostgreSQL 9.4 / SQL: 2003 FILTER для агрегатных функций
- Все еще используете Windows 3.1? Так почему же придерживаться SQL-92?
- Драгоценный камень SQL, о котором вы еще не знали: агрегатная функция EVERY ()
- Удивительный трюк SQL: ограничения на представления
- CUME_DIST (), малоизвестный гем SQL