两个表之间的差异(使用sql) - >增量变化

时间:2016-11-04 18:57:49

标签: sql oracle

我需要确定两个表之间的差异。我看过sql query to return differences between two tables,但对我来说,根据我目前的SQL技巧进行推断有点太不同了。

表A是昨天拍摄快照的特定人群的快照,其中每一行都是一个独特的人和关于此人的某些特征。表B是24小时后拍摄的相同快照。在24小时内:

  1. 可能已添加新人。
  2. 昨天的人可能已被删除。
  3. 昨天的人可能已经改变了(即原始行有,但是一个或多个列值已经改变)。
  4. 我的输出应该包含以下内容:

    1. 为每个新人添加一行
    2. 每个人移除一行
    3. 每个已更改的人
    4. 我很感激任何想法。谢谢!

6 个答案:

答案 0 :(得分:1)

这类问题有一个非常简单有效的解决方案,不使用连接(它甚至不使用两个MINUS操作结果的并集) - 它只使用一个union和GROUP BY操作。多年前,该解决方案是在AskTom的一个主题中开发的,令人惊讶的是它并没有被更广泛地知晓和使用。例如(但不仅仅):https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:24371552251735

在您的情况下,假设PERSON_ID上存在主键约束(这使得解决方案更简单):

select max(flag) as flag, PERSON_ID, first_name, last_name, (etc. - all the columns)
from   ( select 'old' as flag, t1.*
           from old_table t1
         union all
         select 'new' as flag, t2.*
           from new_table t2
       )
group by PERSON_ID, first_name, last_name, (etc.)
having  count(*) = 1
order by PERSON_ID  --     optional
;

