我需要确定两个表之间的差异。我看过sql query to return differences between two tables,但对我来说,根据我目前的SQL技巧进行推断有点太不同了。
表A是昨天拍摄快照的特定人群的快照,其中每一行都是一个独特的人和关于此人的某些特征。表B是24小时后拍摄的相同快照。在24小时内:
我的输出应该包含以下内容:
我很感激任何想法。谢谢!
答案 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;