Oracle多行數據合併一行顯示【SQL實現詳細解析】

背景

前段時間,優化一張報表,在優化一條SQL查詢時候,出現這樣一個問題:

結果列中,有某一列因爲條件關聯原因,查詢的結果會報:

ORA-01427: single-row subquery returns more than one row,顧名思義就是查詢的結果出現多行了,與其他列保持不一致。

對於這種狀況,之前開發的邏輯是這樣的:先查詢除了此列外的所有結果,然後再把此列的結果一一Reset到報表裏。

這樣無疑增加了很大的time cost,剛開始因爲數據量不多,還沒出現速度上的隱患,隨時間推移,便暴露出來了。對此做法,以如今pro 數據增長速度堅決不可取。

後來在網上找了例子,可以把多行數據合併成一行顯示,查詢速度快得驚人。當時沒有好好研究其原理,現在有空摸索下其實現邏輯。


Template SQL:

SELECT   
PICKING_KEY,TRANSLATE (LTRIM (text, '/'), '*/', '*,') RESULT
FROM (SELECT ROW_NUMBER () OVER (PARTITION BY PICKING_KEY ORDER BY PICKING_KEY,lvl DESC) rn,
         PICKING_KEY, 
         text
         FROM (SELECT  PICKING_KEY, 
                      LEVEL lvl,
                      SYS_CONNECT_BY_PATH (ship_mark, '/') text
                      FROM (SELECT 
                            ship_mark AS ship_mark,
                            PICKING_KEY, 
                            ROW_NUMBER () OVER (PARTITION BY  PICKING_KEY ORDER BY  PICKING_KEY, ship_mark) x
                            FROM 
                            (  SELECT 
                               DISTINCT ship_mark AS ship_mark,
                               PICKING_KEY 
                               FROM con_pack_carton, con_pack_order
                               WHERE CON_PACK_CARTON.CON_PACK_KEY = CON_PACK_ORDER.CON_PACK_KEY
                                AND CON_PACK_ORDER.PICKING_KEY in ('0002927779')
                                AND CON_PACK_ORDER.BATCH_NO = 1 
                               ORDER BY ship_mark,PICKING_KEY)  
                       ORDER BY  ship_mark, PICKING_KEY)
                CONNECT BY PRIOR PICKING_KEY = PICKING_KEY AND PRIOR x - 1 = x)
      )
WHERE rn = 1;
結果:


圖1

上圖的效果,就是我想得到的結果。

雖然結果很簡單,但是實現的SQL嵌套5層邏輯,涉及的知識可不少,如層次化查詢、窗口函數、替換函數和刪除函數。

作者認爲,把這條SQL摸索透徹,差不多把SQL函數好好複習了一遍,OMG。


開始一層一層開始解析。

第一層

SELECT DISTINCT ship_mark AS ship_mark, picking_key
           FROM con_pack_carton, con_pack_order
          WHERE con_pack_carton.con_pack_key = con_pack_order.con_pack_key
            AND con_pack_order.picking_key IN ('0002927779')
            AND con_pack_order.batch_no = 1
       ORDER BY ship_mark, picking_key

結果:


圖2

這層是源數據,以PICKING_KEY爲準,合併所有SHIP_MARK值,這是我想要的結果,如圖1


第二層

SELECT   ship_mark AS ship_mark, picking_key,
         ROW_NUMBER () OVER (PARTITION BY picking_key ORDER BY picking_key,
          ship_mark) x
    FROM (SELECT DISTINCT ship_mark AS ship_mark, picking_key
                     FROM con_pack_carton, con_pack_order
                    WHERE con_pack_carton.con_pack_key =
                                                   con_pack_order.con_pack_key
                      AND con_pack_order.picking_key IN ('0002927779')
                      AND con_pack_order.batch_no = 1
                 ORDER BY ship_mark, picking_key)
ORDER BY ship_mark, picking_key

結果:


圖3

這層很簡單,也就是賦予一個順序值。關鍵一點使用到窗口函數ROW_NUMBER () OVER (PARTITION BY  PICKING_KEY ORDER BY  PICKING_KEY, ship_mark),

平常幾乎不用,現在認識它,以後就是我的朋友了。


第三層

