2016-11-04 81 views
1

我需要識別兩個表之間的差異。我曾看過sql query to return differences between two tables,但對於我推斷當前的SQL技能有點太不同了。兩個表之間的差異(使用sql) - >增量更改

表A是昨天拍攝快照的某個人羣的快照,其中每行是一個獨特的人物和某人的某些特徵。表B是24小時後拍攝的相同快照。在24小時內:

  1. 可能已添加新用戶。
  2. 昨天的人可能已被刪除。
  3. 來自昨天的人可能已經改變了(即,原始行在那裏,但是一個或多個列值已經改變)。

我的輸出應具備以下條件:

  1. 添加到每個新的人行
  2. 每個人的行刪除
  3. 每個誰改變了人行

我會感激任何想法。謝謝!

+2

請顯示錶格模式,「真實」的示例輸入和輸出數據,而不是措辭。 – OldProgrammer

+2

你應該用你正在使用的數據庫標記你的問題。 –

+0

一旦你得到這個工作,我會建議使它成爲寫入歷史表的觸發器,並自動記錄這些更改。 – levelonehuman

回答

0

前兩個部分很容易: 選擇'新',名稱從B不存在(從A選擇名稱,其中A.name = B.name) 聯合選擇'刪除',名稱來自A不存在(從B選擇名稱,其中B.name = A.name)

最後一個是您需要比較特徵的位置。他們中有多少人?你想列出改變了什麼或只改變了他們?

爲了說明的緣故,讓我們只說特徵是地址和電話#: 聯合選擇'電話',名字來自A,B,其中A.name = B.name和A.telephone!= B.電話 union select'Address',name from A,B where A.name = B.name and A.address!= B.地址

+0

謝謝。我用mathguy的迴應,但我非常感謝迴應的努力和時間。 –

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個結果集中。如果你放棄了工會,你最終會得到一個單獨的組合。

+0

謝謝。我用mathguy的迴應,但我非常感謝迴應的努力和時間。 –

0

我建議使用除了獲取更改的記錄。如果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) 
+0

謝謝。我用mathguy的迴應,但我非常感謝迴應的努力和時間。 –

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 
+0

謝謝。我用mathguy的迴應,但我非常感謝迴應的努力和時間。 –

+0

@TimClotworthy。 。 。你應該接受他的回答(即使我認爲這種方法更簡單)。 –

1

這種類型的問題是不使用非常簡單而有效的解決方案加入 (它甚至不使用兩個MINUS操作的結果的聯合) - 它只使用一個聯合和一個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個計數(因此每個只有一行!)的組或者是在一個表中而不在另一箇中的行。如果添加了一個人,那隻會顯示一行,並且標誌='新'。如果一個人被刪除了,你將只會得到一行,並帶有「舊」標誌。如果有更新,同樣PERSON_ID會出現兩次,但由於至少有一個字段不同,所以兩行(一個標記爲'new',另一個爲'old')將位於不同的組中,它們將通過HAVING過濾器,他們將在輸出中。

這與您要求的略有不同;你將得到舊的和新的更新信息,標記爲「舊」和「新」。你說你只想要其中的一個,但沒有說明哪一個。這會讓你們兩人(無論如何都更有意義),但是如果你真的只想要一個,可以在上面的查詢中輕鬆完成。

注 - 外select必須max(flag)而不是flag因爲flag不是GROUP BY列;但是它只是一行上的max(),所以無論如何這將是該行的flag

添加 - OP表示他希望只爲具有更新(更改,修改)數據的人員獲取「新」行。在這種情況下,下面顯示的方法會將標誌更改爲「更改」。

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 
+0

感謝mathguy。這很好。如此簡單和優雅。有一件事會改善它。正如你所說,我沒有說是否因爲「改變」我想要舊的,新的,或兩者兼而有之。其實,我的用例很簡單,因爲我只需要新的東西。你知道一種方法來修改這個,所以我可以得到一個「改變」的行,而不是舊的和新的行的配對?再次感謝! –

+0

我將編輯答案來補充。我會將標誌更改爲'changed',並只提供新數據的行。 (不知道你是否需要區分'new'和'changed/modified/updated'。) – mathguy

+0

我正在嘗試更新。我收到一個錯誤:ORA-00905:缺少關鍵字 00905. 00000 - 「缺少關鍵字」 *原因: *操作: 錯誤在行:1列:50.對應於「as flag」。有任何想法嗎? –

0

mathguy提供了成功的答案,我最初的問題。我問他一個修改(使其更好)。他提供了一個修訂版,但是在執行我的代碼時出現「缺少關鍵字」錯誤。這是我的代碼:

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; 
+0

LASTNAME後第三行末尾缺少逗號 – mathguy