1.函数简介

(1)arrayJoin函数

先把官方的文档贴上来

Array JOIN 子句允许在数据表的内部,与数组或者嵌套的字段进行JOIN操作,从而将一行数据变多行。适用于行转列操作

示例:

CREATE TABLE arrays_test
(s String,arr Array(UInt8)
) ENGINE = Memory;INSERT INTO arrays_test
VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []);
┌─s───────────┬─arr─────┐
│ Hello       │ [1,2]   │
│ World       │ [3,4,5] │
│ Goodbye     │ []      │
└─────────────┴─────────┘

array join

SELECT s, arr
FROM arrays_test
ARRAY JOIN arr;
┌─s─────┬─arr─┐
│ Hello │   1 │
│ Hello │   2 │
│ World │   3 │
│ World │   4 │
│ World │   5 │
└───────┴─────┘

left array join

SELECT s, arr
FROM arrays_test
LEFT ARRAY JOIN arr;
┌─s───────────┬─arr─┐
│ Hello       │   1 │
│ Hello       │   2 │
│ World       │   3 │
│ World       │   4 │
│ World       │   5 │
│ Goodbye     │   0 │
└─────────────┴─────┘

(2)arrayMap函数

arrayMap(fun1,arr1)

参数1:这是一个高阶函数,您必须将 lambda 函数作为第一个参数传递给它,并且不能省略它,它规定了对于数组中元素的操作

参数2:要执行的数组

示例:对数组中每个元素+2

SELECT arrayMap(x -> (x + 2), [1, 2, 3]) as arr;
┌─res─────┐
│ [3,4,5] │
└─────────┘

从不同的数组创建元素元组:

SELECT arrayMap((x, y) -> (x, y), [1, 2, 3], [4, 5, 6]) AS res
┌─res─────────────────┐
│ [(1,4),(2,5),(3,6)] │
└─────────────────────┘

2.使用场景

先看数据:

┌─dept──┬─budget_name─┬─type──┬────money─┬─posting_date─┐
│ 部门1 │ 主营收入    │ type1 │  3654.32 │   2023-04-10 │
│ 部门1 │ 所得税      │ type1 │      300 │   2023-04-10 │
│ 部门1 │ 成本        │ type1 │  2036.32 │   2023-04-10 │
│ 部门1 │ 折旧和摊销  │ type1 │   462.98 │   2023-04-10 │
│ 部门1 │ 发货套数    │ type1 │      100 │   2023-04-10 │
│ 部门1 │ 主营收入    │ type2 │  45632.5 │   2023-04-10 │
│ 部门1 │ 所得税      │ type2 │ 12036.32 │   2023-04-10 │
│ 部门1 │ 成本        │ type2 │ 10630.34 │   2023-04-10 │
│ 部门1 │ 折旧和摊销  │ type2 │   4123.2 │   2023-04-10 │
│ 部门1 │ 发货套数    │ type2 │     1000 │   2023-04-10 │
│ 部门1 │ 主营收入    │ 公共  │ 10000000 │   2023-04-10 │
│ 部门2 │ 主营收入    │ type1 │ 13654.32 │   2023-04-10 │
│ 部门2 │ 所得税      │ type1 │  3000.37 │   2023-04-10 │
│ 部门2 │ 成本        │ type1 │      300 │   2023-04-10 │
│ 部门2 │ 折旧和摊销  │ type1 │      300 │   2023-04-10 │
│ 部门2 │ 发货套数    │ type1 │      300 │   2023-04-10 │
│ 部门2 │ 主营收入    │ type2 │  3654.32 │   2023-04-10 │
│ 部门2 │ 所得税      │ type2 │    300.3 │   2023-04-10 │
│ 部门2 │ 成本        │ type2 │   2203.9 │   2023-04-10 │
│ 部门2 │ 折旧和摊销  │ type2 │      300 │   2023-04-10 │
│ 部门2 │ 发货套数    │ type2 │      300 │   2023-04-10 │
│ 部门2 │ 主营收入    │ 公共  │ 30000000 │   2023-04-10 │
└───────┴─────────────┴───────┴──────────┴──────────────┘