SELECT     picking_key, LEVEL lvl, SYS_CONNECT_BY_PATH (ship_mark, '/') text
      FROM (SELECT   ship_mark AS ship_mark, picking_key,
                     ROW_NUMBER () OVER (PARTITION BY picking_key ORDER BY picking_key,
                      ship_mark) x
                FROM (SELECT DISTINCT ship_mark AS ship_mark, picking_key
                                 FROM con_pack_carton, con_pack_order
                                WHERE con_pack_carton.con_pack_key =
                                                   con_pack_order.con_pack_key
                                  AND con_pack_order.picking_key IN
                                                               ('0002927779')
                                  AND con_pack_order.batch_no = 1
                             ORDER BY ship_mark, picking_key)
            ORDER BY ship_mark, picking_key)
CONNECT BY PRIOR picking_key = picking_key AND PRIOR x - 1 = x
結果:

圖4

這層最複雜,關鍵點再層次化查詢及金字塔式合併字符串,最終的目的就是把所有數據合併起來(雖然會把其他亂七八糟數據帶出來)。

CONNECT BY PRIOR PICKING_KEY = PICKING_KEY AND PRIOR x - 1 = x這句SQL的意思大概這麼講:凡是符合繼承關係的,均羅列出來。

如員工屬於哪個小組,小組屬於哪個部門,部門屬於哪事業羣。具體的用法大家看書吧,我在這裏也不好講清楚。

至於爲什麼PICKING_KEY = PICKING_KEY AND x - 1 = x,我也不是很清楚作者爲什麼這麼寫,我想最終的目的實現繼承的關係吧。


第四層

SELECT ROW_NUMBER () OVER (PARTITION BY picking_key ORDER BY picking_key,
        lvl DESC) rn,
       picking_key, text
  FROM (SELECT     picking_key, LEVEL lvl,
                   SYS_CONNECT_BY_PATH (ship_mark, '/') text
              FROM (SELECT   ship_mark AS ship_mark, picking_key,
                             ROW_NUMBER () OVER (PARTITION BY picking_key ORDER BY picking_key,
                              ship_mark) x
                        FROM (SELECT DISTINCT ship_mark AS ship_mark,
                                              picking_key
                                         FROM con_pack_carton, con_pack_order
                                        WHERE con_pack_carton.con_pack_key =
                                                   con_pack_order.con_pack_key
                                          AND con_pack_order.picking_key IN
                                                               ('0002927779')
                                          AND con_pack_order.batch_no = 1
                                     ORDER BY ship_mark, picking_key)
                    ORDER BY ship_mark, picking_key)
        CONNECT BY PRIOR picking_key = picking_key AND PRIOR x - 1 = x)

結果:


圖5

經過上層精心處理後,我們想要的結果已經快出來了,這層我們只是讓它排序,再次用到

函數ROW_NUMBER () OVER (PARTITION BY PICKING_KEY ORDER BY PICKING_KEY,lvl DESC)


第五層

SELECT   
PICKING_KEY,TRANSLATE (LTRIM (text, '/'), '*/', '*,') RESULT
FROM (SELECT ROW_NUMBER () OVER (PARTITION BY PICKING_KEY ORDER BY PICKING_KEY,lvl DESC) rn,
         PICKING_KEY, 
         text
         FROM (SELECT  PICKING_KEY, 
                      LEVEL lvl,
                      SYS_CONNECT_BY_PATH (ship_mark, '/') text
                      FROM (SELECT 
                            ship_mark AS ship_mark,
                            PICKING_KEY, 
                            ROW_NUMBER () OVER (PARTITION BY  PICKING_KEY ORDER BY  PICKING_KEY, ship_mark) x
                            FROM 
                            (  SELECT 
                               DISTINCT ship_mark AS ship_mark,
                               PICKING_KEY 
                               FROM con_pack_carton, con_pack_order
                               WHERE CON_PACK_CARTON.CON_PACK_KEY = CON_PACK_ORDER.CON_PACK_KEY
                                AND CON_PACK_ORDER.PICKING_KEY in ('0002927779')
                                AND CON_PACK_ORDER.BATCH_NO = 1 
                               ORDER BY ship_mark,PICKING_KEY)  
                       ORDER BY  ship_mark, PICKING_KEY)
                CONNECT BY PRIOR PICKING_KEY = PICKING_KEY AND PRIOR x - 1 = x)
      )
WHERE rn = 1;
最終結果:


圖6

最後一層了,現在需要把'/'字符去掉,還有取RN=1的數據,一切的一切浮出水面。

TRANSLATE這個函數我還是第一回撞見,用Oracle都兩年了,OMG。剛開始看到時,TRANSLATE這不是“翻譯”的意思嗎,在這裏幹嘛用?後來再次翻譯,也可以是轉換的意思 -_-。


今天是馬丁·路德·金紀念日,還記得他那句:我有個夢想嗎?

I have a dream, make a nice extraordinary life


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章