Это может случиться очень легко. Вы адаптируете таблицу, добавляя новый столбец:
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