需求1:求出各个dept各个type(不包括公共)的收入,所得税,成本,折旧和摊销,发货套数,产值(收入*1.13),毛利(收入-成本),单套收入(收入/发货套数),利润总额(收入-成本-折旧和摊销-所得税),净利润(利润总额-所得税),要求输出的数据集中带有指标名称。

select dept,type,arr.1 as budget,arr.2 as money from
(select dept,type,[('主营收入',sumIf(ifNull(money,0),budget_name='主营收入') as `营业收入`),('所得税',sumIf(ifNull(money,0),budget_name='所得税') as `所得税`),('成本',sumIf(ifNull(money,0),budget_name='成本') as `成本`),('折旧和摊销',sumIf(ifNull(money,0),budget_name='折旧和摊销') as `折旧和摊销`),('发货套数',sumIf(ifNull(money,0),budget_name='发货套数') as `发货套数`),('产值', toDecimal64((`营业收入` * 1.13), 2) as `产值`),('毛利', toDecimal64((`营业收入` - `成本`), 2) as  `毛利`),('单套收入', toDecimal64(if(`发货套数` != 0, (`营业收入` / `发货套数`), 0), 2) as `单套收入`),('利润总额',toDecimal64((`营业收入` - `成本` - `折旧和摊销`), 2) as  `利润总额`),('净利润',toDecimal64((`利润总额` - `所得税` ), 2) as  `净利润`)] as arr
from factory.factory
where toYear(posting_date)=2023 and type!='公共'
group by dept,type) array join arr;
┌─dept──┬─type──┬─budget─────┬────money─┐
│ 部门1 │ type1 │ 主营收入   │  3654.32 │
│ 部门1 │ type1 │ 产值       │  4129.38 │
│ 部门1 │ type1 │ 净利润     │   855.02 │
│ 部门1 │ type1 │ 利润总额   │  1155.02 │
│ 部门1 │ type1 │ 单套收入   │    36.54 │
│ 部门1 │ type1 │ 发货套数   │      100 │
│ 部门1 │ type1 │ 成本       │  2036.32 │
│ 部门1 │ type1 │ 所得税     │      300 │
│ 部门1 │ type1 │ 折旧和摊销 │   462.98 │
│ 部门1 │ type1 │ 毛利       │     1618 │
│ 部门1 │ type2 │ 主营收入   │  45632.5 │
│ 部门1 │ type2 │ 产值       │ 51564.72 │
│ 部门1 │ type2 │ 净利润     │ 18842.64 │
│ 部门1 │ type2 │ 利润总额   │ 30878.96 │
│ 部门1 │ type2 │ 单套收入   │    45.63 │
│ 部门1 │ type2 │ 发货套数   │     1000 │
│ 部门1 │ type2 │ 成本       │ 10630.34 │
│ 部门1 │ type2 │ 所得税     │ 12036.32 │
│ 部门1 │ type2 │ 折旧和摊销 │   4123.2 │
│ 部门1 │ type2 │ 毛利       │ 35002.16 │
│ 部门2 │ type1 │ 主营收入   │ 13654.32 │
│ 部门2 │ type1 │ 产值       │ 15429.38 │
│ 部门2 │ type1 │ 净利润     │ 10053.95 │
│ 部门2 │ type1 │ 利润总额   │ 13054.32 │
│ 部门2 │ type1 │ 单套收入   │    45.51 │
│ 部门2 │ type1 │ 发货套数   │      300 │
│ 部门2 │ type1 │ 成本       │      300 │
│ 部门2 │ type1 │ 所得税     │  3000.37 │
│ 部门2 │ type1 │ 折旧和摊销 │      300 │
│ 部门2 │ type1 │ 毛利       │ 13354.32 │
│ 部门2 │ type2 │ 主营收入   │  3654.32 │
│ 部门2 │ type2 │ 产值       │  4129.38 │
│ 部门2 │ type2 │ 净利润     │   850.12 │
│ 部门2 │ type2 │ 利润总额   │  1150.42 │
│ 部门2 │ type2 │ 单套收入   │    12.18 │
│ 部门2 │ type2 │ 发货套数   │      300 │
│ 部门2 │ type2 │ 成本       │   2203.9 │
│ 部门2 │ type2 │ 所得税     │    300.3 │
│ 部门2 │ type2 │ 折旧和摊销 │      300 │
│ 部门2 │ type2 │ 毛利       │  1450.42 │
└───────┴───────┴────────────┴──────────┘

