Dune 已经正式推出了其团队基于Trino(https://trino.io/)自研的查询引擎Dune SQL。本文介绍Dune SQL的一些常见查询语法、注意事项和细节。
注:由于Dune已经宣布2023年下半年起将全面过渡到Dune SQL查询引擎,所以本篇教程将原有的所有Query全部升级到了Dune SQL 版本。
Dune SQL需要注意的书写语法要点有几个:
- Dune SQL 使用双引号来引用包含特殊字符或者本身是关键字的字段名或表名,如
"from", "to"
。 - Dune SQL的字符串类型和常用数值类型分别是
varchar
、double
和decimal(38, 0)
。 - Dune SQL 不支持隐式类型转换。比如,Dune SQL中,不能将
'2022-10-01'
直接与 block_time 进行比较,需要用date('2022-10-01')
等函数显式转换为日期后才能比较。不能直接将数值类型和字符串连接,要用cast(number_value as varchar)
转换为字符串后才能连接。
Dune 文档提供了一份比较详细的语法对照表表,链接是:Syntax Comparison,大家可以参考。下图列出了部分差异对照:
Dune SQL使用双引号
select "from" as address, gas_price, gas_used
from ethereum.transactions
where success = true
limit 10
Dune SQL 不支持字符串格式的日期值隐式转换为日期时间类型的值,必须使用显式转换。可以使用日期时间函数或者日期时间操作符。
- 使用日期值
Dune SQL使用date()函数
select block_time, hash, "from" as address, "to" as contract_address
from ethereum.transactions
where block_time >= date('2022-12-18')
limit 10
- 使用日期时间值
Dune SQL使用timestamp 操作符
select block_time, hash, "from" as address, "to" as contract_address
from ethereum.transactions
where block_time >= timestamp '2022-12-18 05:00:00'
limit 10
- 使用interval
Dune SQL使用interval '12' hour
select block_time, hash, "from" as address, "to" as contract_address
from ethereum.transactions
where block_time >= now() - interval '12' hour
limit 10
Dune SQL 查询中,地址和哈希值可以不放入单引号中直接使用,此时大小写不敏感,可以不显示转换为小写格式。
select block_time, hash, "from" as address, "to" as contract_address
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
and (
hash = 0x2a5ca5ff26e33bec43c7a0609670b7d7db6f7d74a14d163baf6de525a166ab10
or "from" = 0x76BE685c0C8746BBafECD1a578fcaC680Db8242E
)
Dune SQL中的字符串和常用数值类型是varchar
和double
。Dune SQL中的整数值默认是bigint
类型,在做一些大数字的乘法时,容易产生溢出错误,此时可以强制转换为double
类型或者decimal(38, 0)
类型。Dune SQL中进行整数除法也不会隐式转换为浮点数再进行相除,而是直接返回一个整数,这点也需要注意。
- 转换为字符串
Dune SQL
select block_time, hash, "from" as address, "to" as contract_address,
cast(value / 1e9 as varchar) || ' ETH' as amount_value,
format('%,.2f', value / 1e9) || ' ETH' as amount_value_format
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
and (
hash = 0x2a5ca5ff26e33bec43c7A0609670b7d7db6f7d74a14d163baf6de525a166ab10
or "from" = 0x76BE685c0C8746BBafECD1a578fcaC680Db8242E
)
检查上面的SQL输出,可以看到当将比较大或者比较小的数字直接cast()转换为字符串时,会被处理为科学计数法的输出格式,效果不太理想。使用format()
则可以精确控制输出的字符串的格式,所以推荐用这种方式。
- 转换为数值
注意,表erc20_ethereum.evt_Transfer
中,value
字段的类型是字符串。可以使用cast()
函数将其转换为double 或者 decimal(38, 0) 数值类型。
select evt_block_time, evt_tx_hash, "from", "to",
cast(value as double) as amount,
cast(value as decimal(38, 0)) as amount2
from erc20_ethereum.evt_Transfer
where evt_block_time >= date('2022-12-18') and evt_block_time < date('2022-12-19')
and evt_tx_hash in (
0x2a5ca5ff26e33bec43c7a0609670b7d7db6f7d74a14d163baf6de525a166ab10,
0xb66447ec3fe29f709c43783621cbe4d878cda4856643d1dd162ce875651430fc
)
如前所述,Dune SQL不支持隐式类型转换,当我们需要将两种不同类型的值进行比较或者执行某些操作的时候,就需要确保它们是相同的(兼容的)数据类型,如果不是,则需要使用相关的函数或者操作符进行显式的类型转换。否则可能会遇到类型不匹配相关的错误。这里再举一个简单例子:
Dune SQL未做类型转换时,下面的SQL会报错:
select 1 as val
union all
select '2' as val
Dune SQL显式类型转换,可以执行
select 1 as val
union all
select cast('2' as int) as val
当我们遇到类似"Error: Line 47:1: column 1 in UNION query has incompatible types: integer, varchar(1) at line 47, position 1."这种错误时,就需要处理相应字段的类型兼容问题。
Dune SQL 支持整数类型 int
和 bigint
,但是由于EVM等区块链不支持小数导致数值经常很大,比如当我们计算gas 费的时候,就可能遇到数值溢出的错误。下面的SQL,为了故意导致错误,我们将计算的gas fee乘以1000倍了:
select hash, gas_price * gas_used * 1000 as gas_fee
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
order by gas_used desc
limit 10
执行上面的SQL将会遇到错误:
Error: Bigint multiplication overflow: 15112250000000000 * 1000.
为了避免类型溢出错误,我们可以将第一个参数显式转换为double类型。下面的SQL可以正确执行:
select hash, cast(gas_price as double) * gas_used * 1000 as gas_fee
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
order by gas_used desc
limit 10
同样,如果两个数值是bigint 类型,二者相除默认返回的也是整数类型,小数部分会被舍弃。如果希望返回小数部分,可以将被除数显式转换为double类型。
select hash, gas_used, gas_limit,
gas_used / gas_limit as gas_used_percentage
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
limit 10
执行上面的SQL,gas_used_percentage的值将会是0或者1,小数部分被舍弃取整,显然这不是我们想要的结果。将被除数gas_used显式转换为double类型,可以得到正确结果:
select hash, gas_used, gas_limit,
cast(gas_used as double) / gas_limit as gas_used_percentage
from ethereum.transactions
where block_time >= date('2022-12-18') and block_time < date('2022-12-19')
limit 10
Dune SQL 定义了一组新的函数来处理将varbinary类型字符串转换到十进制数值的转换,字符串必须以0x
前缀开始。
select bytearray_to_uint256('0x00000000000000000000000000000000000000000000005b5354f3463686164c') as amount_raw
详细帮助可以参考:Byte Array to Numeric Functions
- 数值序列
Dune SQL生成数值序列的语法:
select num from unnest(sequence(1, 10)) as t(num)
-- select num from unnest(sequence(1, 10, 2)) as t(num) -- step 2
- 日期序列
Duen SQL使用unnest()
搭配sequence()
来生成日期序列值并转换为多行记录。
Dune SQL生成日期序列的语法:
select block_date from unnest(sequence(date('2022-01-01'), date('2022-01-31'))) as s(block_date)
-- select block_date from unnest(sequence(date('2022-01-01'), date('2022-01-31'), interval '7' day)) as s(block_date)
- Dune SQL 使用
cardinality()
查询数组大小。
Dune SQL语法:
select evt_block_time, evt_tx_hash, profileIds
from lens_polygon.LensHub_evt_Followed
where cardinality(profileIds) = 2
limit 10
- Dune SQL 数组的索引从 1 开始计数
Dune SQL访问数组元素:
select evt_block_time, evt_tx_hash, profileIds,
profileIds[1] as id1, profileIds[2] as id2
from lens_polygon.LensHub_evt_Followed
where cardinality(profileIds) = 2
limit 10
- 将数组元素拆分到多行记录。
Dune SQL拆分数组元素到多行:
select evt_block_time, evt_tx_hash, profileIds, tbl.profile_id
from lens_polygon.LensHub_evt_Followed
cross join unnest(profileIds) as tbl(profile_id)
where cardinality(profileIds) = 3
limit 20
- 同时将多个数组字段拆分到多行记录。
要同时将多个数组字段拆分到多行(前提是它们必须具有相同的长度),Dune SQL中可以在unnest()
函数中包括多个字段,同时输出多个对应字段。
Dune SQL拆分多个数组元素到多行:
SELECT evt_block_time, evt_tx_hash, ids, "values", tbl.id, tbl.val
FROM erc1155_polygon.evt_TransferBatch
cross join unnest(ids, "values") as tbl(id, val)
WHERE evt_tx_hash = 0x19972e0ac41a70752643b9f4cb453e846fd5e0a4f7a3205b8ce1a35dacd3100b
AND evt_block_time >= date('2022-12-14')
将已经存在的Spark SQL引擎编写的query迁移到Dune SQL的过程是非常便利的。你可以直接进入Query的Edit界面,从左边的数据集下拉列表中切换到“1. v2 Dune SQL”,同时对Query的内容做相应的调整,涉及的主要修改已经在本文前面各节分别进行了介绍。这里举一个实际的例子:
Spark SQL 版本:https://dune.com/queries/1773896 Dune SQL 版本:https://dune.com/queries/1000162
迁移时修改内容对照:
Dune SQL 还有一个潜在的高级功能,就是允许针对一个已保存的查询进行查询(Query of Query)。这个功能有很多的想象空间,可简化查询逻辑,优化缓存使用等。比如,你可以将一个复杂的查询的基础部分保存为一个query,然后基于此query来进一步的汇总统计。这个功能貌似有时还不太稳定。不过大家可以试试。
-- original query: https://dune.com/queries/1752041
select * from query_1752041
where user_status = 'Retained'
-- original query: https://dune.com/queries/1752041
select * from query_1752041
where user_status = 'Churned'
SixdegreeLab(@SixdegreeLab)是专业的链上数据团队,我们的使命是为用户提供准确的链上数据图表、分析以及洞见,并致力于普及链上数据分析。通过建立社区、编写教程等方式,培养链上数据分析师,输出有价值的分析内容,推动社区构建区块链的数据层,为未来广阔的区块链数据应用培养人才。
欢迎访问SixdegreeLab的Dune主页。
因水平所限,不足之处在所难免。如有发现任何错误,敬请指正。