如果对于PERSON_ID,两个表中的所有数据都相同,那么该组的计数将为2。所以它不会通过HAVING条件。唯一的计数为1的组(因此每个只有一行!)是一个表中的行,而不是另一个表中的行。如果添加了一个人,则只会显示一行,并且标记=' new'。如果某人被删除,您将只获得一行,标记为“旧”。如果有更新,则同一PERSON_ID会出现两次,但由于至少有一个字段不同,所以两行(一行带有标记' new'另一行带有' old&# 39;)将在不同的组中,它们将通过HAVING过滤器,它们将同时出现在输出中。

与您的要求略有不同;您将获得旧的和新的更新信息,标记为' old'和' new'。你说你只想要其中一个,但没有说明哪一个。这将给你们两个(这更有意义),但如果你真的只想要一个,它可以在上面的查询中轻松完成。

注意 - 外部select必须包含max(flag)而不是flag,因为flag不是GROUP BY列;但它只有max()一行,所以无论如何它都是flag

已添加 - OP表示他只想获得" new"具有更新(已更改,已修改)数据的人员的行。下面显示的方法会将标志更改为"已更改"在这种情况下。

with old_table ( person_id, first_name, last_name ) as (
       select 101, 'John', 'Smith' from dual union all
       select 102, 'Mary', 'Green' from dual union all
       select 103, 'July', 'Dobbs' from dual union all
       select 104, 'Will', 'Scott' from dual
     ),
     new_table ( person_id, first_name, last_name ) as (
       select 101, 'Joe' , 'Smith' from dual union all
       select 102, 'Mary', 'Green' from dual union all
       select 104, 'Will', 'Scott' from dual union all
       select 105, 'Andy', 'Brown' from dual
     )
-- end of test data; solution (SQL query) begins below this line
select case ct when 1 then flag else 'changed' end as flag,
       person_id, first_name, last_name
from   (
select max(flag) as flag, person_id, first_name, last_name,
       count(*) over (partition by person_id) as ct,
       row_number() over (partition by person_id order by max(flag)) as rn
from   ( select 'old' as flag, t1.*
           from old_table t1
         union all
         select 'new' as flag, t2.*
           from new_table t2
       )
group by person_id, first_name, last_name
having  count(*) = 1
)
where rn = 1
order by person_id   --  ORDER BY clause is optional
;

<强>输出

FLAG     PERSON_ID FIRS_NAME LAST_NAME
------- ---------- --------- ---------
changed        101 Joe       Smith
old            103 July      Dobbs
new            105 Andy      Brown

答案 1 :(得分:0)

前两部分很简单:     选择&#39; New&#39;,B中的名称不存在(从A中选择名称,其中A.name = B.name)     union select&#39; Removed&#39;,来自A的名称不存在(从B中选择名称,其中B.name = A.name)

最后一个是您需要比较特征的地方。他们中有多少人?您想列出已更改的内容还是仅列出已更改的内容?

为了论证,我们只说这些特征是地址和电话#:     union select&#39; Phone&#39;,名字来自A,B,其中A.name = B.name和A.telephone!= B.telephone     union select&#39; Address&#39;,名字来自A,B,其中A.name = B.name和A.address!= B.address

答案 2 :(得分:0)

注意:该问题目前尚未使用dbms标记。我使用sql-server,这就是我以前写的内容。另一个dbms可能会略有不同。

你可以按照以下方式做点什么:

select  *
from    TableA a
        left join TableB b on b.ID = a.ID
where   a.ID is null -- added since yesterday
union
select  *
from    TableA a
        left join TableB b on b.ID = a.ID
where   b.ID is null -- removed since yesterday
union
select  *
from    TableA a
        inner join TableB b on b.ID = a.ID -- restrict to records in both tables
where   a.SomeValue <> b.SomeValue
or      a.SomeOtherValue <> b.SomeOtherValue
--etc

每个选择处理预期输出的一部分。以这种方式,它们都被加入到1个结果集中。如果你删除联盟,你最终会为每个联盟设置一个单独的组合。

答案 3 :(得分:0)

我建议使用Except来获取更改的记录。如果db是sql server,则以下查询应该有效。

-- added since yesterday
SELECT B.*
FROM TableA A
LEFT Outer Join TableB B on B.ID = A.ID
WHERE A.ID IS NULL

UNION

-- removed since yesterday
SELECT A.*
FROM TableA A
LEFT OUTER JOIN TableB B on B.ID = A.ID
WHERE B.ID IS NULL 

UNION

-- Those changed with values from yesterdady
SELECT B.* FROM TableB B WHERE EXISTS(SELECT A.ID FROM TableA A WHERE A.ID = B.ID) 
EXCEPT
SELECT A.* FROM TableA A WHERE EXISTS(SELECT B.ID FROM TableB B WHERE B.ID = A.ID)

答案 4 :(得分:0)

假设您对每个人都有一个唯一的id,您可以使用full outer join

select coalesce(ty.customerid, tt.customerid) as customerid,
      (case when ty.customerid is null then 'New'
            when tt.customerid is null then 'Removed'
            else 'Modified'
       end) as status
from tyesterday ty full outer join
     ttoday tt
     on ty.customerid= tt.customerid
where ty.customerid is null or
      tt.customerid is null or
      (tt.col1 <> ty.col1 or tt.col2 <> ty.col2 or . . . );  -- may need to take `NULL`s into account

答案 5 :(得分:0)

mathguy为我最初的问题提供了一个成功的答案。我请他修改(使其更好)。他提供了一个修订版,但我得到了一个缺少关键字&#34;执行我的代码时出错。这是我的代码:

select case when ct = 1 then flag else 'changed' as flag, PERSON_ID, FIRSTNAME, LASTNAME
from (
   select max(flag), PERSON_ID, FIRSTNAME, LASTNAME
          count() over (partition by PERSON_ID) as ct,
          row_number() over (partition by PERSON_ID 
                             order by case when flag = 'new' then 0 end) as rn
   from   ( select 'old' as flag, t1.*
              from YESTERDAY_TABLE t1
            union all
            select 'new' as flag, t2.*
              from TODAY_TABLE t2
          )
   group by PERSON_ID, FIRSTNAME, LASTNAME
          having  count(*) = 1
 )
where rn = 1
order by PERSON_ID;