2017-01-27 58 views
0

如何創建一個新列,以返回表中每行內不同值的數量?例如,統計每行不同值的數量(SQL)

ID Description Pay1 Pay2 Pay3 #UniquePays  
1 asdf1   10  20  10  2 
2 asdf2   0  10  20  3 
3 asdf3   100  100 100  1 
4 asdf4     0  10  3 

該查詢可能會返回> 100萬行,因此它需要有點高效。總共有8個「支付」列,它們可以是NULL或整數。還要注意,'0'應該被計數與NULL不同。

我已經能夠迄今完成的最(我才意識到是不是甚至精確)計數每行中收費項目總數:

nvl(length(length(Pay1)),0) 
+nvl(length(length(Pay2)),0) 
+nvl(length(length(Pay3)),0) "NumPays" 

典型的行只填充8列中的4個,其餘爲空,支付列中的最大整數是'999'(因此長度 - 長度轉換嘗試..)

我的SQL技能是原始的,但任何幫助表示!

+0

是您的電流輸出,做一個支點查詢的結果呢?如果是這樣,從基礎數據開始將更容易。否則,它看起來像你可能有非規範化的數據。 –

+0

你的桌子設計不好。你不應該有8列的薪酬,你應該爲每個薪酬插入一行。 –

+0

兩個NULL是否被認爲是「相同的」? – mathguy

回答

0

拆分出來的每個值到它自己的行(像它應該已經被存儲在第1名),然後union然後起來(因爲union丟棄重複)只計算行數:

select id, description, count(*) unique_pays from (
    select id, description, nvl(pay1, -1) from mytable 
    union select id, description, nvl(pay2, -1) from mytable 
    union select id, description, nvl(pay3, -1) from mytable 
    union select id, description, nvl(pay4, -1) from mytable 
    union select id, description, nvl(pay5, -1) from mytable 
    union select id, description, nvl(pay6, -1) from mytable 
    union select id, description, nvl(pay7, -1) from mytable 
    union select id, description, nvl(pay8, -1) from mytable 
) x 
group by id, description 

我將空值更改爲-1,以便他們可以乾淨地參與重複數據刪除。

+1

這將讀取一個有數百萬行八次的表格。 OP很關心效率...... – mathguy

+2

這太糟糕了OP在設計他的桌子時並不關心效率...... –

+0

@mathguy然後OP可能會修復他的模式。你的觀點是有價值的,但是「數百萬行」這些日子並不是那麼多(百萬人越來越多)。無論如何,這可能會執行正常 - 讓我們看看OP選擇哪個答案。 – Bohemian

-1

您可以在插入觸發存儲過程寫來計算的獨特價值總數爲唯一列各插入語句和更新。

2

這是一個比較簡單的方法:

CREATE TYPE number_list AS TABLE OF NUMBER; 

with t (ID, Description, Pay1, Pay2, Pay3) as (
    select 1, 'asdf1', 10, 20, 10 from dual 
    union all select 2, 'asdf2', 0, 10, 20 from dual 
    union all select 3, 'asdf3', 100, 100, 100 from dual 
    union all select 4, 'asdf4', null, 0, 10 from dual 
) 
SELECT id, 
     description, 
     pay1, 
     pay2, 
     pay3, 
     (SELECT COUNT (DISTINCT NVL (TO_CHAR (COLUMN_VALUE), '#NULL#')) 
     FROM TABLE (number_list (pay1, pay2, pay3))) uniquepays 
FROM t; 

     ID DESCR  PAY1  PAY2  PAY3 UNIQUEPAYS 
---------- ----- ---------- ---------- ---------- ---------- 
     1 asdf1   10   20   10   2 
     2 asdf2   0   10   20   3 
     3 asdf3  100  100  100   1 
     4 asdf4      0   10   3 
0

的解決辦法是:

  1. 開始與您最初的表,而列#uniquePays
  2. Unpivot your table。

從這

ID Description Pay1 Pay2 Pay3 
1 asdf1   10  20  10 

使此:

