Как сравнить две таблицы в Oracle, когда количество столбцов в таблицах отличается

#oracle

#Oracle

Вопрос:

В моей базе данных есть две таблицы, это EXP1 и EXP2. Я попытался с помощью приведенного ниже запроса, этот запрос работает, когда обе таблицы имеют одинаковое количество столбцов, но моя таблица EXP1 содержит 1000 столбцов, а EXP2 — 1000 4.

 select *
from 
(
    (select * from exp1
     minus 
     select * from exp2)
    union all
    (select * from exp2
     minus
     select * from exp1)
);
  

Комментарии:

1. Чувак, используй список столбцов (общую часть всех столбцов этих двух таблиц) вместо *

2. насколько мне известно, таблицы должны быть совместимы с объединением.. это означает тот же набор столбцов.

3. Что неясно в сообщении об ошибке?

4. если в одной моей таблице 1000 столбцов, а в другой 1000 4.. мне будет сложно добавить все совпадающие имена столбцов в запросе itself…..is есть какой-либо запрос, чтобы исключить те четыре столбца, которые отличаются.?……….

5. Вы можете запросить таблицу USER_TAB_COLUMNS или all_tab_columns для столбцов таблицы

Ответ №1:

ВСТУПЛЕНИЕ: Ниже я показываю, как можно сделать «вручную» то, что инструменты (например, SQL Developer) могут сделать намного быстрее и намного лучше. Мой интерес к этому (и ваш!) двоякий: изучить и использовать некоторые идеи, которые могут помочь во многих других проблемах; и понять, что эти инструменты делают под капотом в первую очередь.

ОК. Предположим, у вас есть две таблицы, и у них много общих столбцов (возможно, не в том же порядке), и несколько столбцов могут отличаться — в одной таблице может быть несколько столбцов, но их нет в другой. Сначала вы хотите иметь возможность просматривать только общие столбцы.

Затем предположим, что это сделано. Теперь то, что осталось от двух таблиц, имеет много общих строк, но есть и несколько отличающихся. Строка может существовать в одной таблице, но не в другой, или две строки, по одной из каждой таблицы, могут быть очень похожи, но они могут отличаться только одним или небольшим количеством значений столбца. Логически это все еще одна строка в первой таблице, но не во второй, а другая строка только во второй таблице, но не в первой. Однако, допустим, обе таблицы имеют один и тот же столбец PK — тогда у вас может быть одинаковое значение PK в обеих таблицах, но по крайней мере один из ДРУГИХ столбцов имеет разные значения для этого значения PK в двух таблицах. И вы хотите найти эти различия между двумя таблицами.

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

Когда вы переходите к сравнению строк в двух таблицах на последнем шаге, (A minus B) union all (B minus A) работает, но не очень эффективно. Каждая таблица читается дважды, и minus это дорогостоящий оператор. Более эффективное решение, которое я иллюстрирую ниже, обсуждалось в длинной теме на AskTom несколько лет назад. А именно: соберите все строки из обеих таблиц (с union all ), сгруппируйте по всем столбцам и игнорируйте группы, количество которых равно 2. Это означает строки, которые были найдены в обеих таблицах, поэтому они являются дубликатами в union all ! На самом деле, вы увидите небольшой дополнительный трюк, позволяющий определить, из какой таблицы взяты «недублированные» строки. Добавьте столбец для «имя_таблицы» и в окончательном выборе, после группировки и сохранения групп с count(*) = 1 , выберите max(table_name) . Вам нужна агрегирующая функция (например max() ), потому что вы группируете, но для этих строк каждая группа имеет только одну строку, так что max() на самом деле это просто название таблицы.

Прелесть этого подхода в том, что его можно использовать и для определения общих столбцов! В этом случае мы сравним строки из USER_TAB_COLS представления — мы выбираем имена столбцов, которые отображаются в любой из таблиц, и сохраняем только дублирующие имена столбцов (таким образом, имена столбцов отображаются в обеих таблицах). В этой части решения я также извлекаю column_id , который используется для упорядочивания столбцов. Не волнуйтесь, если вы не знакомы с keep (dense_rank first...) — на самом деле это не так сложно, но и не так важно.

Сначала давайте настроим тестовый пример. Я копирую EMP таблицу из SCOTT схемы в свою собственную схему, реплицирую ее (так что теперь у меня есть две копии с именами EMP1 и EMP2 ) и немного изменяю их. Я удаляю разные столбцы из каждой, я удаляю несколько (разных) строк из каждой и я изменяю одну зарплату в одной таблице. Я не буду показывать результирующие (немного отличающиеся) таблицы, но если вы следите за ними, просто select * из обеих и сравните их, прежде чем продолжить чтение.

Создайте таблицы:

 create table EMP1 as select * from scott.emp;
Table EMP1 created.

select * from EMP1;

EMPNO ENAME      JOB        MGR HIREDATE              SAL   COMM  DEPTNO
----- ---------- --------- ---- ------------------- ----- ------ -------
 7369 SMITH      CLERK     7902 1980-12-17 00:00:00   800             20
 7499 ALLEN      SALESMAN  7698 1981-02-20 00:00:00  1600    300      30
 7521 WARD       SALESMAN  7698 1981-02-22 00:00:00  1250    500      30
 7566 JONES      MANAGER   7839 1981-04-02 00:00:00  2975             20
 7654 MARTIN     SALESMAN  7698 1981-09-28 00:00:00  1250   1400      30
 7698 BLAKE      MANAGER   7839 1981-05-01 00:00:00  2850             30
 7782 CLARK      MANAGER   7839 1981-06-09 00:00:00  2450             10
 7788 SCOTT      ANALYST   7566 1987-04-19 00:00:00  3000             20
 7839 KING       PRESIDENT      1981-11-17 00:00:00  5000             10
 7844 TURNER     SALESMAN  7698 1981-09-08 00:00:00  1500      0      30
 7876 ADAMS      CLERK     7788 1987-05-23 00:00:00  1100             20
 7900 JAMES      CLERK     7698 1981-12-03 00:00:00   950             30
 7902 FORD       ANALYST   7566 1981-12-03 00:00:00  3000             20
 7934 MILLER     CLERK     7782 1982-01-23 00:00:00  1300             10
  