因为需求中要输出指标名称,但是有的指标名称是通过现有指标延申出来的,这种指标名称需要造出来,所以如果对于clickhouse的array join不熟悉的话,很多人可能第一个想法是通过union all 和as的方式

select dept,type,'主营收入' as name,sum(money) from factory.factory
where toYear(posting_date)=2023 and type!='公共'
group by dept,type
union all
select dept,type,'产值' as name,toDecimal64(sumIf(money,budget_name='主营收入')*1.13,2) as money from factory.factory
where toYear(posting_date)=2023 and type!='公共'
group by dept,type
union all
.....

这种方式也能实现需求,这是因为我们举例的dept,type以及指标都很少,但是如果部门和指标很多呢,union all连接起来的sql将会特别多,而且每个sql都会全量数据查询,这将会执行很长时间,且各个指标的计算逻辑非常不清晰,如果后期有一个指标逻辑调整,需要改动的不止一段sql,特别容易出错,相反array join的方式,只会全量查询一次且指标逻辑清晰,没有重复sql,效果翻倍

再对于金额类数据时需要注意精度问题,比如下面这个需求

需求2:计算每个dept的各type的分摊收入

计算逻辑:每个dept的各个非公共type占所有非公共type的收入占比*公共的收入

select A.dept,A.type,A.rate*B.public as money,B.public from
(select dept,type,if(bb!=0,aa / bb,0) as rate from(
select distinctdept,type,sum(toDecimal64(money,9)) over(partition by dept,type) as aa,sum(toDecimal64(money,9)) over(partition by dept)  as bb
from factory.factory
where toYear(posting_date)=2023 and type!='公共' and budget_name='主营收入') ) A left join
(select dept,ifNull(sum(money),0) as public from factory.factory
where budget_name='主营收入' and toYear(posting_date)=2023 and type='公共' group by dept) B
on A.dept=B.dept
┌─dept──┬─type──┬───────money─┬───public─┐
│ 部门1 │ type1 │   741439.59 │ 10000000 │
│ 部门1 │ type2 │   9258560.4 │ 10000000 │
│ 部门2 │ type1 │ 23666192.13 │ 30000000 │
│ 部门2 │ type2 │  6333807.84 │ 30000000 │
└───────┴───────┴─────────────┴──────────┘

我们可以看到每个部门的公共金额特别大,如果我们不做decimal9处理,除出来的小数位只有2位,而且decimal他是截取小数位,2位后面的小数会直接截取掉,即使他截取0.00001个精度,0.00001*10000000 也将丢失100,而decimal64,9处理后他的误差只有0.01,在可以接受的范围内。

我们可以把decimal64,9改成2

┌─dept──┬─type──┬────money─┬───public─┐
│ 部门1 │ type1 │   700000 │ 10000000 │
│ 部门1 │ type2 │  9200000 │ 10000000 │
│ 部门2 │ type1 │ 23400000 │ 30000000 │
│ 部门2 │ type2 │  6300000 │ 30000000 │
└───────┴───────┴──────────┴──────────┘

可以看到部门1的误差达到了100000,部门2的误差达到了300000

通常对于数字型数据处理时还要注意ifNull判断,因为null和任何数字做算法都为null

┌─plus(NULL, 1)─┐
│ ᴺᵁᴸᴸ          │
└───────────────┘