ID seq Description Pay 
1 1 asdf1  10 
1 2 asdf1  20 
1 3 asdf1  10 
  • 從非透視表中,運行一個SELECT COUNT(DISTINCT付款)
  • 重新旋轉表格,添加COUNT(DISTINCT Pay)。
  • 這樣做,還是你需要一個示例腳本?最近我已經發布了很多關於旋轉和不旋轉的東西......似乎是一個流行的需求: - ]

    馬爾科理智

    +0

    Count忽略空值 - 對於ID 4,這會報告2個唯一值而不是3?對於大型餐桌來說,這也可能不是很有效。 –

    +0

    我有一種感覺,樞軸就是這個方向,這對我來說很有意義。示例腳本將有助於讓我走上正軌,但無論哪種方式,我都會磨合我的旋轉技能!謝謝! – KevinT

    4

    如果你有,或可以創建,數字的用戶定義的表,你可以使用創建一個集合,使用the set function擺脫重複的,然後用the cardinality function計算剩餘價值:

    cardinality(set(t_num(pay1, pay2, pay3))) as uniquepays 
    

    要包括你列的所有八個,只是添加到列表傳遞給tnum()構造額外的列名。

    cardinality(set(t_num(pay1, pay2, pay3, pay4, pay5, pay6, pay7, pay8))) as uniquepays 
    

    演示與樣品表作爲CTE產生:

    create type t_num as table of number 
    /
    
    with t (ID, Description, Pay1, Pay2, Pay3) as (
        select 1, 'asdf1', 10, 20, 10 from dual 
        union all select 2, 'asdf2', 0, 10, 20 from dual 
        union all select 3, 'asdf3', 100, 100, 100 from dual 
        union all select 4, 'asdf4', null, 0, 10 from dual 
    ) 
    select id, description, pay1, pay2, pay3, 
        cardinality(set(t_num(pay1, pay2, pay3))) as uniquepays 
    from t 
    order by id; 
    
         ID DESCR  PAY1  PAY2  PAY3 UNIQUEPAYS 
    ---------- ----- ---------- ---------- ---------- ---------- 
         1 asdf1   10   20   10   2 
         2 asdf2   0   10   20   3 
         3 asdf3  100  100  100   1 
         4 asdf4      0   10   3 
    

    無論是與數百萬行的足夠的效率將需要進行測試。

    +0

    我喜歡你如何使用'CARDINALITY'來處理OP所需的空值。就個人而言,我不會指望'UNIQUEPAYS'的空薪酬價值,但問題在於這個問題。 –

    +0

    我認爲'VALUES()'比'from dual union all'更好# – Hogan

    +0

    @Hogan - 據我所知,只有SQL Server和PostgreSQL支持'values(1,'asdf1',10,20, 10),(2,'asdf2',...),...'。如果這就是你的意思。 Oracle只允許在insert語句中使用'values',即使只有一個.. er ..值集。 SQL標準的另一部分Oracle不支持? –

    1

    這是一個解決方案,它只讀取一次基表,並利用已在行中組織的數據。 (不透明將是低效率的,因爲這些信息將會丟失,導致大量的額外工作。)

    它假定所有NULL都被計算爲相同。如果相反,他們應該相互視爲不同,在nvl-1更改爲不同的值:-1Pay1-2Pay2

    with 
        inputs(ID, Description, Pay1, Pay2, Pay3) as (  
         select 1, 'asdf1',     10, 20, 10 from dual union all 
         select 2, 'asdf2',     0, 10, 20 from dual union all 
         select 3, 'asdf3',     100, 100, 100 from dual union all 
         select 4, 'asdf4', cast(null as number), 0, 10 from dual 
        ) 
    -- End of TEST data (not part of solution!) SQL query begins BELOW THIS LINE. 
    select id, description, pay1, pay2, pay3, 
          1 
         + case when nvl(pay2, -1) not in (nvl(pay1, -1)) 
           then 1 else 0 end 
         + case when nvl(pay3, -1) not in (nvl(pay1, -1), nvl(pay2, -1)) 
           then 1 else 0 end 
                 as distinct_pays 
    from  inputs 
    order by id -- if needed 
    ; 
    
    ID DESCRIPTION  PAY1 PAY2 PAY3 DISTINCT_PAYS 
    -- ------------ ------- ------- ------- ------------- 
    1 asdf1    10  20  10    2 
    2 asdf2    0  10  20    3 
    3 asdf3   100  100  100    1 
    4 asdf4      0  10    3 
    
    4 rows selected. 
    
    +2

    你意識到有8個支付列。你的病例陳述將不得不迎合大量的比較(這就是爲什麼我使用btw聯盟)。讓我們看看他們! – Bohemian

    +0

    @Bohemian - 是的,我意識到這一點。比較是微不足道的;瓶頸(大多數情況下)會重複訪問行。除此之外:任何一種計數都可以完成相同的比較(您如何期望Oracle引擎能夠「計數不同的」值?) – mathguy

    +0

    oracle使用O(n log n)效率來執行此操作,但您必須使用O(n2)效率,因爲這是必須使用case語句進行多少次比較 – Bohemian