利用ClickHouse派生表优化查询性能

本文详细介绍了ClickHouse中的派生表概念,通过实例展示了如何创建、连接和多次使用派生表来简化复杂查询,提高查询效率。同时,还探讨了CTE(公共表表达式)的使用,以及它们在提升SQL可读性和执行性能方面的作用。

派生表是由SQL查询动态创建的表,仅存在与查询过程中的临时表,不会持久化在数据库中。ClickHouse提供派生表用于简化复杂查询、减少需要处理的数据量。本文通过多个实例展示如何创建、连接派生表,以及如何多次使用派生表。

隐式创建派生表

创建派生表

要创建派生表,可以在主查询中包括子查询,使用子查询结果作为派生表,举例:

SELECT *
FROM (
    SELECT customer_id, COUNT(*) AS num_orders
    FROM orders
    GROUP BY customer_id
) AS customer_orders
WHERE num_orders > 10;

该示例中子查询创建派生表,负责计算每个客户端的订单数。主查询过滤派生表结果,仅保留订单数量超过10的记录。

连接派生表

派生表还可以与其他表连接实现更复杂查询,举例:

SELECT *
FROM products
    JOIN (
        SELECT product_id, AVG(price) AS avg_price
        FROM prices
        GROUP BY product_id
    ) AS product_prices
    ON products.product_id = product_prices.product_id
WHERE product_prices.avg_price > 50;

该示例中,子查询创建派生表,负责计算每个产品的平均价格;主查询连接派生表和产品表,并过滤仅保留平均价格大于50的产品。

多次使用派生表

可以在一个查询中多次使用派生表,举例:

SELECT *
FROM (
    SELECT customer_id, COUNT(*) AS num_orders
    FROM orders
    GROUP BY customer_id
) AS customer_orders
JOIN (
    SELECT customer_id, SUM(total_price) AS total_spent
    FROM orders
    GROUP BY customer_id
) AS customer_totals
ON customer_orders.customer_id = customer_totals.customer_id
WHERE customer_orders.num_orders > 10 AND customer_totals.total_spent > 1000;

该实例中创建了两个派生表:一个负责计算每个客户的订单数量,另一个计算每个客户的消费总额。主查询连接两个派生表,并过滤符合条件的记录:订单数量大于10,消费总额大于1000。

显示创建派生表

CTE(common table expression)是临时命名数据集,是定义在SELECT, INSERT, UPDATE, DELETE 语句中派生表,利用CTE可以提升查询SQL的可读性。但CTE和临时表或嵌套表有一定差异:

  • 如果子查询是关联查询 (也称为同步子查询),它有可能对查询中每行需要重复执行,极大地增加查询执行负载。

  • 大量数据填充临时表可能会占用磁盘空间,由于存储临时表特性,使用临时表执行查询会增加执行时间。

CTE语法

ClickHouse支持两种语法:

WITH <expression> AS <identifier>

-- 或者
WITH <identifier> AS <subquery expression> 

如果一次定义多个CTE,可以使用逗号分隔。

举例:

WITH locations AS
(
    SELECT location
    FROM table
    WHERE date > (today() - 10)
)
SELECT *
FROM locations

-- 另一个简单示例
WITH ('USA', 'BRA') AS locations
SELECT 'ARG' IN (locations)

完整CTE示例

首先创建表并插入示例数据:

CREATE TABLE SpareParts
(
    `id` UInt32,
    `partName` String,
    `partOrigin` String,
    `storeID` UInt32
)
ENGINE = MergeTree()
ORDER BY id

-- 插入数据

INSERT INTO SpareParts VALUES (1, 'headlight', 'USA', 1)
,(2, 'hood', 'JPN', 1)
, (3, 'bumper', 'USA', 1)
, (4, 'radiator', 'BRA', 3)
, (5, 'wheel', 'BRA', 2)
,(6, 'stabilizer', 'ARG', 3)
, (7, 'absorber', 'TUR', 2)
, (8, 'cable', 'MEX', 1)
,(9, 'spring', 'MEX', 3)
, (10, 'door', 'USA', 2);

定义CTE查询:

WITH
    originsByStore AS
    (
        SELECT
            storeID,
            groupArray(partOrigin) AS origins
        FROM SpareParts
        GROUP BY storeID
    ),
    partsByStore AS
    (
        SELECT
            storeID,
            groupArray(partName) AS partNames
        FROM SpareParts
        GROUP BY storeID
    ),
    has(origins, 'USA') = 1 AS isUSA
SELECT
    storeID,
    origins,
    partNames,
    isUSA
FROM originsByStore AS t1
LEFT JOIN
(
    SELECT
        storeID,
        partNames
    FROM partsByStore
) AS t2 USING (storeID)