如果我们想要对null值进行转换操作,还需要注意是否存在空字符串,空字符串并不会被当作null值处理,可以结合empty

array join还可以临时创建配置表使用:比如 求3种产品的当月销量,某个产品当月没有数据但是又需要展示出来时可以造一个配置表去left join 比如select arrayJoin( array('室内门','入户','窗')) as product

3. array join与arrayMap的结合

先看数据

┌─dept─┬─money─┬─posting_date─┐
│ dept │   100 │   2021-01-01 │
│ dept │   100 │   2021-02-01 │
│ dept │   100 │   2021-03-01 │
│ dept │   100 │   2021-04-01 │
│ dept │   100 │   2021-05-01 │
│ dept │   100 │   2021-06-01 │
│ dept │   100 │   2021-07-01 │
│ dept │   100 │   2021-08-01 │
│ dept │   100 │   2021-09-01 │
│ dept │   100 │   2021-10-01 │
│ dept │   100 │   2021-11-01 │
│ dept │   100 │   2021-12-01 │
│ dept │   100 │   2022-01-01 │
│ dept │   100 │   2022-02-01 │
│ dept │   100 │   2022-03-01 │
│ dept │   100 │   2022-04-01 │
│ dept │   100 │   2022-05-01 │
│ dept │   100 │   2022-06-01 │
│ dept │   100 │   2022-07-01 │
│ dept │   100 │   2022-08-01 │
│ dept │   100 │   2022-09-01 │
│ dept │   100 │   2022-10-01 │
│ dept │   100 │   2022-11-01 │
│ dept │   100 │   2022-12-01 │
└──────┴───────┴──────────────┘

需求:求dept近12个月的金额,每个月的计算范围往是截止到当前月的近12个月

select dept,sum(money) as money,toYear(m) as year,toMonth(m) as month
from factory.factory_1 array join  arrayMap(x->addMonths(posting_date, x), range(0, 12)) as m
where posting_date between toStartOfMonth(subtractMonths(toDate(now()), 23)) and now()
and m between toStartOfMonth(subtractMonths(toDate(now()), 11)) and now()
group by dept ,year,month
order by year asc,month asc
┌─dept─┬─money─┬─year─┬─month─┐
│ dept │  1200 │ 2022 │     5 │
│ dept │  1200 │ 2022 │     6 │
│ dept │  1200 │ 2022 │     7 │
│ dept │  1200 │ 2022 │     8 │
│ dept │  1200 │ 2022 │     9 │
│ dept │  1200 │ 2022 │    10 │
│ dept │  1200 │ 2022 │    11 │
│ dept │  1200 │ 2022 │    12 │
│ dept │  1100 │ 2023 │     1 │
│ dept │  1000 │ 2023 │     2 │
│ dept │   900 │ 2023 │     3 │
│ dept │   800 │ 2023 │     4 │
└──────┴───────┴──────┴───────┘

range(0,12)形成一个0-11的数组,x代表数组的每个元素

arrayMap(x->addMonths(posting_date, x), range(0, 12))

arrayMap让posting_date依次去addMonths,每个时间都被addMonths执行了12次

可以看到数据是截止到22年12月,所以23年开始每个月都少了100,截止到23年4月的近12个月只有800

