IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 【clickhouse实践】clickhouse中如何实现ROW_NUMBER() OVER(PARTITION BY ‘xxx‘ ORDER BY ‘xxx‘ DESC/ASC) -> 正文阅读

[大数据]【clickhouse实践】clickhouse中如何实现ROW_NUMBER() OVER(PARTITION BY ‘xxx‘ ORDER BY ‘xxx‘ DESC/ASC)

0 背景

HIVE中我们经常使用窗口函数中的排序函数ROW_NUMBER() OVER(PARTITION BY 'xxx' ORDER BY 'xxx' DESC/ASC)对数据集生成顺序编号或者进行数据去重的操作。然而在Clickhouse中没有提供该功能的函数,那么在clickhouse我们要想实现类似的功能我们应该如何实现呢?今天我们就来用实例说明下在clickhouse该如何实现ROW_NUMBER()功能。

1 数据准备

1.1 Clickhouse数据准备

  • 建表语句:
CREATE TABLE test.row_number
(
    `user_id` String,
    `user_phone` String,
    `create_time` DateTime,
    `update_time` DateTime
)
ENGINE = MergeTree
ORDER BY user_id
SETTINGS index_granularity = 8192;
  • 数据准备
┌─user_id─┬─user_phone──┬─────────create_time─┬─────────update_time─┐
│ 1132345678912021-07-14 11:54:192021-07-14 11:54:19 │
│ 1132345678902021-07-14 11:40:092021-07-14 11:40:09 │
│ 1132345678922021-07-14 11:54:372021-07-14 11:54:37 │
│ 2132345678932021-07-14 11:55:052021-07-14 11:55:05 │
│ 2132345678942021-07-14 11:57:222021-07-14 11:57:22 │
│ 3132345678952021-07-14 11:57:302021-07-14 11:57:30 │
│ 3132345678962021-07-14 11:57:452021-07-14 11:57:45 │
└─────────┴─────────────┴─────────────────────┴─────────────────────┘

1.2 Hive 数据准备

  • 建表语句:
CREATE TABLE test.row_number
(
    `user_id` string,
    `user_phone` string,
    `create_time` timestamp,
    `update_time` timestamp
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS textfile
LOCATION '/big-data/test/row_number';
  • 数据准备:
1	13234567891	2021-07-14 11:54:19	2021-07-14 11:54:19
1	13234567890	2021-07-14 11:40:09	2021-07-14 11:40:09
1	13234567892	2021-07-14 11:54:37	2021-07-14 11:54:37
2	13234567894	2021-07-14 11:57:22	2021-07-14 11:57:22
2	13234567893	2021-07-14 11:55:05	2021-07-14 11:55:05
3	13234567896	2021-07-14 11:57:45	2021-07-14 11:57:45
3	13234567895	2021-07-14 11:57:30	2021-07-14 11:57:30

2 ROW_NUMBER()排序实现

  • Hive:
    • 实现:
SELECT
   user_id,
   user_phone,
   create_time,
   update_time,
   ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC) AS rank
FROM test.row_number
ORDER BY user_id
  • 查询结果
    在这里插入图片描述
  • ClickHouse:
    • 实现:
SELECT user_id,
       groupArray(user_phone) AS user_phone,
       groupArray(create_time) AS create_time,
       groupArray(update_time) AS update_time,
       arrayEnumerate(update_time) AS row_number
FROM 
    (SELECT DISTINCT user_id,
                     user_phone,
                     create_time,
                     update_time
     FROM test.row_number
     ORDER BY update_time DESC
    )
GROUP BY user_id
ORDER BY user_id;
  • 查询结果:
┌─user_id─┬─user_phone──────────────────────────────────┬─create_time─────────────────────────────────────────────────────────┬─update_time─────────────────────────────────────────────────────────┬─row_number─┐
│ 1['13234567892','13234567891','13234567890']['2021-07-14 11:54:37','2021-07-14 11:54:19','2021-07-14 11:40:09']['2021-07-14 11:54:37','2021-07-14 11:54:19','2021-07-14 11:40:09'][1,2,3]    │
│ 2['13234567894','13234567893']['2021-07-14 11:57:22','2021-07-14 11:55:05']['2021-07-14 11:57:22','2021-07-14 11:55:05'][1,2]      │
│ 3['13234567896','13234567895']['2021-07-14 11:57:45','2021-07-14 11:57:30']['2021-07-14 11:57:45','2021-07-14 11:57:30'][1,2]      │
└─────────┴─────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────┴────────────┘

3 rows in set. Elapsed: 0.005 sec.

在这里插入图片描述

3 ROW_NUMBER()排序后取出rank=1的结果

  • Hive
    • 实现:
SELECT
   user_id,
   user_phone,
   create_time,
   update_time
FROM(
     SELECT
        user_id,
        user_phone,
        create_time,
        update_time,
        ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC) AS rank
     FROM test.row_number
    )
WHERE rank = 1
ORDER BY user_id
  • 查询结果: 在这里插入图片描述
  • ClickHouse
    • 实现1:利用groupArray

    这种实现方式是利用了clickhouse自带函数groupArray(n)(cloumn_name)实现。 ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC),其中排序顺序由子查询中ORDER BY update_time desc实现,分组PARTITION BY user_id外层查询中 group by user_id实现,rank = 1通过groupArray(1)(cloumn_1)来实现。从深层次理解我们可以认为我们只是将数据放到数组中然后取出数组中的第一个元素并把它作为子数组的唯一元素。

select user_id, 
      groupArray(1)(user_phone) AS user_phone,
      groupArray(1)(create_time) AS create_time,
      groupArray(1)(update_time) AS update_time
 from (select user_id, 
              user_phone,
              create_time,
               update_time
         from test.row_number
        ORDER BY update_time desc
       ) a
