查找表中两行之间的差异。甲骨文

时间:2018-03-28 13:22:38

标签: sql oracle

我有一个包含30多个字段的表格,我想获得字段名称和不同的值。

例如在表X中我们有

ID |  City      | State | Zip   | Segment_One | Segment_Two | ....
1  |            |  NY   | 14228 |   X71       |   5         |
2  |  JamesTown |  NY   | 14845 |   X72       |   5         |

查询应该返回字段名称和数据的差异。

ID | City      | Zip      | Segment_One
1  |           | 14228    |  X71
2  | JamesTown | 14845    |  X72

我可以使用一个查询来始终将行限制为两行....所以我想我总会在两行之间进行比较,但是我如何找到字段名称的差异并获取那些值的值字段?

编辑:这是我尝试过的,但它似乎会返回状态,即使它是不同的。

select * from 
(select a.* from X a where ROWNUM = 1 order by last_updt_date desc ) 
minus
select * from
(select b.* from X b where ROWNUM = 2 order by last_updt_date desc );

结果:

ID  |  City  |  State  | Zip   |  Segment_One |  Segment_Two |
1   |        |   NY    | 14228 |  X71         |     5        | ...

这将返回行中的所有字段,但我只想要差异字段和值。对于第2行,这不会返回。如果我反转减号,那么它将不返回任何内容。

6 个答案:

答案 0 :(得分:1)

假设有两行输入且你可以标记第1行和第2行(例如通过row_number()分析函数),这应该可以工作:

WITH your_table AS (SELECT 1 id, NULL city, 'NY' state, 14228 zip, 'X71' segement_one, 5 segment_two from dual UNION ALL
                    SELECT 2 id, 'JamesTown' city, 'NY' state, 14845 zip, 'X71' segement_one, 5 segment_two from dual)
SELECT ID,
       CASE WHEN ID = 1 AND NVL(city, '{null}') = LEAD(NVL(city, '{null}')) OVER (ORDER BY ID) THEN NULL
            WHEN ID = 2  AND NVL(city, '{null}') = LAG(NVL(city, '{null}')) OVER (ORDER BY ID) THEN NULL
            ELSE city
       END city,
       CASE WHEN ID = 1 AND NVL(state, '{null}') = LEAD(NVL(state, '{null}')) OVER (ORDER BY ID) THEN NULL
            WHEN ID = 2  AND NVL(state, '{null}') = LAG(NVL(state, '{null}')) OVER (ORDER BY ID) THEN NULL
            ELSE state
       END state,
       CASE WHEN ID = 1 AND NVL(zip, -99999999999) = LEAD(NVL(zip, -99999999999)) OVER (ORDER BY ID) THEN NULL
            WHEN ID = 2  AND NVL(zip, -99999999999) = LAG(NVL(zip, -99999999999)) OVER (ORDER BY ID) THEN NULL
            ELSE zip
       END zip,
       CASE WHEN ID = 1 AND NVL(segement_one, '{null}') = LEAD(NVL(segement_one, '{null}')) OVER (ORDER BY ID) THEN NULL
            WHEN ID = 2  AND NVL(segement_one, '{null}') = LAG(NVL(segement_one, '{null}')) OVER (ORDER BY ID) THEN NULL
            ELSE segement_one
       END segement_one,
       CASE WHEN ID = 1 AND NVL(segment_two, -99999999999) = LEAD(NVL(segment_two, -99999999999)) OVER (ORDER BY ID) THEN NULL
            WHEN ID = 2  AND NVL(segment_two, -99999999999) = LAG(NVL(segment_two, -99999999999)) OVER (ORDER BY ID) THEN NULL
            ELSE segment_two
       END segment_two
FROM  your_table;

        ID CITY      STATE        ZIP SEGEMENT_ONE SEGMENT_TWO
---------- --------- ----- ---------- ------------ -----------
         1                      14228              
         2 JamesTown            14845              

请注意,这只会返回不匹配的列中的值,并且匹配的列中没有任何内容;您将无法仅选择不匹配的列。

但是,你可以使用case语句来输出你可以运行的select语句,它只有受影响的列,或者你可以输出一个包含不同列的列表的字符串,如果这是你所追求的;从您的帖子中可以清楚地了解您将要执行的查询结果。

答案 1 :(得分:1)

SQL不能像那样工作,因为结果集中总是必须有已知数量的列。你能做的是:

with t as
 (select '1' id, ''          city, 'NY' state, '14228' Zip, 'X71' Segment_One, 5 Segment_Two from dual
  UNION ALL
  select '2' id, 'JamesTown' city, 'NY' state, '14845' Zip, 'X72' Segment_One, 5 Segment_Two from dual
 )
