1. 程式人生 > >SQL查詢案例:多行轉換為一行

SQL查詢案例:多行轉換為一行

使用通常的方式測試表與測試資料

CREATE TABLE TestTitle (

name   VARCHAR(10),

titleVARCHAR(10)

);

INSERT INTO TestTitle VALUES ('張三', '程式設計師');

INSERT INTO TestTitle VALUES ('張三', '系統管理員');

INSERT INTO TestTitle VALUES ('張三', '網路管理員');

INSERT INTO TestTitle VALUES ('李四', '專案經理');

INSERT INTO TestTitle VALUES ('李四', '系統分析員');

要求

對於測試資料,要求查詢結果為:

張三程式設計師,系統管理員,網路管理員

李四專案經理,系統分析員

這種結構的結果。

思路

簡單檢視這個結果,很像對字元型的GROUP BY處理。

數值型別的可以SUM,但是字元型別的無法這麼處理。

只好依次MAX(1) + MAX(2) + MAX(3)這種辦法來處理。

實現

第一步,設定好分組的編號

SELECT

ROW_NUMBER() OVER(PARTITION BY name ORDER BY title) AS no,

name,

title

FROM

TestTitle

ORDER BY

name,

title

no                   name       title

-------------------- ---------- ----------

                   1李四        系統分析員

                   2李四        專案經理

                   1張三        程式設計師

                   2張三        網路管理員

                   3張三        系統管理員

第二步,根據有編號的子查詢,進行分組處理

SELECT

name,

CASE WHEN COUNT(title) = 1 THEN MAX(title)

       WHEN COUNT(title) = 2 THEN

         MAX( CASE WHEN SubQuery.no = 1 THEN title + ',' ELSE '' END )

         + MAX( CASE WHEN SubQuery.no = 2 THEN titleELSE '' END )

       WHEN COUNT(title) = 3 THEN

         MAX( CASE WHEN SubQuery.no = 1 THEN title + ',' ELSE '' END )

         + MAX( CASE WHEN SubQuery.no = 2 THEN title + ','ELSE '' END )

         + MAX( CASE WHEN SubQuery.no = 3 THEN titleELSE '' END )

END AS new_title

FROM

(

SELECT

    ROW_NUMBER() OVER(PARTITION BY name ORDER BY title) AS no,

    name,

    title

FROM

    TestTitle

) subQuery

GROUP BY

name

執行結果

name       new_title

---------- ----------------------------------

李四        系統分析員,專案經理

張三        程式設計師,網路管理員,系統管理員

對於SQL Server 2005 以上版本使用FOR XML的方式測試表與測試資料要求

與前面的一樣

思路

首先把一個使用者的資料,單獨的讀取出來

然後按照分組進行處理

實現

第一步 把一個使用者的資料,單獨的讀取出來

SELECT

',' + title

FROM

TestTitle

WHERE

name = '張三'

FOR XML PATH('')

第二步Group By每個人

SELECT

name,

STUFF(

   (

   SELECT

     ',' + title

   FROM

     TestTitle subTitle

   WHERE

     name = TestTitle.name

   FOR XML PATH('')

   ),

   1, 1, '') AS allTitle

FROM

TestTitle

GROUP BY

name

執行結果

name      allTitle

---------- --------------------------------

李四        專案經理,系統分析員

張三        程式設計師,系統管理員,網路管理員

對於SQL Server 2005 以上版本使用 CTE 的處理方式 (使用遞迴方式處理)

WITH
t1  AS
(
  SELECT
    ROW_NUMBER() OVER(PARTITION BY name ORDER BY title) AS ID,
    name,
    title
  FROM
    TestTitle
),
t2 AS
(
  SELECT 
    t1.id, 
    t1.name, 
    CAST(t1.title AS varchar(100)) AS title
  FROM 
    t1 
  WHERE 
    t1.id = 1
  UNION ALL
  SELECT 
    t1.id, 
    t2.name, 
    CAST( t1.title + ',' + t2.title AS varchar(100)) AS title
  FROM 
    t1, t2
  WHERE 
    t1.name = t2.name
    AND t1.id = (t2.id + 1)
)
SELECT
  name, 
  title
FROM
  t2
WHERE
  NOT EXISTS (
    SELECT 1
    FROM t2 t22
    WHERE
      t2.name = t22.name
      AND t2.id < t22.id
  );

name       title

