当前位置: 首页 > article >正文

DuckDB: 两种方法实现动态分组查询

在使用关系数据库时,你可能经常遇到需要根据不同的标准生成动态查询的场景。其中一个场景是按多列动态分组数据并计算聚合值,例如sum或average。在本文中,我们将演示如何使用DuckDB实现这一点,重点关注GROUPING SETS和CUBE功能。

需求提出

首先准备数据,创建表及数据准备如下:

CREATE TABLE sample_table (
    YEAR INTEGER,
    BRAND VARCHAR, 
    PRODUCT VARCHAR,  
    SALES INTEGER     
);
 
INSERT INTO sample_table (YEAR, BRAND, PRODUCT, SALES) VALUES
(2023, 'AX', 'A', 10),
(2024, 'AX', 'A', 20),
(2024, 'AX', 'B', 70),
(2022, 'AY', 'C', 20),
(2023, 'AY', 'C', 90);

给定这些数据,我们希望创建宏,使我们能够按列(如BRAND和PRODUCT)动态分组,并计算总销售额。以下是实现该目标的方法。
在这里插入图片描述

静态SQL查询方案

可以使用以下SQL查询静态地实现所需的结果:

SELECT YEAR, BRAND, PRODUCT, SUM(SALES)
FROM sample_table 
GROUP BY YEAR, GROUPING SETS(CUBE(BRAND, PRODUCT));

这将生成跨不同年份值的BRAND和PRODUCT的各种组合的总和。输出结果如下:

┌───────┬─────────┬─────────┬────────────┐
│ YEAR  │  BRAND  │ PRODUCT │ sum(SALES) │
│ int32 │ varchar │ varchar │   int128   │
├───────┼─────────┼─────────┼────────────┤
│  2022 │         │         │         20 │
│  2022 │ AY      │         │         20 │
│  2023 │         │         │        100 │
│  2022 │ AY      │ C       │         20 │
│  2023 │         │ A       │         10 │
│  2024 │         │ A       │         20 │
│  2022 │         │ C       │         20 │
│  2024 │         │ B       │         70 │
│  2024 │         │         │         90 │
│  2023 │ AX      │         │         10 │
│  2024 │ AX      │         │         90 │
│  2023 │ AX      │ A       │         10 │
│  2024 │ AX      │ B       │         70 │
│  2023 │ AY      │ C       │         90 │
│  2023 │ AY      │         │         90 │
│  2024 │ AX      │ A       │         20 │
│  2023 │         │ C       │         90 │
├───────┴─────────┴─────────┴────────────┤
│ 17 rows                      4 columns │
└────────────────────────────────────────┘

动态SQL查询方案

创建动态解决方案稍微复杂一些。在这里,我们将介绍两种方法:使用宏和使用存储过程。

执行动态SQL

在 DuckDB 中,json_serialize_sql函数的作用是将 DuckDB 的数据结构(如结构体、数组等)序列化为 JSON 格式的字符串。json_execute_serialized_sql函数用于执行包含在 JSON 序列化格式中的 SQL 语句。这是一种以灵活的方式来存储和执行 SQL 语句,特别是在处理动态 SQL 场景或从外部数据源接收 SQL 指令(以 JSON 格式存储)时非常有用。

构造一个 JSON 字符串,其中的 SQL 语句用于查询 employees 表中年龄大于某个指定值的员工信息,同时设置参数,示例如下:

-- 创建宏
CREATE OR REPLACE MACRO filter_emps(min_age) AS TABLE(
    FROM json_execute_serialized_sql(
        json_serialize_sql(
        	format('SELECT name, age FROM employees WHERE age > {0}', min_age)
    	)
	)
);

执行宏:

D from filter_emps(30);
┌─────────┬───────┐
│  name   │  age  │
│ varchar │ int32 │
├─────────┼───────┤
│ Bob     │    35 │
└─────────┴───────┘

宏可以实现类似带参数的视图功能,非常灵活。下面利用宏实现动态分组功能。

方法1:自定义宏

要动态构建查询,可以使用json_execute_serialized_sql函数来运行它。下面是如何在DuckDB中实现这一点的示例:

CREATE OR REPLACE MACRO msum(tbl, grpcols) AS TABLE (
   FROM json_execute_serialized_sql(json_serialize_sql(
      format('
         FROM {0}
         SELECT {1}, SUM(SALES)
         GROUP BY YEAR, GROUPING SETS(CUBE({1}))', 
         tbl, 
         list_reduce(grpcols, (x, y) -> format('{0},{1}', x, y))
      )
   ))
);