返回结果:

┌─storeID─┬─origins───────────────────┬─partNames─────────────────────────────┬─isUSA─┐
│       3 │ ['BRA','ARG','MEX']       │ ['radiator','stabilizer','spring']    │     0 │
│       2 │ ['BRA','TUR','USA']       │ ['wheel','absorber','door']           │     1 │
│       1 │ ['USA','JPN','USA','MEX'] │ ['headlight','hood','bumper','cable'] │     1 │
└─────────┴───────────────────────────┴───────────────────────────────────────┴───────┘

总结

本文介绍了ClickHouse派生表,如何显示或隐式创建派生表,利用好派生表不仅可以提升SQL可读性,还可以提升查询性能。

<think>好的,用户想了解派生查询SQL用法示例。首先我需要回忆一下派生的基本概念。派生,也称为子查询派生,是在FROM子句中使用的子查询,它生成一个临时供外部查询使用。记得派生必须要有别名,这是关键点之一。接下来,我需要考虑如何用简单的例子来说明。比如,用户可能需要计算每个部门的平均工资,然后用这个结果来筛选出高于平均工资的员工。这样的例子能直观展示派生的用途。同时,结合用户提供的引用[1]中的子查询示例,可以进一步说明派生与普通子查询的区别,比如派生在FROM中的位置别名的必要性。 然后,我需要检查用户提供的引用内容。引用[1]提到了使用IN子句的子查询,而引用[2]提到ORDER BY的位置。虽然这些引用主要关于子查询集合操作,但可以联系起来,说明派生可以与其他SQL语句结合使用,比如JOIN或ORDERBY。例如,在派生内部排序后再进行外部查询的连接操作。用户可能存在的疑问是派生与普通子查询的区别,以及何时使用派生。需要强调派生在FROM子句中作为数据源的角色,而普通子查询可能在WHERE或SELECT部分。另外,派生可以简化复杂查询,避免重复计算,提高可读性。 还要考虑用户的实际应用场景,比如处理多层嵌套查询,或者需要多次引用同一个子查询结果时,使用派生会更高效。这时候可能需要举例说明,比如统计每个部门的最高工资,并与其他连接获取部门名称。最后,确保示例的SQL语法正确,使用正确的别名,并符合用户提到的引用中的结构。例如,在示例中包含JOIN操作别名应用,同时展示如何将派生的结果用于进一步的分析或过滤。</think>### 派生查询的用法及示例 #### 1. 基本概念 **派生**(Derived Table)是嵌套在`FROM`子句中的子查询,它生成临时供外部查询使用。派生必须定义别名,且仅在当前查询中有效[^1]。 #### 2. 核心语法 ```sql SELECT 列名 FROM ( -- 子查询派生) SELECT 列名 FROM WHERE 条件 ) AS 别名 -- 必须指定别名 WHERE 外部查询条件; ``` #### 3. 典型应用场景 **场景1:多层聚合计算** ```sql -- 计算每个部门平均工资,并筛选高于部门平均值的员工 SELECT e.employee_id, e.salary, dept_avg.avg_salary FROM employees e JOIN ( SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id ) AS dept_avg ON e.department_id = dept_avg.department_id WHERE e.salary > dept_avg.avg_salary; ``` **场景2:简化复杂筛选** ```sql -- 筛选销售额排名前5的销售员 SELECT salesperson_id, total_sales FROM ( SELECT salesperson_id, SUM(amount) AS total_sales, RANK() OVER (ORDER BY SUM(amount) DESC) AS sales_rank FROM orders GROUP BY salesperson_id ) AS ranked_sales WHERE sales_rank <= 5; ``` #### 4. 进阶用法 **结合窗口函数:** ```sql -- 计算员工工资与部门中位数的差值 SELECT employee_id, salary, salary - dept_stats.median_salary AS diff_from_median FROM employees JOIN ( SELECT department_id, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary FROM employees GROUP BY department_id ) AS dept_stats USING (department_id); ``` **多层派生嵌套:** ```sql -- 统计各部门最高工资对应的员工 SELECT final.dept_name, final.employee_name, final.max_salary FROM ( SELECT d.dept_name, e.employee_name, e.salary, MAX(e.salary) OVER (PARTITION BY d.dept_id) AS max_salary FROM departments d JOIN employees e ON d.dept_id = e.department_id ) AS final WHERE final.salary = final.max_salary; ``` #### 5. 性能优化建议 - 为派生中频繁使用的字段添加索引 - 避免超过3层嵌套(可改用CTE提升可读性) - 使用`EXPLAIN`分析执行计划
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值