CLICKHOUSE函数使用经验(arrayJoin与arrayMap函数应用场景)相关推荐

  1. qml 函数使用经验总结1(image中调用函数)

    Text {id: imgSourcetext: translation.qsTr(type)visible: false}Rectangle{//左上角的文件类型图标width: 16height: ...

  2. Go 学习笔记(61)— Go 高阶函数、函数作为一等公民(函数作为输入参数、返回值、变量)的写法

    函数在 Go 语言中属于"一等公民(First-Class Citizen)"拥有"一等公民"待遇的语法元素可以如下使用 可以存储在变量中: 可以作为参数传递给 ...

  3. C++ 笔记(13)— 函数(函数声明、函数定义、函数调用[传值、指针、引用]、函数参数默认值、函数重载)

    每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数. 1. 函数声明 函数声明告诉编译器函数的名称.返回类型和参数.函数声明包括以下几个部分: ret ...

  4. linux系统发送信号的系统调用是,linux系统编程之信号:信号发送函数sigqueue和信号安装函数sigaction...

    信号发送函数sigqueue和信号安装函数sigaction sigaction函数用于改变进程接收到特定信号后的行为. sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然 ...

  5. php中去空函数trim,PHP中trim()函数简单使用指南

    string trim ( string $str [, string $charlist ] ) - 去除字符串首尾处的空白字符(或者其他字符) trim()函数当第二个参数为空时,默认去掉空格.制 ...

  6. python数字类型转换函数_Python的数据类型转换函数

    玩蛇网这篇文章给大家介绍关于,Python数据类型的转换函数. Python提供了一些可以把某个值从一种数据类型,转换成为另一种数据类型的内置函数和方法.int函数可以将任何可以转换为整型的值转换为整 ...

  7. mysql 日期时间格式化字符串_MySQL日期函数与日期转换格式化函数大全

    Mysql作为一款开元的免费关系型数据库,用户基础非常庞大,本文列出了MYSQL常用日期函数与日期转换格式化函数 1.DAYOFWEEK(date) SELECT DAYOFWEEK('2016-01 ...

  8. C++ 函数模板和排序的函数模板——学习笔记

    我们在使用重载函数时,只是使用了函数名,而函数体还是得分别定义,在C++中函数模板为我们很好的解决了这个问题. 1.函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函 ...

  9. C++——运算符的重载---以成员函数方式重载---以友元函数方式重载

    一.运算符的重载 1.运算符的重载 允许把标准运算符(如+ - * /等运算符)应用于自定义数据类型的对象,可以提高程序的可读性,运算符的重载本质上还是函数重载.运算符仅仅是语法上的方便,它是另一种函 ...

最新文章

  1. 小试牛刀之Django
  2. pmp知识点详解-项目大牛整理_PMP第七章:项目成本管理(1)项目管理核心知识点...
  3. springmvc如何使用视图解析器_SpringMVC工作原理
  4. 正确使用计算机说课稿,《初识计算机》说课稿
  5. virtualbox虚拟机安装Ubuntu异常处理:FATAL: NO bootable medium found! System halted
  6. 能买?这款手机搭载联发科P60+32G,仅售399元
  7. java 如何捕获线程中的异常处理_如何捕获Java中另一个线程抛出的异常?
  8. 初学者应该怎么学习前端?web前端的学习路线大剖析
  9. java request含嵌套_使用 RxJava 进行嵌套串行网络请求的一种方法
  10. ● firewalld.service Loaded: not-found (Reason: No such file or directory)
  11. 三、Oracle 查询+where条件
  12. C语言 指针访问数组,C语言通过指针引用数组
  13. Centos操作系统基本介绍
  14. 【转载】smobiler说明
  15. FSR402传感器简介
  16. php木马在线查杀_网站木马监控查杀扫描 ASP木马 PHP木马上传拦截
  17. centos安装中文字体
  18. 小游戏《别踩白块》-第十一个程序20200625
  19. 阿里 + 京东 Java 岗面试题概要(面试须知
  20. 【外卖cps源码分享】支持美团饿了么

热门文章

  1. php音视频同步,FFMPEG转码音视频不同步情况总结
  2. 如何从Lytro 相机中获取图像阵列
  3. commvault备份mysql_CommVault备份到华为云对象存储实践
  4. 痛过,才坚强;傻过,才成长
  5. 第几代计算机有了硬件资源管理系统,从(??)计算机开始,在软件方面有了专门从事计算机软硬件资源管理的操作系统...
  6. 给js添加类名/添加元素标签/
  7. 关于行人检测中的常用数据集
  8. 基于单片机仓库智能巡检车的设计
  9. shardingJdbc 的 actual-data-nodes节点扩展
  10. c语言将字符串写入文件