select *
from
 (select decode(min(id),    max(id),    '',  'ID')    as name, min(id)    as min_value, max(id)    as max_value from t
  UNION ALL
  select decode(min(city),  max(city),  '',  'CITY')  as name, min(city)  as min_value, max(city)  as max_value from t
  UNION ALL
  select decode(min(state), max(state), '',  'STATE') as name, min(state) as min_value, max(state) as max_value from t
  UNION ALL
  select decode(min(zip),   max(zip),   '',  'ZIP')   as name, min(zip)   as min_value, max(zip)   as max_value from t
 )
where name is not null
;



NAME  MIN_VALUE MAX_VALUE
----- --------- ---------
ID    1         2
ZIP   14228     14845

答案 2 :(得分:1)

您可以使用PL / SQL块和动态CURSOR尝试这样的事情。请注意,我在这里 使用ID = 1和2作为变量,您应该知道并从某处传递。

DBMS_SQL.RETURN_RESULT(12c及以上)用于显示动态构造的游标的输出。或者,您可以使用PRINT命令从REFCURSOR获取并显示o / p。

SET serveroutput ON
DECLARE
  v_ref SYS_REFCURSOR;
  id1 X.ID%TYPE := 1;
  id2 X.ID%TYPE := 2;
  v_col  VARCHAR2(32);
  v_cols VARCHAR2(1000);
BEGIN
  FOR r IN
  ( SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = 'X'
  )
  LOOP
    EXECUTE IMMEDIATE ' 
SELECT 
CASE                 
WHEN a.'||r.column_name|| ' = ' || 'b.'|| r.column_name ||'                               
THEN a.'||r.column_name || ' END                     
FROM                           
X  a                
CROSS JOIN X b WHERE 
a.ID = :Id1 AND b.ID = :Id2' INTO v_col USING id1,id2 ;
    v_cols := v_cols ||
    CASE
    WHEN v_col IS NULL THEN
      ','||r.column_name
    END;
  END LOOP;
  v_cols := TRIM(BOTH ',' FROM v_cols);
  OPEN v_ref FOR 'select ' || v_cols || '  FROM X WHERE ID =  '||id1||'                
UNION ALL   select ' || v_cols || '  FROM X WHERE ID =  '||id2 ;
  DBMS_SQL.RETURN_RESULT(v_ref);
END;
/
ResultSet #1


ID                 CITY      ZIP          SEGMENT_ONE 
------------------ --------- ------------ ----------- 
1                            14228        X71         
2                  JamesTown 14845        X72         


PL/SQL procedure successfully completed.

答案 3 :(得分:1)

当我之前做过类似的事情时,我使用字典表为它生成sql。不确定这是否适合你。像这样的东西

select 'select '''||column_name||''' col_name, x1.'||column_name||', x2.'||column_name||' from X x1 join X x2 on x1.id=1 and x2.id=2 where x1.'||column_name||' != x2.'||column_name||';'
from dba_tab_columns where table_name='X';

如果你运行它,它将生成30个sql语句,每列一个,然后你可以运行所有这些,看看你得到了什么。如果列可以为空,则可能需要添加NVL

答案 4 :(得分:0)

您是否尝试过使用MINUS运算符 这将返回2个记录集/元组之间不同的所有记录 您必须使用案例陈述来确定哪些列不同。

答案 5 :(得分:0)

Kaushik Nayak的回答挽救了我的生活!
https://stackoverflow.com/a/49538691/14163316

我的帐户是新帐户,所以我无法写α评论,而是α完整答案。

在我的情况下,原始sql联合有一些额外的行,因此我不得不将其反转。 这是从原始答案中修改后的sql,它很漂亮。
我在代码块中留下了一些注释,以显示您必须更改的地方。

define tableName = 'TABLE';     -- Change to your table name
define idName = 'ID';           -- Change to your ID name if its not ID
 
SET serveroutput ON
DECLARE
    id1 number:= 1;             -- Change to the ID of the first row you want to compare
    id2 number:= 2;             -- Change to the ID of the second row you want to compare
    v_ref SYS_REFCURSOR;
    v_col  VARCHAR2(32);
    v_cols VARCHAR2(3000);
BEGIN
    FOR r IN
        ( SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = '&tableName'
        )
        LOOP
            EXECUTE IMMEDIATE '
SELECT
CASE
WHEN a.'||r.column_name|| ' != ' || 'b.'|| r.column_name ||'
THEN a.'||r.column_name || ' END
FROM
&tableName  a
CROSS JOIN &tableName b WHERE
a.&idName = :id1 AND b.&idName = :id2' INTO v_col USING id1,id2 ;
            v_cols := v_cols ||
                      CASE
                          WHEN v_col IS NOT NULL THEN
                                  ','||r.column_name
                          END;
        END LOOP;
    v_cols := TRIM(BOTH ',' FROM v_cols);
    OPEN v_ref FOR 'select ' || v_cols || '  FROM &tableName WHERE &idName =  '||id1||'
UNION ALL select ' || v_cols || '  FROM &tableName WHERE &idName =  '||id2 ;
    DBMS_SQL.RETURN_RESULT(v_ref);
END;
/

再次感谢Kaushik Nayak的原始答案!