ClickHouse

1.ClickHouse函数

ClickHouse主要提供两类函数—普通函数聚合函数。普通函数由IFunction接口定义,拥有数十种函数实现,例如FunctionFormatDateTime、FunctionSubstring等。除了一些常见的函数 ( 诸如四则运算、日期转换等 ) 之外,也不乏一些非常实用的函数,例如网址提取函数、IP地址脱敏函数等。普通函数是没有状态的,函数效果作用于每行数据之上。当然,在函数具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用于一整列数据。

聚合函数由IAggregateFunction接口定义,相比无状态的普通函数,聚合函数是有状态的。以COUNT聚合函数为例,其AggregateFunctionCount的状态使用整型UInt64记录。聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。

1.1 类型转换

toUInt(8|16|32|64)OrZero
toUInt(8|16|32|64)OrNull
toFloat(32|64)
toFloat(32|64)OrZero
toFloat(32|64)OrNull
toDate
toDateOrZero
toDateOrNull
toDateTime
toDateTimeOrZero
toDateTimeOrNull
toDecimal(32|64|128)

更多参考:https://clickhouse.com/docs/zh/sql-reference/functions/type-conversion-functions/

1.2 日期函数
SELECT
    toDateTime('2022-01-15 23:00:00') AS time,
    toDate(time) AS date_local,
    toDate(time, 'Asia/Yekaterinburg') AS date_yekat,
    toString(time, 'US/Samoa') AS time_samoa
    
--------------------------------------------------
┌─────────time─┬─date_local─┬─date_yekat─┬─time_samoa───┐

│ 2022-01-15 23:00:002022-01-152022-01-152022-01-15 04:00:00 │

└─────────────────────┴────────────┴────────┴
  • toYear

将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。

  • toQuarter

将Date或DateTime转换为包含季度编号的UInt8类型的数字。

  • toMonth

将Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。

  • toDayOfYear

将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。

  • toDayOfMonth

将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。

  • toDayOfWeek

将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。

  • toHour

将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。
这个函数假设如果时钟向前移动,它是一个小时,发生在凌晨2点,如果时钟被移回,它是一个小时,发生在凌晨3点(这并非总是如此 - 即使在莫斯科时钟在不同的时间两次改变)。

  • toMinute

将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。

  • toSecond

将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。
闰秒不计算在内。

  • toUnixTimestamp

对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳。
对于String参数:根据时区将输入字符串转换为日期时间(可选的第二个参数,默认使用服务器时区),并返回相应的unix时间戳。

1.3 条件函数
##### 创建表
create table if not exists tb_if(uid Int16, name String ,gender String)engine = TinyLog ;
##### 插入数据
insert into tb_if values(1,'zss1','M') ;
insert into tb_if values(2,'zss2','M') ;
insert into tb_if values(3,'zss3','F') ;
insert into tb_if values(4,'zss4','O') ;
insert into tb_if values(5,'zss5','F') ;
------------------------------------------
SELECT *,if(gender = 'M', '男', '女') FROM tb_if ;
------------------------------------------
SELECT *,multiIf(gender = 'M', '男', gender = 'F', '女', '保密') AS sex FROM tb_if;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yf26Ty8m-1666171378833)(ClickHouse.assets/image-20220119193156461.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-751nUsdQ-1666171378835)(ClickHouse.assets/image-20220119193232921.png)]

1.4 数组函数

数组的定义

  • SELECT range(2, 10, 2) – 定义一个范围数组

  • SELECT array(2 , 10 , 2) ; – 定义数组

  • SELECT arrayConcat([1, 2], [3, 4], [5, 6]) AS res – 合并数组

  • SELECT has([1, 2, NULL], NULL) --检查’arr’数组是否具有’elem’元素。

  • hasAny([1, 2], [3, 4]) --检查两个数组是否存在交集

  • SELECT indexOf([1,3,NULL,NULL],NULL) --返回数组中第一个’x’元素的索引(从1开始),如果’x’元素不存在在数组中,则返回0。

  • SELECT arrayPopBack([1, 2, 3]) AS res – 从数组中删除最后一项。

  • SELECT arrayPopFront([1, 2, 3]) AS res --从数组中删除第一项。

  • SELECT arrayPushBack([‘a’], ‘b’) AS res – 添加一个元素到数组的末尾。

  • SELECT arrayPushFront([‘b’], ‘a’) AS res – 将一个元素添加到数组的开头。

  • SELECT arrayResize([1], 3) – 更改数组的长度

  • SELECT arraySlice([1, 2, NULL, 4, 5], 2, 3) AS res --返回一个子数组,包含从指定位置的指定长度的元素。

  • SELECT arraySort([1, 3, 3, 0]); --排序(升序)

    SELECT arraySort((x) -> -x, [1, 2, 3]) as res; – 降序排序(lambda函数方式)

    SELECT arrayReverseSort([1, 3, 3, 0]); – 降序排序

    SELECT arrayZip([‘a’,‘b’],[1,2]) – 拉链操作