group by user_id
order by user_id
SELECT
   user_id,
   user_phone[1] AS user_phone,
   create_time[1] AS create_time,
   update_time[1] AS update_time
FROM 
(
     SELECT user_id,
            groupArray(user_phone) AS user_phone,
            groupArray(create_time) AS create_time,
            groupArray(update_time) AS update_time
     FROM 
         (SELECT DISTINCT user_id,
                          user_phone,
                          create_time,
                          update_time
          FROM test.row_number
          ORDER BY update_time DESC
         )
     GROUP BY user_id
)
ORDER BY user_id;
  • 查询结果1:
    在这里插入图片描述在这里插入图片描述

  • 方法2:利用max函数实现倒序,如果正序使用min函数即可

这里ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC)PARTITON BY子查询中的GROUP BY实现,ORDER BYrank = 1由子查询中maxmin实现。

SELECT
   user_id,
   user_phone,
   create_time,
   update_time
FROM test.row_number
WHERE update_time in (
                       SELECT max(update_time) AS update_time
                       FROM test.row_number
                       GROUP BY user_id
                     )
ORDER BY user_id
  • 查询结果2:
    在这里插入图片描述
  • 方法3:利用rowNumberInAllBlocks函数
    要理解这个方式必须先了解rowNumberInAllBlocks函数结果。我们来看
SELECT
    user_id,
    user_phone,
    create_time,
    update_time,
    rowNumberInAllBlocks() AS rank
FROM
(
    SELECT
        user_id,
        user_phone,
        create_time,
        update_time
    FROM test.row_number
    ORDER BY user_id,update_time DESC
)

结果如下:
在这里插入图片描述

我们来看看使用rowNumberInAllBlocks()如何实现ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC)。由上我们看到我们通过上面的工作已经将数据整体按照user_id做正序,update_time做了倒序排序。那么接下来我们只需要将数据按照user_id进行分组取出第一条就可以了,也就是将rank为0,3,5的三条数据取出来,实现如下:

SELECT
    user_id,
    user_phone,
    create_time,
    update_time,
    rank
FROM
(
    SELECT
        user_id,
        user_phone,
        create_time,
        update_time,
        rowNumberInAllBlocks() AS rank
    FROM
    (
        SELECT
            user_id,
            user_phone,
            create_time,
            update_time
        FROM test.row_number
        ORDER BY user_id,update_time DESC
    ) AS a
) AS b
LIMIT 1 BY user_id
  • 查询结果3:
    在这里插入图片描述
    我们发现通过使用LIMITBY两个关键字实现了分组和取数。自然LIMIT的作用是取数,BY作用是分组。
  • 方法4:利用arrayEnumerate函数
    对单字段的操作比较好比如只是选出是哪些用户,在实际业务用途不是很大,适用于类似用户生成标签的场景。
SELECT
   user_id,
   1 AS is_last_active_user
FROM 
(
     SELECT user_id,
            groupArray(update_time) AS update_time,
            arrayEnumerate(update_time) AS row_number
     FROM 
         (SELECT DISTINCT user_id,
                          update_time
          FROM test.row_number
          ORDER BY update_time DESC
         )
     GROUP BY user_id
)
WHERE row_number[1] = 1
ORDER BY user_id;
  • 查询结果:
    在这里插入图片描述

4 场景实例分析

  • 要求:
    对于以下场景,需要按照user_id分组,按照update_time倒序,取最新一条,若日期一致,则随机取一条作为结果即可
  • hive写法:
SELECT
   user_id,
   user_phone,
   create_time,
   update_time
FROM(
     SELECT
        user_id,
        user_phone,
        create_time,
        update_time,
        ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY update_time DESC) AS rank
     FROM test.row_number
    )
WHERE rank = 1
ORDER BY user_id
  • ClickHouse写法:

有网友说这个很容想到使用关联,我在这里只能说相当无语,下面我们给出该想法仅供大家吸取教训:

select a.user_id as user_id, 
       a.user_phone as user_phone,
       a.create_time as create_time,
       a.update_time as update_time
from test.row_number a
inner join (
             select user_id, 
                    groupArray(1)(update_time) as update_time
             from (
                    select user_id, 
                           update_time
                    from test.row_number
                    ORDER BY user_id, update_time desc
                  ) a
             group by user_id) b
on a.user_id = b.user_id
and cast(a.update_time as String) = cast(b.update_time[1] as String) 

这里我们是先把符合要求的user_id和时间取出来,再回去关联,取出需要的列,因为这些函数都有一个缺点是只能有partition by的字段和排序字段,不能有其他字段,所以要返回关联,ininer join原表。虽然这种写法实现了相关操作但是不建议推荐使用,这种方式就好像是方法1未用到极致,最终实力不够,join来凑,在大多数业务场景下可能不会出现什么问题,但是在一些特定的业务或者数理场景极容易出现bug,而且join会使效率降低,这里不推荐使用。
其实看到实例的时候大家应该就笑了,我们之前的案例就是按照这个写的,这样我们自然就想到了前三种方式就可以将问题顺利解决,其实第四种使用上边的关联的思路也可以实现,但是显然是有局限性的。和上边关联的思路类似都存在这样的问题:
大家试想,如果同一个用户分组中有多条相同的数据,在与源表发生join的时候这多条数据都会被关联上,也就是我们常说的一对多关联。这就破坏了之前做的取最新一条和随机一条的操作,真是一‘招’回到解放前啊。

总结

讲到这里相信大家对于clickhouse如何实现ROW_NUMBER() OVER(PARTITION BY 'xxx' ORDER BY 'xxx' DESC/ASC)的功法已经小成,希望能够帮助大家解决实际工作中的问题。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 23:47:47  更:2021-07-15 23:48:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 16:31:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码