Слегка измените их:

 create table EMP2 as select * from EMP1;
Table EMP2 created.

alter table emp1 drop column hiredate;
Table EMP1 altered.

alter table emp2 drop column comm;
Table EMP2 altered.

delete from EMP1 where ename like 'A%';
2 rows deleted;

delete from EMP2 where sal >= 3000;
3 rows deleted

update EMP2 set sal = 2950 where empno = 7698;
1 row updated

commit;
  

На этом этапе вам не мешало бы select * from EMP1; and select * from EMP2; и сравнить.

Теперь давайте выясним, какие столбцы у двух таблиц остались общими.

 select column_name, 
       min(column_id) keep(dense_rank first order by table_name) as col_id
from   user_tab_cols
where  table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
order by col_id;

COLUMN_NAME COL_ID
----------- ------
EMPNO            1
ENAME            2
JOB              3
MGR              4
SAL              5
DEPTNO           7

 6 rows selected 
  

Отлично, теперь мы можем сравнить две таблицы, но только после того, как мы «спроецируем» их только по общим столбцам.

 select max(table_name) as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
from (
       select 'EMP1' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP1
       union all
       select 'EMP2' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP2
     )
group by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
having count(*) = 1
order by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO, table_name;

TABLE_NAME EMPNO ENAME      JOB          MGR    SAL   DEPTNO
---------- ----- ---------- --------- ------ ------ --------
EMP2        7499 ALLEN      SALESMAN    7698   1600       30
EMP1        7698 BLAKE      MANAGER     7839   2850       30
EMP2        7698 BLAKE      MANAGER     7839   2950       30
EMP1        7788 SCOTT      ANALYST     7566   3000       20
EMP1        7839 KING       PRESIDENT          5000       10
EMP2        7876 ADAMS      CLERK       7788   1100       20
EMP1        7902 FORD       ANALYST     7566   3000       20

 7 rows selected 
  

Результат — это в значительной степени то, что нам было нужно. Обратите внимание на первый столбец, который сообщает нам, откуда берется «непарная» строка; и обратите внимание на БЛЕЙКА, у которого разная зарплата в двух таблицах (и первый столбец помогает нам увидеть, какая зарплата у него в какой таблице).

Пока это выглядит идеально, но что делать, когда у вас 1000 столбцов? Вы могли бы объединить это на C или Java и т.д., Используя результат приведенного выше запроса «общие столбцы», или вы могли бы сделать все это в Oracle с помощью dynamic SQL.

Насколько я знаю, в Oracle нет установленного ограничения на длину текста инструкции SQL; в документации говорится «Ограничение на то, какой длины может быть инструкция SQL, зависит от многих факторов, включая конфигурацию базы данных, дисковое пространство и память» (и, вероятно, от вашей версии Oracle, которую они не упомянули). В любом случае, это будет более 4000 символов, поэтому нам нужно работать с CLOB. В частности, мы не можем использовать listagg() — нам нужен обходной путь. Я использую xmlagg() ниже. Затем в документации говорится, что если вы объединяете текст и хотя бы один операнд имеет значение CLOB, результатом будет CLOB ; если у вас это не сработает, возможно, вам придется обернуть меньшие фрагменты текста внутри to_clob() . Приведенный ниже запрос «dynamic SQL» выдаст полный текст запроса, который я использовал выше; вы просто скопируете его, вставите обратно в свой интерфейс и выполните. Возможно, вам придется удалить заключающие двойные кавычки или что-то подобное, в зависимости от вашего интерфейса и настроек.

First here is how we can create a (potentially very long) string, the list of common column names, which is repeated five times in the final query — just look again at the «final query» we used to compare the two tables above.

 with
     common_cols ( column_name, col_id ) as (
       select column_name, 
              min(column_id) keep(dense_rank first order by table_name) as col_id
       from   user_tab_cols
       where  table_name in ('EMP1', 'EMP2')
       group by column_name
       having count(*) = 2
     ),
     col_string ( str ) as (
       select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id)
                                                    as clob), ', ') from common_cols
     ) 
select * from col_string;

STR
-----------------------------------
EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
  

И, наконец, полный динамический SQL-запрос (результат — это именно тот запрос, который я использовал для сравнения EMP1 и EMP2 для их общих столбцов ранее):

 with
     common_cols ( column_name, col_id ) as (
       select column_name, 
              min(column_id) keep(dense_rank first order by table_name) as col_id
       from   user_tab_cols
       where  table_name in ('EMP1', 'EMP2')
       group by column_name
       having count(*) = 2
     ),
     col_string ( str ) as (
       select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id) 
                                                    as clob), ', ') from common_cols
     )
select 'select max(table_name) as table_name, ' || str                 || chr(10) ||
       'from ('                                                        || chr(10) ||
       '       select ''EMP1'' as table_name, ' || str || ' from EMP1' || chr(10) ||
       '       union all'                                              || chr(10) ||
       '       select ''EMP2'' as table_name, ' || str || ' from EMP2' || chr(10) ||
       '     )'                                                        || chr(10) ||
       'group by ' || str                                              || chr(10) ||
       'having count(*) = 1'                                           || chr(10) ||
       'order by ' || str || ', table_name;'  as comp_sql_str
from   col_string;