添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
卖萌的勺子  ·  HTML <pre> 标签 与 ...·  1 年前    · 
睿智的黄瓜  ·  Schannel Events | ...·  1 年前    · 

1. 简介

在数据库SQL处理中,常常有行转列(Pivot)和列转行(Unpivot)的数据处理需求。本文以示例说明在Data Lake Analytics( https://www.aliyun.com/product/datalakeanalytics )中,如何使用SQL的一些技巧,达到行转列(Pivot)和列转行(Unpivot)的目的。另外,DLA支持函数式表达式的处理逻辑、丰富的JSON数据处理函数和UNNEST的SQL语法,结合这些功能,能够实现非常丰富、强大的SQL数据处理语义和能力,本文也以JSON数据列展开为示例,说明在DLA中使用这种SQL的技巧。

2. 行转列(Pivot)

2.1 样例数据

test_pivot表内容:

+------+----------+---------+--------+
| id   | username | subject | source |
+------+----------+---------+--------+
| 1    | 张三     | 语文    | 60     |
| 2    | 李四     | 数学    | 70     |
| 3    | 王五     | 英语    | 80     |
| 4    | 王五     | 数学    | 75     |
| 5    | 王五     | 语文    | 57     |
| 6    | 李四     | 语文    | 80     |
| 7    | 张三     | 英语    | 100    |
+------+----------+---------+--------+

2.2 方法一:通过CASE WHEN语句

SQL语句:

SELECT 
   username,
   max(CASE WHEN subject = '语文' THEN source END) AS `语文`,
   max(CASE WHEN subject = '数学' THEN source END) AS `数学`,
   max(CASE WHEN subject = '英语' THEN source END) AS `英语`
FROM test_pivot
GROUP BY username
ORDER BY username;
+----------+--------+--------+--------+
| username | 语文   | 数学   | 英语   |
+----------+--------+--------+--------+
| 张三     | 60     | NULL   | 100    |
| 李四     | 80     | 70     | NULL   |
| 王五     | 57     | 75     | 80     |
+----------+--------+--------+--------+

2.3 方法二:通过map_agg函数

该方法思路上分为两个步骤:
第一步,通过map_agg函数把两个列的多行的值,映射为map;
第二步,通过map的输出,达到多列输出的目的。

第一步SQL:

SELECT username, map_agg(subject, source) kv
FROM test_pivot
GROUP BY username
ORDER BY username;

第一步输出:

+----------+-----------------------------------+
| username | kv                                |
+----------+-----------------------------------+
| 张三     | {语文=60, 英语=100}               |
| 李四     | {数学=70, 语文=80}                |
| 王五     | {数学=75, 语文=57, 英语=80}       |
+----------+-----------------------------------+

可以看到map_agg的输出效果。

最终,该方法的SQL:

SELECT
  username,
  if(element_at(kv, '语文') = null, null, kv['语文']) AS `语文`,
  if(element_at(kv, '数学') = null, null, kv['数学']) AS `数学`,
  if(element_at(kv, '英语') = null, null, kv['英语']) AS `英语`
FROM (
  SELECT username, map_agg(subject, source) kv
  FROM test_pivot
  GROUP BY username
ORDER BY username;
+----------+--------+--------+--------+
| username | 语文   | 数学   | 英语   |
+----------+--------+--------+--------+
| 张三     | 60     | NULL   | 100    |
| 李四     | 80     | 70     | NULL   |
| 王五     | 57     | 75     | 80     |
+----------+--------+--------+--------+

3. 列转行(Unpivot)

3.1 样例数据

test_unpivot表内容:

+----------+--------+--------+--------+
| username | 语文   | 数学   | 英语   |
+----------+--------+--------+--------+
| 张三     | 60     | NULL   | 100    |
| 李四     | 80     | 70     | NULL   |
| 王五     | 57     | 75     | 80     |
+----------+--------+--------+--------+

3.2 方法一:通过UNION语句

SQL语句:

SELECT username, subject, source
FROM (
  SELECT username, '语文' AS subject, `语文` AS source FROM test_unpivot WHERE `语文` is not null
  UNION
  SELECT username, '数学' AS subject, `数学` AS source FROM test_unpivot WHERE `数学` is not null
  UNION
  SELECT username, '英语' AS subject, `英语` AS source FROM test_unpivot WHERE `英语` is not null
ORDER BY username;
+----------+---------+--------+
| username | subject | source |
+----------+---------+--------+
| 张三     | 语文    | 60     |
| 张三     | 英语    | 100    |
| 李四     | 语文    | 80     |
| 李四     | 数学    | 70     |
| 王五     | 英语    | 80     |
| 王五     | 语文    | 57     |
| 王五     | 数学    | 75     |
+----------+---------+--------+

3.3 方法二:通过CROSS JOIN UNNEST语句

SQL语句:

SELECT t1.username, t2.subject, t2.source
FROM test_unpivot t1
CROSS JOIN UNNEST (
  array['语文', '数学', '英语'],
  array[`语文`, `数学`, `英语`]
) t2 (subject, source)
WHERE t2.source is not null
+----------+---------+--------+
| username | subject | source |
+----------+---------+--------+
| 张三     | 语文    | 60     |
| 张三     | 英语    | 100    |
| 李四     | 语文    | 80     |
| 李四     | 数学    | 70     |
| 王五     | 语文    | 57     |
| 王五     | 数学    | 75     |
| 王五     | 英语    | 80     |
+----------+---------+--------+

4. JSON数据列展开

JSON数据的表达能力非常灵活,因此在数据库和SQL中,常常需要处理JSON数据,常常碰到稍复杂的需求,就是将JSON数据中的某些属性字段,进行展开转换,转成行、列的关系型表达。

4.1 基本思路和步骤

  • 使用JSON函数,对JSON字符串进行解析和数据提取;
  • 提取、转换为ARRAY或者MAP的数据结构,如有需要,可以使用Lambda函数式表达式进行转换处理;
  • 利用UNNEST语法进行列展开。
  • 下面以多个示例说明。

    4.2 用UNNEST对MAP进行关系型展开

    SQL示例:

    SELECT t.m, t.n
    FROM (
      SELECT MAP(ARRAY['foo', 'bar'], ARRAY[1, 2]) as map_data
    CROSS JOIN unnest(map_data) AS t(m, n);
    +------+------+
    | m    | n    |
    +------+------+
    | foo  |    1 |
    | bar  |    2 |
    +------+------+

    4.3 用UNNEST对JSON数据进行关系型展开

    SQL示例:

    SELECT json_extract(t.a, '$.a') AS a, 
           json_extract(t.a, '$.b') AS b
    FROM (
        SELECT cast(json_extract('{"x":[{"a":1,"b":2},{"a":3,"b":4}]}', '$.x') 
               AS array<JSON>) AS package_array
    CROSS JOIN UNNEST(package_array) AS t(a);
    +------+------+
    | a    | b    |
    +------+------+
    | 1    | 2    |
    | 3    | 4    |
    +------+------+

    SQL示例:

    SELECT t.m AS _col1, t.n AS _col2
    FROM (
        SELECT cast(json_extract('{"x":[{"a":1,"b":2},{"a":3,"b":4}]}', '$.x') 
               AS array<JSON>) AS array_1, 
               cast(json_extract('{"x":[{"a":5,"b":6}, {"a":7,"b":8}, {"a":9,"b":10}, {"a":11,"b":12}]}', '$.x') 
               AS array<JSON>) AS array_2
    CROSS JOIN UNNEST(array_1, array_2) AS t(m, n);
    +---------------+-----------------+
    | _col1         | _col2           |
    +---------------+-----------------+
    | {"a":1,"b":2} | {"a":5,"b":6}   |
    | {"a":3,"b":4} | {"a":7,"b":8}   |
    | NULL          | {"a":9,"b":10}  |
    | NULL          | {"a":11,"b":12} |
    +---------------+-----------------+

    SQL示例:

    SELECT json_extract(t.m, '$.a') AS _col1, 
           json_extract(t.m, '$.b') AS _col2, 
           json_extract(t.n, '$.a') AS _col3, 
           json_extract(t.n, '$.b') AS _col4 
    FROM (
        SELECT cast(json_extract('{"x":[{"a":1,"b":2},{"a":3,"b":4}]}', '$.x') 
               AS array<JSON>) AS array_1, 
               cast(json_extract('{"x":[{"a":5,"b":6}, {"a":7,"b":8}, {"a":9,"b":10}, {"a":11,"b":12}]}', '$.x') 
               AS array<JSON>) AS array_2
    CROSS JOIN UNNEST(array_1, array_2) AS t(m, n);
    +-------+-------+-------+-------+
    | _col1 | _col2 | _col3 | _col4 |
    +-------+-------+-------+-------+
    | 1     | 2     | 5     | 6     |
    | 3     | 4     | 7     | 8     |
    | NULL  | NULL  | 9     | 10    |
    | NULL  | NULL  | 11    | 12    |
    +-------+-------+-------+-------+

    4.4 结合Lambda表达式,用UNNEST对JSON数据进行关系型展开

    SQL示例:

    SELECT count(*) AS cnt, 
           package_name 
    FROM ( 
        SELECT t.a AS package_name 
        FROM ( 
            SELECT transform(packages_map_array, x -> Element_at(x, 'packageName')) 
                   AS package_array 
            FROM (
                SELECT cast(Json_extract(data_json, '$.packages') 
                       AS array<map<VARCHAR, VARCHAR>>) AS packages_map_array
                FROM (
                    SELECT json_parse(data) AS data_json
                    FROM ( 
                        SELECT '{
                                  "packages": [
                                      "appName": "铁路12306",
                                      "packageName": "com.MobileTicket",
                                      "versionName": "4.1.9",
                                      "versionCode": "194"
                                      "appName": "QQ飞车",
                                      "packageName": "com.tencent.tmgp.speedmobile",
                                      "versionName": "1.11.0.13274",
                                      "versionCode": "1110013274"
                                      "appName": "掌阅",
                                      "packageName": "com.chaozh.iReaderFree",
                                      "versionName": "7.11.0",
                                      "versionCode": "71101"
                        AS data 
        ) AS x (package_array)
        CROSS JOIN UNNEST(package_array) AS t (a)
    GROUP BY package_name 
    ORDER BY cnt DESC;
    +------+------------------------------+
    | cnt  | package_name                 |
    +------+------------------------------+
    |    1 | com.MobileTicket             |
    |    1 | com.tencent.tmgp.speedmobile |
    |    1 | com.chaozh.iReaderFree       |
    +------+------------------------------+
    【SQL开发实战技巧】系列(九):一个update误把其他列数据更新成空了?Merge改写update!给你五种删除重复数据的写法!
    本篇文章讲解的主要内容是:***你有没有经历过一个update把其他列数据清空了、使用merge更新合并记录、删除违反参照完整性的记录、给你五种删除重复数据的写法*** 【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。
    Flink 1.10.0 SQL DDL中如何定义watermark和计算列
    随着Flink1.10.0版本的发布,在SQL上一个重大的优化是支持了watermark语义的计算,在之前的Flink1.9.x版本中是不支持的,当时只能用SQL DDL进行processing time的计算,但是现在可以进行eventtime语义的计算了,那在Flink1.10.0版本中也推出了很多新的特性,这里就不在多介绍了,本篇文章主要是接上一篇文章,FlinkSQL使用DDL语句创建kafka源表,主要来介绍一下Flink1.10.0中怎么定义watermark.
    前面几节中给出的规则将导致为SQL查询中的所有表达式分配非unknown数据类型, 除非未指定类型文字显示为SELECT命令的简单输出列。例如,在 SELECT 'Hello World'; 没有什么可以确定字符串文字应被视为什么类型。 5.4. 系统列 每一个表都拥有一些由系统隐式定义的系统列。因此,这些列的名字不能像用户定义的列一样使用(注意这种限制与名称是否为关键词没有关系,即便用引号限定一个名称也无法绕过这种限制)。 事实上用户不需要关心这些列,只需要知道它们存在即可。