通过下面语句调用宏示例:

FROM msum(sample_table, ['BRAND', 'YEAR,PRODUCT']);

返回结果如下:

D FROM msum(sample_table, ['BRAND', 'PRODUCT']);
┌─────────┬─────────┬────────────┐
│  BRAND  │ PRODUCT │ sum(SALES) │
│ varcharvarchar │   int128   │
├─────────┼─────────┼────────────┤
│         │         │        100 │
│         │         │         20 │
│ AY      │ C       │         20 │
│         │         │         90 │
│ AY      │         │         20 │
│ AX      │         │         10 │
│ AX      │         │         90 │
│         │ C       │         90 │
│         │ A       │         10 │
│         │ A       │         20 │
│         │ C       │         20 │
│ AX      │ A       │         10 │
│ AX      │ B       │         70 │
│ AY      │ C       │         90 │
│ AY      │         │         90 │
│ AX      │ A       │         20 │
│         │ B       │         70 │
├─────────┴─────────┴────────────┤
│ 17 rows              3 columns │
└────────────────────────────────┘

当然,你也可以传递多个参数:

FROM msum(sample_table, ['BRAND', 'YEAR,PRODUCT']);

结果与静态SQL查询示例相同。

方法2:使用存储过程

另一种方法是使用存储过程动态生成和执行SQL查询。这里有一个例子:

CREATE OR REPLACE PROCEDURE dynamic_grouping_sum(
     IN GRPCOLS VARCHAR -- Pass column names as a comma-separated string like 'BRAND,PRODUCT'
)
BEGIN
    DECLARE sql_query VARCHAR;
    -- Construct the SQL query dynamically
    SET sql_query = CONCAT(
        'SELECT YEAR, ', GRPCOLS, ', SUM(SALES) AS total_sales ',
        'FROM sample_table ',
        'GROUP BY YEAR, GROUPING SETS(CUBE(', GRPCOLS, '));'
    );
 
   -- Execute the dynamically created SQL
   EXECUTE IMMEDIATE sql_query;
END;

使用下面语句调用存储过程:

CALL dynamic_grouping_sum('BRAND, PRODUCT');

存储过程实际执行的语句为:

SELECT YEAR, BRAND, PRODUCT, SUM(SALES) AS total_sales
FROM sample_table
GROUP BY YEAR, GROUPING SETS(CUBE(BRAND, PRODUCT));

总结

这两种方法都支持动态生成SQL查询,以便根据指定列对数据进行分组和聚合。宏在DuckDB中提供了一种灵活的脚本选项,而存储过程则为习惯了传统RDBMS系统的用户提供了一种更结构化、更熟悉的方法。最佳选择取决于你的应用场景和编码风格。


http://www.kler.cn/a/442706.html

相关文章:

  • TiDB常见操作指南:从入门到进阶
  • JSON.stringify(res,null,2)的含义
  • Cython全教程2 多种定义方式
  • mermaid大全(语法、流程图、时序图、甘特图、饼图、用户旅行图、类图)
  • Web前端:JavaScript标识符与变量
  • 【Java计算机毕业设计】基于SSM旅游景区网络购票系统【源代码+数据库+LW文档+开题报告+答辩稿+部署教程+代码讲解】
  • 解决git push出现的报错:Permission denied (publickey)
  • 本地项目显示正常,打包部署后ElementUI重点饿图标全部显示异常为小方框
  • 天线覆盖方案简图
  • 云连POS-ERP管理系统ZksrService存在SQL注入漏洞
  • bug之浮点数精度求和计算
  • IntelliJ IDEA中的语言级别版本与目标字节码版本配置
  • c++数据结构算法复习基础--13--基数算法
  • STM32卡死、跑飞、进入HardFault_Handler如何精准的确定问题
  • 【go每日一题】 实现生产者消费者模式
  • 电源的分类
  • windows 使用python共享网络给另外一个网卡
  • 谁说C比C++快?
  • 矩阵的基本知识
  • 【ETCD】ETCD 的一致性读(Linearizable Read)流程解析
  • nexus5x安卓root
  • 队列的原理及应用
  • Git安装详解(写吐了,看完不后悔)
  • 9_less教程 --[CSS预处理]
  • 代码生成器
  • 架构实践04-高扩展架构模式