1.5 JSON函数
  • JSONHas(json[, indices_or_keys]…)
    如果JSON中存在该值,则返回1。
    如果该值不存在,则返回0。
    示例:

    select JSONHas('{"a": "hello", "b": [-100, 200.0, 300]}', 'b') = 1
    select JSONHas('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 4) = 0
    

    indices_or_keys可以是零个或多个参数的列表,每个参数可以是字符串或整数。

    • String = 按成员名称访问JSON对象成员。
    • 正整数 = 从头开始访问第n个成员/成员名称。
    • 负整数 = 从末尾访问第n个成员/成员名称。
  • JSONLength(json[, indices_or_keys]…)

    返回JSON数组或JSON对象的长度。

    如果该值不存在或类型错误,将返回0。

    示例:

    select JSONLength('{"a": "hello", "b": [-100, 200.0, 300]}', 'b') = 3
    select JSONLength('{"a": "hello", "b": [-100, 200.0, 300]}') = 2
    
  • JSONType(json[, indices_or_keys]…)

    返回JSON值的类型。

    如果该值不存在,将返回Null。

    示例:

    select JSONType('{"a": "hello", "b": [-100, 200.0, 300]}') = 'Object'
    select JSONType('{"a": "hello", "b": [-100, 200.0, 300]}', 'a') = 'String'
    select JSONType('{"a": "hello", "b": [-100, 200.0, 300]}', 'b') = 'Array'
    
  • JSONExtractUInt(json[, indices_or_keys]…)
    JSONExtractInt(json[, indices_or_keys]…)
    JSONExtractFloat(json[, indices_or_keys]…)
    JSONExtractBool(json[, indices_or_keys]…)

    解析JSON并提取值。这些函数类似于visitParam*函数。

    如果该值不存在或类型错误,将返回0。

    示例:

    select JSONExtractInt('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 1) = -100
    select JSONExtractFloat('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 2) = 200.0
    select JSONExtractUInt('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', -1) = 300
    
  • JSONExtractString(json[, indices_or_keys]…)

    解析JSON并提取字符串。此函数类似于visitParamExtractString函数。

    如果该值不存在或类型错误,则返回空字符串。

    该值未转义。如果unescaping失败,则返回一个空字符串。

    示例:

    select JSONExtractString('{"a": "hello", "b": [-100, 200.0, 300]}', 'a') = 'hello'
    select JSONExtractString('{"abc":"\\n\\u0000"}', 'abc') = '\n\0'
    select JSONExtractString('{"abc":"\\u263a"}', 'abc') = '☺'
    select JSONExtractString('{"abc":"\\u263"}', 'abc') = ''
    select JSONExtractString('{"abc":"hello}', 'abc') = ''
    
  • JSONExtract(json[, indices_or_keys…], Return_type)

    解析JSON并提取给定ClickHouse数据类型的值。

    这是以前的JSONExtract函数的变体。 这意味着JSONExtract(…, ‘String’)返回与JSONExtractString()返回完全相同。JSONExtract(…, ‘Float64’)返回于JSONExtractFloat()`返回完全相同。

    示例:

    SELECT JSONExtract('{"a": "hello", "b": [-100, 200.0, 300]}', 'Tuple(String, Array(Float64))') = ('hello',[-100,200,300])
    SELECT JSONExtract('{"a": "hello", "b": [-100, 200.0, 300]}', 'Tuple(b Array(Float64), a String)') = ([-100,200,300],'hello')
    SELECT JSONExtract('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 'Array(Nullable(Int8))') = [-100, NULL, NULL]
    SELECT JSONExtract('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 4, 'Nullable(Int64)') = NULL
    SELECT JSONExtract('{"passed": true}', 'passed', 'UInt8') = 1
    SELECT JSONExtract('{"day": "Thursday"}', 'day', 'Enum8(\'Sunday\' = 0, \'Monday\' = 1, \'Tuesday\' = 2, \'Wednesday\' = 3, \'Thursday\' = 4, \'Friday\' = 5, \'Saturday\' = 6)') = 'Thursday'
    SELECT JSONExtract('{"day": 5}', 'day', 'Enum8(\'Sunday\' = 0, \'Monday\' = 1, \'Tuesday\' = 2, \'Wednesday\' = 3, \'Thursday\' = 4, \'Friday\' = 5, \'Saturday\' = 6)') = 'Friday'
    
  • JSONExtractKeysAndValues(json[, indices_or_keys…], Value_type)

    从JSON中解析键值对,其中值是给定的ClickHouse数据类型。

    示例:

    SELECT JSONExtractKeysAndValues('{"x": {"a": 5, "b": 7, "c": 11}}', 'x', 'Int8') = [('a',5),('b',7),('c',11)];
    
  • JSONExtractRaw(json[, indices_or_keys]…)

    返回JSON的部分。

    如果部件不存在或类型错误,将返回空字符串。

    示例:

    select JSONExtractRaw('{"a": "hello", "b": [-100, 200.0, 300]}', 'b') = '[-100, 200.0, 300]'
    
1.6 空值处理函数

isNull
检查参数是否为NULL。

SELECT x FROM t_null WHERE isNull(y)

isNotNull
检查参数是否不为 NULL.

SELECT x FROM t_null WHERE isNotNull(y)

ifNull
如果第一个参数为«NULL»,则返回第二个参数的值。

SELECT ifNull('a', 'b')

┌─ifNull('a', 'b')─┐
│ a                │
└──────────────────┘
----------------------------
SELECT ifNull(NULL, 'b')

┌─ifNull(NULL, 'b')─┐
│ b                 │
└───────────────────┘

nullIf
如果参数相等,则返回NULL。

SELECT nullIf(1, 1)

┌─nullIf(1, 1)─┐
│         ᴺᵁᴸᴸ │
└──────────────┘
-----------------------------
SELECT nullIf(1, 2)

┌─nullIf(1, 2)─┐
│            1 │
└──────────────┘

toTypeName

将参数的类型转换为Nullable。

SELECT toTypeName(10)

┌─toTypeName(10)─┐
│ UInt8          │
└────────────────┘
--------------------------------
SELECT toTypeName(toNullable(10))

┌─toTypeName(toNullable(10))─┐
│ Nullable(UInt8)            │
└────────────────────────────┘
1.7 URL解析函数
qianfeng01 :) select firstSignificantSubdomain('https://news.yandex.ru/');
qianfeng01 :) select cutToFirstSignificantSubdomain('https://news.yandex.com.tr/');

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZfabR8XD-1666171378837)(ClickHouse.assets/image-20220119200331519.png)]

更多参考:https://clickhouse.com/docs/zh/sql-reference/functions/url-functions/

1.8 行列转换函数
create table tb_array_join(id Int8 , msg String) engine=TinyLog ;
insert into tb_array_join  values (1, 'a,b,c') ;
insert into tb_array_join  values (2, 'h,j,k') ;
---------------------------------行转列
select id, arrayJoin(splitByChar(',',msg)) from tb_array_join;
SELECT id, arrayJoin(splitByChar(',', msg)) FROM tb_array_join;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92rwxSR1-1666171378838)(ClickHouse.assets/image-20220119200837220.png)]

------------将列转换成数组
select  groupArray(msg) from tb_array_join ; 
----------------------------------------列转行
select  arrayStringConcat(groupArray(msg),'|') from tb_array_join ; 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fg8zDc17-1666171378838)(ClickHouse.assets/image-20220119201106559.png)]

1.9 位图函数

bitmapBuild
从无符号整数数组构建位图对象。

SELECT bitmapBuild([1, 2, 3, 4, 5]) AS res

bitmapToArray
将位图转换为整数数组。

SELECT bitmapToArray(bitmapBuild([1, 2, 3, 4, 5])) AS res

bitmapSubsetInRange
将位图指定范围(不包含range_end)转换为另一个位图。

SELECT bitmapToArray(bitmapSubsetInRange(bitmapBuild([25,26,27,28,29,30,31,32,33,100,200,500]), toUInt32(30), toUInt32(200))) AS res

bitmapContains
检查位图是否包含指定元素。

SELECT bitmapContains(bitmapBuild([1,5,7,9]), toUInt32(9)) AS res

bitmapHasAny
与hasAny(array,array)类似,如果位图有任何公共元素则返回1,否则返回0。
对于空位图,返回0。

SELECT bitmapHasAny(bitmapBuild([1,2,3]),bitmapBuild([3,4,5])) AS res

bitmapHasAll
与hasAll(array,array)类似,如果第一个位图包含第二个位图的所有元素,则返回1,否则返回0。
如果第二个参数是空位图,则返回1。

SELECT bitmapHasAll(bitmapBuild([1,2,3]),bitmapBuild([3,4,5])) AS res

bitmapAnd
为两个位图对象进行与操作,返回一个新的位图对象。

SELECT bitmapToArray(bitmapAnd(bitmapBuild([1,2,3]),bitmapBuild([3,4,5]))) AS res

bitmapOr
为两个位图对象进行或操作,返回一个新的位图对象。

SELECT bitmapToArray(bitmapOr(bitmapBuild([1,2,3]),bitmapBuild([3,4,5]))) AS res

bitmapXor
为两个位图对象进行异或操作,返回一个新的位图对象。

SELECT bitmapToArray(bitmapXor(bitmapBuild([1,2,3]),bitmapBuild([3,4,5]))) AS res

bitmapAndnot
计算两个位图的差异,返回一个新的位图对象。

SELECT bitmapToArray(bitmapAndnot(bitmapBuild([1,2,3]),bitmapBuild([3,4,5]))) AS res

bitmapCardinality
返回一个UInt64类型的数值,表示位图对象的基数。

SELECT bitmapCardinality(bitmapBuild([1, 2, 3, 4, 5])) AS res

bitmapMin
返回一个UInt64类型的数值,表示位图中的最小值。如果位图为空则返回UINT32_MAX。

SELECT bitmapMin(bitmapBuild([1, 2, 3, 4, 5])) AS res

bitmapMax
返回一个UInt64类型的数值,表示位图中的最大值。如果位图为空则返回0。

SELECT bitmapMax(bitmapBuild([1, 2, 3, 4, 5])) AS res

bitmapAndCardinality
为两个位图对象进行与操作,返回结果位图的基数。

SELECT bitmapAndCardinality(bitmapBuild([1,2,3]),bitmapBuild([3,4,5])) AS res;

bitmapOrCardinality
为两个位图进行或运算,返回结果位图的基数。

SELECT bitmapOrCardinality(bitmapBuild([1,2,3]),bitmapBuild([3,4,5])) AS res;

bitmapXorCardinality
为两个位图进行异或运算,返回结果位图的基数。

SELECT bitmapXorCardinality(bitmapBuild([1,2,3]),bitmapBuild([3,4,5])) AS res;
1.10 字符串函数

empty

对于空字符串返回1,对于非空字符串返回0。
结果类型是UInt8。
如果字符串包含至少一个字节,则该字符串被视为非空字符串,即使这是一个空格或空字符。
该函数也适用于数组。

notEmpty

对于空字符串返回0,对于非空字符串返回1。
结果类型是UInt8。
该函数也适用于数组。

length

返回字符串的字节长度。
结果类型是UInt64。
该函数也适用于数组。

lengthUTF8

假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值(不会抛出异常)。
结果类型是UInt64。

lower, lcase

将字符串中的ASCII转换为小写。

upper, ucase

将字符串中的ASCII转换为大写。

lowerUTF8

将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。
同时函数不检测语言。因此对土耳其人来说,结果可能不完全正确。
如果UTF-8字节序列的长度对于代码点的大写和小写不同,则该代码点的结果可能不正确。
如果字符串包含一组非UTF-8的字节,则将引发未定义行为。
upperUTF8

将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。
同时函数不检测语言。因此对土耳其人来说,结果可能不完全正确。
如果UTF-8字节序列的长度对于代码点的大写和小写不同,则该代码点的结果可能不正确。
如果字符串包含一组非UTF-8的字节,则将引发未定义行为。

reverse

反转字符串。

reverseUTF8

以Unicode字符为单位反转UTF-8编码的字符串。如果字符串不是UTF-8编码,则可能获取到一个非预期的结果(不会抛出异常)。

format(pattern, s0, s1, …)

使用常量字符串pattern格式化其他参数。pattern字符串中包含由大括号{}包围的«替换字段»。 未被包含在大括号中的任何内容都被视为文本内容,它将原样保留在返回值中。 如果你需要在文本内容中包含一个大括号字符,它可以通过加倍来转义:{{和{{ ‘}}’ }}。 字段名称可以是数字(从零开始)或空(然后将它们视为连续数字)

SELECT format('{1} {0} {1}', 'World', 'Hello')

┌─format('{1} {0} {1}', 'World', 'Hello')─┐
│ Hello World Hello                       │
└─────────────────────────────────────────┘

SELECT format('{} {}', 'Hello', 'World')

┌─format('{} {}', 'Hello', 'World')─┐
│ Hello World                       │
└───────────────────────────────────┘

concat(s1, s2, …)

将参数中的多个字符串拼接,不带分隔符。

substring(s,offset,length),mid(s,offset,length),substr(s,offset,length)

以字节为单位截取指定位置字符串,返回以’offset’位置为开头,长度为’length’的子串。’offset’从1开始(与标准SQL相同)。’offset’和’length’参数必须是常量。

substringUTF8(s,offset,length)

与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。

base64Encode(s)

将字符串’s’编码成base64

base64Decode(s)

使用base64将字符串解码成原始字符串。如果失败则抛出异常。

endsWith(s,后缀)

返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。

startsWith(s,前缀)

返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。

trimLeft(s)

返回一个字符串,用于删除左侧的空白字符。

trimRight(s)

返回一个字符串,用于删除右侧的空白字符。

1.11 数学函数
e() 

返回一个接近数学常量e的Float64数字。
pi() 

返回一个接近数学常量π的Float64数字。
exp(x) 

接受一个数值类型的参数并返回它的指数。
log(x),ln(x) 

接受一个数值类型的参数并返回它的自然对数。
exp2(x) 

接受一个数值类型的参数并返回它的2的x次幂。
log2(x) 

接受一个数值类型的参数并返回它的底2对数。
exp10(x) 

接受一个数值类型的参数并返回它的10的x次幂。
log10(x) 

接受一个数值类型的参数并返回它的底10对数。
sqrt(x) 

接受一个数值类型的参数并返回它的平方根。
cbrt(x) 

接受一个数值类型的参数并返回它的立方根。
erf(x) 

如果’x’是非负数,那么erf(x / σ√2)是具有正态分布且标准偏差为«σ»的随机变量的值与预期值之间的距离大于«x»。

示例 (三西格玛准则):

SELECT erf(3 / sqrt(2))

┌─erf(divide(3, sqrt(2)))─┐
│      0.9973002039367398 │
└─────────────────────────┘

erfc(x) 

接受一个数值参数并返回一个接近1 - erf(x)的Float64数字,但不会丢失大«x»值的精度。
lgamma(x) 

返回x的绝对值的自然对数的伽玛函数。
tgamma(x) 

返回x的伽玛函数。
sin(x) 

返回x的三角正弦值。
cos(x) 

返回x的三角余弦值。
tan(x) 

返回x的三角正切值。
asin(x) 

返回x的反三角正弦值。
acos(x) 

返回x的反三角余弦值。
atan(x) 

返回x的反三角正切值。
pow(x,y),power(x,y) 

接受x和y两个参数。返回x的y次方。
intExp2 

接受一个数值类型的参数并返回它的2的x次幂(UInt64)。
intExp10 

接受一个数值类型的参数并返回它的10的x次幂(UInt64)。
1.12 聚合函数

参考:https://clickhouse.com/docs/zh/sql-reference/aggregate-functions/reference

1.13 表函数

file(path, format, structure)

注意:数据文件必须在指定的目录下 /var/lib/clickhouse/user_files

[root@qianfeng01 user_files]# cat demo.csv 
1,zss,25
2,lss,34
3,ada,19
SELECT *
FROM file('demo.csv', 'CSV', 'id Int8,name String , age UInt8')
--------------------------------------
┌─id─┬─name─┬─age─┐
│  1 │ zss  │  25 │
│  2 │ lss  │  34 │
│  3 │ ada  │  19 │
└── ┴─────┴────┘

**numbers **

numbers(N) – 返回一个包含单个 ‘number’ 列(UInt64)的表,其中包含从0到N-1的整数。
numbers(N, M) - 返回一个包含单个 ‘number’ 列(UInt64)的表,其中包含从N到(N+M-1)的整数。

以下查询是等价的:

SELECT * FROM numbers(10);
SELECT * FROM numbers(0, 10);
SELECT * FROM system.numbers LIMIT 10;
示例:
-- 生成2010-01-01至2010-12-31的日期序列
select toDate('2010-01-01') + number as d FROM numbers(365);

url(URL, format, structure)

url 函数可用于对URL表中的数据进行 SELECTINSERT 的查询中。

-- 示例

-- 获取一个表的前3行,该表是从HTTP服务器获取的包含 String 和 UInt32 类型的列,以CSV格式返回。

SELECT * FROM url('http://127.0.0.1:12345/', CSV, 'column1 String, column2 UInt32') LIMIT 3;

-- 将 URL 的数据插入到表中:

CREATE TABLE test_table (column1 String, column2 UInt32) ENGINE=Memory;
INSERT INTO FUNCTION url('http://127.0.0.1:8123/?query=INSERT+INTO+test_table+FORMAT+CSV', 'CSV', 'column1 String, column2 UInt32') VALUES ('http interface', 42);

SELECT * FROM test_table;

jdbc
jdbc(datasource, schema, table) -返回通过JDBC驱动程序连接的表。

-- 示例
mysql('host:port', 'database', 'table', 'user', 'password'[, replace_query, 'on_duplicate_clause']);

SELECT *
FROM mysql('qianfeng01:3306', 'db_qf_ch', 'emp', 'root', 'root')

HDFS

SELECT * FROM hdfs('hdfs://qianfeng:9000/demo.csv', 'CSV', 'id Int8 ,name String , age Int8')
1.14 多表关联
  • IN 代替 JOIN

    当多表联查时,查询的数据仅从其中一张表出时,可考虑用 IN 操作而不是 JOIN

    -- 例如:
    insert into name select a.* from t1 a where a. id in (select id from t2);
    
  • 大小表 JOIN

    多表 join 时要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较,ClickHouse 中无论是 Left join 、Right join 还是 Inner join 永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表。

    -- 小表在右
    insert into table name select a.* from t1 a left join t2 b on a.id=b.id;
    -- 大表在右
    insert into table name select a.* from t2 b left join t1 a on a.id=b.id;
    
1.15 谓词下推

当 group by 有 having 子句,但是没有 with cube、with rollup 或者 with totals 修饰的时候,having 过滤会下推到 where 提前过滤。例如下面的查询,HAVING name 变成了 WHERE name,在 group by 之前过滤:

SELECT id FROM t1 GROUP BY id HAVING id = '10';

-- 更改语句
SELECT id FROM t1 where id = '10' GROUP BY id;

子查询

SELECT *
FROM (
 SELECT id
 FROM t1
)
WHERE id = '10'

-- 更改语句
SELECT id
FROM 
(
 SELECT id
 FROM t1
 WHERE id = '10'
)
WHERE id = '10'
1.16 优化函数
  • uniqCombined 替代 distinct

    性能可提升 10 倍以上,uniqCombined 底层采用类似 HyperLogLog 算法实现,能接收 2%左右的数据误差,可直接使用这种去重方式提升查询性能。Count(distinct )会使用 uniqExact精确去重。

    不建议在千万级不同数据上执行 distinct 去重查询,改为近似去重 uniqCombined

    --使用distinct:
    select count(distinct rand()) from t1;
    --使用uniqCombined:
    SELECT uniqCombined(rand()) from t1;
    
  • Prewhere 替代 where

    Prewhere 和 where 语句的作用相同,用来过滤数据。不同之处在于 prewhere 只支持MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取 select 声明的列字段来补全其余属性。

    当查询列明显多于筛选列时使用 Prewhere 可十倍提升查询性能,Prewhere 会自动优化执行过滤阶段的数据读取方式,降低 io 操作。

    在某些场合下,prewhere 语句比 where 语句处理的数据量更少性能更高。

    注意:默认情况下, where 条件会自动优化成 prewhere

    -- 设置参数关闭这个功能
    set optimize_move_to_prewhere=0;
    
  • 聚合函数消除

    如果对聚合键,也就是 group by key 使用 min、max、any 聚合函数,则将函数消除,

    例如:

    SELECT
     sum(id * 2),
     max(id),
     max(age)
    FROM t1
    GROUP BY id
    
    -- 更改语句
    
    SELECT 
     sum(id) * 2,
     max(age),
     id
    FROM t1
    GROUP BY id
    
  • 删除重复的 order by key

    SELECT *
    FROM t1
    ORDER BY
    id ASC,
    id ASC,
    age ASC,
    age ASC
    -- 更改语句
    select
    ……
    FROM t1
    ORDER BY 
    id ASC,
    id ASC
    

2 架构原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JEd2v7T3-1666171378840)(ClickHouse.assets/image-20220119204637294.png)]

2.1 Column与Field

Column和Field是ClickHouse数据最基础的映射单元。作为一款百分之百的列式存储数据库,ClickHouse按列存储数据,内存中的一列数据由一个Column对象表示。Column对象分为接口和实现两个部分,在IColumn接口对象中,定义了对数据进行各种关系运算的方法,例如插入数据的insertRangeFrom和insertFrom方法、用于分页的cut,以及用于过滤的filter方法等。而这些方法的具体实现对象则根据数据类型的不同,由相应的对象实现,例如ColumnString、ColumnArray和ColumnTuple等。在大多数场合,ClickHouse都会以整列的方式操作数据,但凡事也有例外。如果需要操作单个具体的数值 ( 也就是单列中的一行数据 ),则需要使用Field对象,Field对象代表一个单值。与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式。在Field对象内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。

一个列中的数据一般是以文件单独存储的

2.2 DataType

数据的序列化和反序列化工作由DataType负责。IDataType接口定义了许多正反序列化的方法,它们成对出现,例如serializeBinary和deserializeBinary、serializeTextJSON和deserializeTextJSON等,涵盖了常用的二进制、文本、JSON、XML、CSV和Protobuf等多种格式类型。IDataType也使用了泛化的设计模式,具体方法的实现逻辑由对应数据类型的实例承载,例如DataTypeString、DataTypeArray及DataTypeTuple等。

2.3 BloCK与BloCK流

ClickHouse内部的数据操作是面向BloCK对象进行的,并且采用了流的形式。虽然Column和Filed组成了数据的基本映射单元,但对应到实际操作,它们还缺少了一些必要的信息,比如数据的类型及列的名称。于是ClickHouse设计了BloCK对象,BloCK对象可以看作数据表的子集。BloCK对象的本质是由数据对象、数据类型和列名称组成的三元组,即Column、DataType及列名称字符串。Column提供了数据的读取能力,而DataType知道如何正反序列化,所以BloCK在这些对象的基础之上实现了进一步的抽象和封装,从而简化了整个使用的过程,仅通过BloCK对象就能完成一系列的数据操作。在具体的实现过程中,BloCK并没有直接聚合Column和DataType对象,而是通过ColumnWithTypeAndName对象进行间接引用。

有了BloCK对象这一层封装之后,对BloCK流的设计就是水到渠成的事情了。流操作有两组顶层接口:IBloCKInputStream负责数据的读取和关系运算,IBloCKOutputStream负责将数据输出到下一环节。BloCK流也使用了泛化的设计模式,对数据的各种操作最终都会转换成其中一种流的实现。IBloCKInputStream接口定义了读取数据的若干个read虚方法,而具体的实现逻辑则交由它的实现类来填充。

IBloCKInputStream接口总共有60多个实现类,它们涵盖了ClickHouse数据摄取的方方面面。这些实现类大致可以分为三类:第一类用于处理数据定义的DDL操作,例如DDLQueryStatusInputStream等;第二类用于处理关系运算的相关操作,例如LimitBloCKInput-Stream、JoinBloCKInputStream及AggregatingBloCKInputStream等;第三类则是与表引擎呼应,每一种表引擎都拥有与之对应的BloCKInputStream实现,例如MergeTreeBaseSelect-BloCKInputStream ( MergeTree表引擎 )、TinyLogBloCKInputStream ( TinyLog表引擎 ) 及KafkaBloCKInputStream ( Kafka表引擎 ) 等。

IBloCKOutputStream的设计与IBloCKInputStream如出一辙。IBloCKOutputStream接口同样也定义了若干写入数据的write虚方法。它的实现类比IBloCKInputStream要少许多,一共只有20多种。这些实现类基本用于表引擎的相关处理,负责将数据写入下一环节或者最终目的地,例如MergeTreeBloCKOutputStream 、TinyLogBloCKOutputStream及StorageFileBloCK-OutputStream等。

2.4 Table

在数据表的底层设计中并没有所谓的Table对象,它直接使用IStorage接口指代数据表。表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现,例如IStorageSystemOneBloCK ( 系统表 )、StorageMergeTree ( 合并树表引擎 ) 和StorageTinyLog ( 日志表引擎 ) 等。IStorage接口定义了DDL ( 如ALTER、RENAME、OPTIMIZE和DROP等 ) 、read和write方法,它们分别负责数据的定义、查询与写入。在数据查询时,IStorage负责根据AST查询语句的指示要求,返回指定列的原始数据。后续对数据的进一步加工、计算和过滤,则会统一交由Interpreter解释器对象处理。对Table发起的一次操作通常都会经历这样的过程,接收AST查询语句,根据AST返回指定列的数据,之后再将数据交由Interpreter做进一步处理。

2.5 Parser与Interpreter

Parser和Interpreter是非常重要的两组接口:Parser分析器负责创建AST对象;而Interpreter解释器则负责解释AST,并进一步创建查询的执行管道。它们与IStorage一起,串联起了整个数据查询的过程。Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析。例如,有负责解析DDL查询语句的ParserRenameQuery、ParserDropQuery和ParserAlterQuery解析器,也有负责解析INSERT语句的ParserInsertQuery解析器,还有负责SELECT语句的ParserSelectQuery等。

Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行’业务逻辑’ ( 例如分支判断、设置参数、调用接口等 );最终返回IBloCK对象,以线程的形式建立起一个查询执行管道。

2.6 分片与副本

ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成。这种分层的概念,在一些流行的分布式系统中十分普遍。例如,在Elasticsearch的概念中,一个索引由分片和副本组成,副本可以看作一种特殊的分片。如果一个索引由5个分片组成,副本的基数是1,那么这个索引一共会拥有10个分片 ( 每1个分片对应1个副本 )。

如果你用同样的思路来理解ClickHouse的分片,那么很可能会在这里栽个跟头。ClickHouse的某些设计总是显得独树一帜,而集群与分片就是其中之一。这里有几个与众不同的特性。

ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片、1副本,则至少需要部署2个服务节点。

分片只是一个逻辑概念,其物理承载还是由副本承担的。

3 ClickHouse客户端

3.1 客户端工具
  • 访问页面:http://ui.tabix.io/#!/login

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGMIorTO-1666171378841)(ClickHouse.assets/image-20220119205918071.png)]

  • DBeaver

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KUP9CJoQ-1666171378842)(ClickHouse.assets/image-20220119210105391.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2PqD9DU-1666171378842)(ClickHouse.assets/image-20220119210312220.png)]

按照上述操作后可以先测试连接,正常情况下都是可以连接上的,然后确定就可以了。

3.2 客户端API

导入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.35</version>
</dependency>
<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.2.4</version>
</dependency>

编写代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 第一种,使用默认的读取数据库的方式
 */
public class demo {
    public static void main(String[] args) throws Exception {
        Class.forName("ru.yandex.clickhouse.ClickHouseDriver");
        String address = "jdbc:clickhouse://node1:8123/default";
        Connection conn = DriverManager.getConnection(address,"default","123456");
        Statement st = conn.createStatement();
        ResultSet resultSet = st.executeQuery("select * from ttt");
        while(resultSet.next()){
            String uid = resultSet.getString("id");
            String value = resultSet.getString("name");
            System.out.println(uid+"---"+value);
        }
        resultSet.close();
        st.close() ;
        conn.close();
    }
}

使用ClickHouse内部Connection

import ru.yandex.clickhouse.{ClickHouseConnection, ClickHouseDataSource}
import ru.yandex.clickhouse.settings.ClickHouseProperties

object demo1 {
  def main(args: Array[String]): Unit = {
    // 加载连接
    Class.forName("ru.yandex.clickhouse.ClickHouseDriver")
    // 创建配置 使用CH的Pro
    val properties = new ClickHouseProperties
    properties.setUser("default")
    properties.setPassword("123456")
    // 使用CH的Driver加载连接
    val dataSource = new ClickHouseDataSource("jdbc:clickhouse://node1:8123/default", properties)
    // 获取Connection
    val conn:ClickHouseConnection = dataSource.getConnection
    // 执行SQL
    val st = conn.createStatement
    val resultSet = st.executeQuery("select * from ttt")
    while (resultSet.next) {
      val uid = resultSet.getString("id")
      val value = resultSet.getString("name")
      System.out.println(uid + "---" + value)
    }
    resultSet.close()
    st.close()
    conn.close()
  }
}

4 ClickHouse监控及备份

注意:要想实现ClickHouse监控功能,首先要有prometheus和Grafana两个组件,这两个实在采集监控项目中安装实现的,但是要修改一些配置,如果没有安装请按照下面的步骤将其安装并修改配置即可。

4.1 监控

安装Prometheus

# 创建目录,放置Prometheus
mkdir -p /opt/soft/prometheus && cd /opt/soft/prometheus
# 下载
wget http://doc.qfbigdata.com/qf/project/soft/prometheus/prometheus-2.17.1.linux-amd64.tar.gz
# 解压
tar -xvzf prometheus-2.17.1.linux-amd64.tar.gz
# 创建软连
ln -s prometheus-2.17.1.linux-amd64 prom
# 创建目录,存放Prometheus 拉取过来的数据,我们这里选择local storage
mkdir -p /data/prometheus/data/
# 修改配置文件
vi /opt/soft/prometheus/prom/prometheus.yml
# 添加采集监控配置
  - job_name: 'clickhouse-1'
    static_configs:
    - targets: ['qianfeng01:9363']
# 启动Prometheus
/opt/soft/prometheus-2.29.1/prometheus --storage.tsdb.path="/data/prometheus/data/"  --log.level=debug --web.enable-lifecycle --web.enable-admin-api  --config.file=/opt/soft/prometheus-2.29.1/prometheus.yml &nohup ./pushgateway 
--web.listen-address :9091 > ./pushgateway.log 2>&1 &
# 至此Prometheus就可以正常工作了
# 访问ip:9090页面即可
# scrape_configs 配置块:配置采集目标相关, prometheus 监视的目标。Prometheus 自身的运行信息可以通过 HTTP 访问,所以 Prometheus 可以监控自己的运行数据。
# ➢ job_name:监控作业的名称
# ➢ static_configs:表示静态目标配置,就是固定从某个 target 拉取数据
# ➢ targets:指定监控的目标,其实就是从哪儿拉取数据。Prometheus会从http://qianfeng01:9090/metrics 上拉取数据。
# Prometheus 是可以在运行时自动加载配置的。启动时需要添加:--web.enable-lifecycle
# prometheus 是 up 状态,表示安装启动成功了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKQ5lalz-1666171378843)(ClickHouse.assets/image-20220120094623182.png)]

安装Grafana

# 我们采用RPM安装
mkdir -p  /opt/soft/grafana/ && cd /opt/soft/grafana/
# 下载
wget http://doc.qfbigdata.com/qf/project/soft/grafana/grafana-6.7.3.linux-amd64.tar.gz
# 解压
tar -xvzf grafana-6.7.3.linux-amd64.tar.gz
# 创建软连
ln -s grafana-6.7.3 graf
# copy 一个配置,对于grafana我们不做更多的配置说明,直接copy一个默认的配置
cp  /opt/soft/grafana/graf/conf/sample.ini  /opt/soft/grafana/graf/conf/grafana.ini
# 启动
/opt/soft/grafana-8.1.2/bin/grafana-server -config /opt/soft/grafana-8.1.2/grafconf/grafana.ini &nohup ./bin/grafana-server web > ./grafana.log 2>&1 &
# 或者进进入目录启动 /opt/soft/grafana/graf
[root@qianfeng01 graf]# ./bin/grafana-server -config conf/grafana.ini &

# 经过上述步骤,grafana已经启动,我们可以通过默认配置的3000端口访问grafan了
# 在浏览器中,输入 自己ip地址:3000 就可以看到grafna 页面了
# 默认的用户名和密码都是: admin
# 输入 用户名密码后,grafan会让你修改你的密码,自己修改一个即可,点击保存后,就可以看到如下界面, 至此Grafna已经成功机器,接下来我们为grafana配置我们的Prometheus数据源

配置Prometheus集成Grafana

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsNxVzkV-1666171378844)(ClickHouse.assets/image-20220120095951416.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blLc9vkg-1666171378844)(ClickHouse.assets/image-20220120100028040.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AztP4deM-1666171378845)(ClickHouse.assets/image-20220120100200080.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rrfd71Eg-1666171378846)(ClickHouse.assets/image-20220120100312223.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lrBNEMxo-1666171378847)(ClickHouse.assets/image-20220120100337516.png)]

到此配置集成就完成了!!!

配置ClickHouse的Metrics插件服务

编辑/etc/clickhouse-server/config.xml,打开如下配置:

 <prometheus>
 <endpoint>/metrics</endpoint>
 <port>9363</port>
 <metrics>true</metrics>
 <events>true</events>
 <asynchronous_metrics>true</asynchronous_metrics>
 <status_info>true</status_info>
 </prometheus>

重新启动ClickHouse

clickhouse-server --config-file=/etc/clickhouse-server/config.xml

查看9090页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gWIrSkiO-1666171378847)(ClickHouse.assets/image-20220120101152447.png)]

浏览器打开: http://qianfeng01:9363/metrics(会看到很多ClickHouse的信息,说明启动成功)

添加监控

手动一个个添加 Dashboard 比较繁琐,Grafana 社区鼓励用户分享 Dashboard,通过https://grafana.com/dashboards 网站,可以找到大量可直接使用的 Dashboard 模板。

Grafana 中所有的 Dashboard 通过 JSON 进行共享,下载并且导入这些 JSON 文件,就可以直接使用这些已经定义好的 Dashboard。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wbh4Y2MG-1666171378848)(ClickHouse.assets/image-20220120102734978.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1p58oDJS-1666171378849)(ClickHouse.assets/image-20220120102833055.png)]

到此ClickHouse监控配置完成!!!

4.4.2 备份

官网:https://clickhouse.tech/docs/en/operations/backup/

使用 clickhouse-backup

我们可以使用 Clickhouse 的备份工具 clickhouse-backup 帮我们自动化实现备份恢复功能。

工具地址:https://github.com/AlexAkulov/clickhouse-backup/

上传解压

[root@qianfeng01 soft]# rpm -ivh clickhouse-backup-1.2.2-1.x86_64.rpm

修改配置文件

[root@qianfeng01 soft]# vim /etc/clickhouse-backup/config.yml.example

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nMEaxyAS-1666171378849)(ClickHouse.assets/image-20220120104256218.png)]

创建备份

查看可用命令

[root@qianfeng01 soft]# clickhouse-backup help

显示要备份的表

[root@qianfeng01 soft]# clickhouse-backup tables

创建备份

[root@qianfeng01 soft]#  clickhouse-backup create

查看现有的本地备份

[root@qianfeng01 soft]# clickhouse-backup list

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmAt3Hqn-1666171378850)(ClickHouse.assets/image-20220120104821615.png)]

备份存储在中/var/lib/clickhouse/backup/BACKUPNAME。备份名称默认为时间戳,但是可以选择使用–name 标志指定备份名称。备份包含两个目录:一个“metadata”目录,其中包含重新创建架构所需的 DDL SQL 语句;以及一个“shadow”目录,其中包含作为 ALTER TABLE … FREEZE 操作结果的数据。

数据恢复

进入到**/var/lib/clickhouse/backup**,查看要进行恢复的文件名,执行如下命令即可恢复

[root@qianfeng01 clickhouse]# clickhouse-backup restore ‘文件名’

参数说明
--schema 只还原表结构。
--data   只还原数据。
--table  备份(或还原)特定表。也可以使用一个正则表达式,例如,针对特定的数据库:--table=dbname.*。

其他注意事项说明

官方备份API文档:https://github.com/AlexAkulov/clickhouse-backup#api

  • 切勿更改文件夹/var/lib/clickhouse/backup 的权限,可能会导致数据损坏。

  • 保存周期:

    backups_to_keep_local,本地保存周期,单位天。

    backups_to_keep_remote,远程存储保存周期,单位天 。

    0 均表示不删除

4.3 参数配置

内存配置

参数说明
max_table_size_to_drop此参数在 config.xml 中,应用于需要删除表或分区的情况,默认是50GB,意思是如果删除 50GB 以上的分区表会失败。建议修改为 0,这样不管多大的分区表都可以删除。
max_bytes_before_external_group_by一般按照 max_memory_usage 的一半设置内存,当 group 使用内存超过阈值后会刷新到磁盘进行。建议 50GB。
max_memory_usage此参数在 users.xml 中,表示单次 Query 占用内存最大值,该值可以设置的比较大,这样可以提升集群查询的上限。比如 128G 内存的机器,设置为 100GB。
max_bytes_before_external_sort当 order by 已使用 max_bytes_before_external_sort 内存就进行溢写磁盘(基于磁盘排序),如果不设置该值,那么当内存不够时直接抛错,设置了该值 order by 可以正常完成,但是速度相对存内存来说肯定要慢点(实测慢的非常多,无法接受)。

CUP配置

参数说明
background_pool_size后台线程池的大小,merge 线程就是在该线程池中执行,该线程池不仅仅是给 merge 线程用的,默认值 16,允许的前提下建议改成 cpu 个数的 2 倍(线程数)。
max_threads设置单个查询所能使用的最大 cpu 个数,默认是 cpu 核数
max_concurrent_queries最大并发处理的请求数(包含 select,insert 等),默认值 100,推荐 150(不够再加)~300。
background_distributed_schedule_pool_size设置为分布式发送执行后台任务的线程数,默认 16,建议改成 cpu个数的 2 倍(线程数)。
background_schedule_pool_size执行后台任务(复制表、Kafka 流、DNS 缓存更新)的线程数。默认 128,建议改成 cpu 个数的 2 倍(线程数)。

5.实战应用

实战案例一

统计N天营销的数据

数据样板

a,2020-02-05,200
a,2020-02-06,300
a,2020-02-07,200
a,2020-02-08,400
a,2020-02-10,600
a,2020-03-01,200
a,2020-03-02,300
a,2020-03-03,200
a,2020-03-04,400
a,2020-03-05,600
b,2020-02-05,200
b,2020-02-06,300
b,2020-02-08,200
b,2020-02-09,400
b,2020-02-10,600
c,2020-01-31,200
c,2020-02-01,300
c,2020-02-02,200
c,2020-02-03,400
c,2020-02-10,600

SQL答案

SELECT DISTINCT name
FROM 
(
  SELECT 
    name,
    count(*) AS cc
  FROM 
  (
    SELECT 
      *,
      subtractDays(value, row_number) AS diff
    FROM 
    (
      SELECT 
        name,
        value,
        row_number
      FROM 
      (
        SELECT 
          name,
          groupArray(dt) AS value_list,
          arrayEnumerate(value_list) AS index_list
        FROM 
        (
          SELECT *
          FROM hdfs('hdfs://qianfeng01:9000/data/shop/*.csv', CSV, 'name String , dt Date , cost Int32')
          ORDER BY dt ASC
        )
        GROUP BY name
      )
      ARRAY JOIN 
        value_list AS value,
        index_list AS row_number
      ORDER BY name ASC
    ) AS t
  ) AS t2
  GROUP BY 
    name,
    diff
  HAVING cc > 3
) AS t3
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小大数据

你的打赏是我活下去的动力哟~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值