---------- -----------------------------------------------------------
-------------------------------
張三         系統管理員,網路管理員,程式設計師

李四         專案經理,系統分析員


(2 行受影響)

對於MySQL使用 GROUP_CONCAT 函式 的方式進行處理(非常簡單)

mysql> SELECT
    ->   name,
    ->   GROUP_CONCAT(title) AS allTitle
    -> FROM
    ->   TestTitle
    -> GROUP BY
    ->   name;
+------+------------------------------+
| name | allTitle                     |
+------+------------------------------+
| 李四 | 專案經理,系統分析員          |
| 張三 | 程式設計師,系統管理員,網路管理員 |
+------+------------------------------+
2 rows in set (0.00 sec)

對於Oracle使用 WMSYS.WM_CONCAT 函式 的方式進行處理(也非常簡單)

SQL> 
SQL> SELECT
  2    name,
  3    WMSYS.WM_CONCAT(title) AS allTitle
  4  FROM
  5    TestTitle
  6  GROUP BY
  7    name;

NAME
----------
ALLTITLE
-------------------------------------------
李四
專案經理,系統分析員

張三
程式設計師,系統管理員,網路管理員

對於 PostgreSQL 使用 string_agg  函式 的方式進行處理(也非常簡單)

Test=#

Test=# SELECT

Test-#   name,

Test-#   string_agg(title,',') AS allTitle

Test-# FROM

Test-#   TestTitle

Test-# GROUP BY

Test-#   name;

 name |           alltitle

------+------------------------------

 李四 | 專案經理,系統分析員

 張三 | 程式設計師,系統管理員,網路管理員

(2 行記錄)

對於 DB2 ,也是使用 CTE 遞迴的方式處理


WITH
t1 (id, name, title) AS
(
  SELECT
    ROW_NUMBER() OVER(PARTITION BY name ORDER BY title) AS ID,
    name,
    title
  FROM
    TestTitle
),
t2 (id, name, title) AS
(
  SELECT 
    t1.id, 
    t1.name, 
    CAST(t1.title AS varchar(100)) AS title
  FROM 
    t1 
  WHERE 
    t1.id = 1
  UNION ALL
  SELECT 
    t1.id, 
    t2.name, 
    CAST( t1.title || ',' || t2.title AS varchar(100)) AS title
  FROM 
    t1, t2
  WHERE 
    t1.name = t2.name
    AND t1.id = (t2.id + 1)
)
SELECT
  name, 
  title
FROM
  t2
WHERE
  NOT EXISTS (
    SELECT 1
    FROM t2 t22
    WHERE
      t2.name = t22.name
      AND t2.id < t22.id
  );

NAME       TITLE

---------- ---------------------------------------------------------------------
-------------------------------
SQL0347W  遞迴公共表表達式 "WZQ.T2" 可能包含無限迴圈。  SQLSTATE=01605

李四       專案經理,系統分析員

張三       網路管理員,系統管理員,程式設計師


  已選擇 2 條記錄,列印 1 條警告訊息。

-------------------------------------------------------------------------------------------------------------------------------------------------------

FOR XML PATH 的作用是把結果以xml文字的形式顯示出來,也就是說,最終結果就是一個字串,因此我們就不需要使用什麼字串合併函數了。

STUFF函式的原型是 Stuff(str1, start, len, str2),作用是,刪掉str1中start開始的len個字元,用str2替換。因此,可以起到在多個項之間插入分隔符。

比如,

select ','+name from student for xml path('') group by class

輸出結果可能是

,Jim,Kate,Tom,Sally

如果使用STUFF,可以刪掉第一個空格

stuff(select ','+name from student for xml path('') group by class, 1, 1, '')

如果使用的是hibernate 就要小心,用for xml path 查出來可能會是org.hibernate.lob.SerializableClob,而不是一個字串,我現在是使用下面的方法轉換的
if(value != null && value instanceof org.hibernate.lob.SerializableClob){
					    org.hibernate.lob.SerializableClob clob = (org.hibernate.lob.SerializableClob)value;
					    try {
                            Reader reader = clob.getCharacterStream();
                            BufferedReader br = new BufferedReader(reader);
                            StringBuilder sb = new StringBuilder();
                            String str = null;
                            while((str = br.readLine()) != null){
                                sb.append(str);
                            }
                            _map.put(key.toUpperCase(), sb.toString());
                        } catch (SQLException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
					}
不知道有沒有更簡單的方法進行配置