查询数据技巧摘要

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【5月更文挑战第11天】 SQL 提效技巧摘要,利用 SQL 特性减少程序模板,如在数据位置处理,优化性能,学习如 `FOR-EACH` 循环和 `NULL` 处理等。

1 数据的重要性

我们编写和执行的大多數 SQL 查詢都是在数据库执行。它是您程序的基石,没有何数据让它沒用。

但这也是刪除大量程序样板的最佳机会,通過使用更花哨的查询方法。
在许多用例中,这些方法还在数据所在的位置进行数据处理时提高性能將其全部转移到您的程序。

这部分將向您展示特殊功能,例如 SQL 中的 for-each 循环,一些 null 处理技巧,您可能会犯的分页错误等等。

你需要仔細查看有关内容使用通用表表达式进行数据优化的技巧;一旦您了解它,你将常常使用它。相信我。

2 减少按列分组的数量

        SELECT actors.firstname, actors.lastname, COUNT(*) as count
        FROM actors
        JOIN actors_movies USING(actor_id)
        GROUP BY actors.id

您可能很久以前就知道,在对某些列进行分组时,您必须添加所有 SELECT 列到 GROUP BY。

但是,当您对主键进行分组时,所有列同一张表的可以省略,因为数据库会自动为你添加它们。

您的查询將更短,因此更易阅读和理解。

3 用大量测试数据填充表格

        -- MySQL
        SET cte_max_recursion_depth = 4294967295;
        INSERT INTO contacts (firstname, lastname)
        WITH RECURSIVE counter(n) AS(
         SELECT 1 AS n
         UNION ALL
         SELECT n + 1 FROM counter WHERE n < 100000
        )
        SELECT CONCAT('firstname-', counter.n), CONCAT('lastname-', counter.n)
        FROM counter
        -- PostgreSQL
        INSERT INTO contacts (firstname, lastname)
        SELECT CONCAT('firstname-', i), CONCAT('lastname-', i)
        FROM generate_series(1, 100000) as i;

有时您可能需要用许多行填充数据库以进行某些性能测试。

这些数据通常是用假数据生成库和大量代码生成的数据看起來最真实。

而不是慢慢地一个接一个地插入大量测试数据,你让数据库生成许多虚拟行填充表以测试索引的效率。

对于有意义的基准,表格需要真实的数据和值分配。但是您可以使用这种方法用更多的行填充表而不是受您查询的影响。

4 使用可空列简化不等式检查

    -- MySQL
    SELECT * FROM example WHERE NOT(column <> 'value');
    -- PostgreSQL
    SELECT * FROM example WHERE column IS DISTINCT FROM 'value';

搜索具有不等于特定值的可空列的行很复杂。

标准方法 col != 'value' 不起作用,因為 null 与任何东西都不可比因此不会包含在結果中。

或者总是使用更复杂的(col IS NULL OR col != 'value') 获得正确結果的方法。
幸运的是,兩者数据库支持一个特殊的功能來比较不平等的列与包含的空值处理。

5 防止除以零错误

      SELECT visitors_today / NULLIF(visitors_yesterday, 0) FROM logs_aggregated;

在数据库中计算统计数据很容易,您可能已完成了几百次。
但是您可能会在几个月后从这些查詢中收到错误,因为编写查询所做的假设不再有效。

可能是你的网站由於停机,特定日期沒有任何访客,或者您的网上商店昨天第一次没有卖出任何产品。

由于那天沒有可用的行,除以SUM(visitors_yesterday) 將触发错误除法。你应该始終保证你不除以零以防某些数据丟失。

通过將除数从零到空值,您可以消除该问题。

6 可空列的排序顺序

      -- MySQL: NULL values placed first (default)
    SELECT * FROM customers ORDER BY country ASC;
    SELECT * FROM customers ORDER BY country IS NOT NULL, country ASC;
    -- MYSQL: NULL values placed last
    SELECT * FROM customers ORDER BY country IS NULL, country ASC;
    -- PostgreSQL: NULL values placed first
    SELECT * FROM customers ORDER BY country ASC NULLS FIRST;
    -- PostgreSQL_ NULL values placed last (default)
    SELECT * FROM customers ORDER BY country ASC;
    SELECT * FROM customers ORDER BY country ASC NULLS LAST;

对于 MySQL,在升序排序時,任何 NULL 值都將放在所有內容之前方向,但对于 PostgreSQL,它们將在最后。

由于 SQL 标准忘记指定NULL 值的排序順序,他们都必须发明自己的规则。

但是那些值排名不应该由您的数据库技术決定。

应用程序应该控制它以改善用戶体验:用戶排序,例如名单中的名字升序或降序对首先看到空值不感兴趣搜索特定行。

它们应该放在最后,因为它们是最不重要的。但是当搜索缺少信息的行來填充時,它们应该是第一个。

更多内容:: Placement of NULL values for ORDER BY with nullable columns

7 分页的确定性排序

    SELECT *
    FROM users
    ORDER BY firstname ASC, lastname ASC, user_id ASC
    LIMIT 20 OFFSET 60;

分页的每一个教程和解释都是错误的!最简单的分页使用 LIMIT 和 OFFSET 关键字跳过某些行的方法实际上存在严重程序问题。

当多行具有相同的值时,不能保证它们的順序,例如许多具有相同名字和姓氏的联系人。

由于并发修改在兩个 SQL 查询或不同执行策略之间的表上,排序结果可能兩次都不同。

如果运气不好,最后一行将显示在当前頁面和下一頁的第一頁。

您应该始终进行订购通过添加更多列去确定,因此每一行都是唯一的,添加主鍵是最直接的方法。

8 比 LIMIT OFFSET 更高效的分页

        -- MySQL, PostgreSQL
        SELECT *
        FROM users
        WHERE (firstname, lastname, id) > ('John', 'Doe', 3150)
        ORDER BY firstname ASC, lastname ASC, user_id ASC
        LIMIT 30

虽然前一个提示是朝著正確方向迈出的一步,但它仍然是错误的。插入行時或同時刪除,頁面偏移量將是错误的,因为它沒有正确反映表。

一些联系人將在行被刪除時再次显示或永远不会显示偏移太大或太小。

这些问题的解決方案是一个名为 keyset 的方法分页。
当前頁面上最后一項的值被传达以获取以下內容頁面結果。

对于高页码,它也比 LIMIT OFFSET 分頁快得多但不支持跳转到任意頁面。

9 具有安全保证的数据库支持的锁

          START TRANSACTION;
        SELECT balance FROM account WHERE account_id = 7 FOR UPDATE;
        -- Race condition free update after processing the data
        UPDATE account SET balance = 540 WHERE account_id = 7;
        COMMIT;

我见过的几乎所有程序都容易有竞争条件:从数据库,程序处理一些数据,用新数据更新值。

但该程序不受计算期间发生的任何更新的保护。
一些关键部件受到锁定解決方案的保护,以防止竞争条件。

然而,锁定算法很难构建,因为崩溃的程序逻辑可能错过锁定。

解決問題的时间的自动锁释放方法仍然有效锁太长了。

当你运行任何数据修改查询(例如 UPDATE),数据库会自行所得任何受影响的行,直到事务結束,以保证数据的正確性。

代替在您的程序中使用锁定方法,您可以使用FOR UPDATE 在您的 SELECT 查询上锁定读取数据以防止竞争条件。

这些事务完成或客戶端断开时,锁自动释放。

注意:

更新值的程序逻辑都应该使用锁定,否則会竞争条件將以不需要的结果結束。

而且你必須在任何地方都这样做,只是在某个時候一小部分沒有解決竞争条件问题。

更多内容: Transactional Locking to Prevent Race Conditions

10 使用公用表表达式细化数据

    WITH most_popular_products AS (
     SELECT products.*, COUNT(*) as sales
     FROM products
     JOIN users_orders_products USING(product_id)
     JOIN users_orders USING(order_id)
     WHERE users_orders.created_at BETWEEN '2022-01-01' AND '2022-06-30'
     GROUP BY products.product_id
     ORDER BY COUNT(*) DESC
     LIMIT 10
    ), applicable_users (
     SELECT DISTINCT users.*
     FROM users
     JOIN users_raffle USING(user_id)
     WHERE users_raffle.correct_answers > 8
    ), applicable_users_bought_most_popular_product AS (
     SELECT applicable_users.user_id, most_popular_products.product_id
     FROM applicable_users
     JOIN users_orders USING(order_id)
     JOIN users_orders_products USING(product_id)
     JOIN most_popular_products USING(product_id)
    ) raffle AS (
     SELECT product_id, user_id, RANK() OVER(
     PARTITION BY product_id
     ORDER BY RANDOM()
     ) AS winner_order
     FROM applicable_users_bought_most_popular_product
    )
    SELECT product_id, user_id FROM raffle WHERE winner_order = 1;

当您必須使用复杂的规则从数据库中获取行时,这些规则很难一次完成单個查询,您可以使用公用表表达式 (CTE) 拆分它。

每一步,你可以細化數據並在以後的細化中使用它(甚至多次)以最終得到期望的結果。

而不是传统的方法,例如许多嵌套的子查詢或几十個连接,CTE 更易於阅读,您可以单独调试迭代步驟。

为了性能,这两个方法有些相同。数据可能會改变它在嵌套子查詢內部或通过緩存单个步驟找到更有效的方法多次使用。

11 许多相似的第一行 仅支持 PGSQL

    -- PostgreSQL
        SELECT DISTINCT ON (customer_id) *
        FROM orders
        WHERE EXTRACT (YEAR FROM created_at) = 2022
        ORDER BY customer_id ASC, price DESC;

有时你有很多行,而你只想要一个,例如每个客戶。

你可以如前所述坚持 for-each-loop-like lateral join 或使用 PostgreSQL 的与众不同发明。

标准的 DISTINCT 查询將通过与一行的所有列。但是使用展示的功能,您可以指定列的子集去make distinct 並且仅保留排序后的第一個匹配行。

12 一个查询中的多个聚合 22

    -- MySQL
    SELECT
     SUM(released_at = 2001) AS released_2001,
     SUM(released_at = 2002) AS released_2002,
     SUM(director = 'Steven Spielberg') AS director_stevenspielberg,
     SUM(director = 'James Cameron') AS director_jamescameron
    FROM movies
    WHERE streamingservice = 'Netflix';
    -- PostgreSQL
    SELECT
     COUNT(*) FILTER (WHERE released_at = 2001) AS released_2001,
     COUNT(*) FILTER (WHERE released_at = 2002) AS released_2002,
     COUNT(*) FILTER (WHERE director = 'Steven Spielberg') AS
    director_stevenspielberg,
     COUNT(*) FILTER (WHERE director = 'James Cameron') AS
    director_jamescameron
    FROM movies
    WHERE streamingservice = 'Netflix';

在某些情況下,您需要计算多個不同的统计数据。而不是执行很多查詢,你可以写一個,一次通过数据收集所有信息。

根据您的数据和索引,这可能加快或減慢你的执行时间。你绝对应该在你的应用程序上测试它。

更多内容: Multiple Aggregates in One Query

13 限制行Rows也包括Ties 仅支持 PGSQL

        -- PostgreSQL
        SELECT *
        FROM teams
        ORDER BY winning_games DESC
        FETCH FIRST 3 ROWS WITH TIES;

想像一下,您想要对体育联盟的球队进行排名並显示前三名。

在罕見的在这个情況下,至少有 2 支球队在比賽结束时的胜场数相同季。
如果他們都排在第三位,你可能想扩大你的限制以包括两者他們。

WITH TIES 选项正好可以满足这样做的。

每当一些行会是尽管与包含的值具有相同的值,但仍被排除在外,但它们也被包含在內,尽管超出限制。

14 快速行计数估计

        -- MySQL
        EXPLAIN FORMAT=TREE SELECT * FROM movies WHERE rating = 'NC-17' AND
        price < 4.99;
        -- PostgreSQL
        EXPLAIN SELECT * FROM movies WHERE rating = 'NC-17' AND price < 4.99;

显示匹配行数是大多数应用程序的关键功能,但它有时难以为大型数据库实施。数据库越大,速度越慢將计算行数。

沒有索引時查詢會很慢帮助计算计数。但即使是現有的索引也无法统计数百個或以千計的索引快。

然而,大概的行数可能就足够了一些用例。

数据库的查询规划器总是计算一个大概的行數对于可以通过向数据库询问执行计划來提取的查询。

2.14 具有间隙填充的基于日期的统计查询

        -- MySQL
        SET cte_max_recursion_depth = 4294967295;
        WITH RECURSIVE dates_without_gaps(day) AS (
         SELECT DATE_SUB(CURRENT_DATE, INTERVAL 14 DAY) as day
         UNION ALL
         SELECT DATE_ADD(day, INTERVAL 1 DAY) as day
         FROM dates_without_gaps
         WHERE day < CURRENT_DATE
        )
        SELECT dates_without_gaps.day, COALESCE(SUM(statistics.count), 0)
        FROM dates_without_gaps
        LEFT JOIN statistics ON(statistics.day = dates_without_gaps.day)
        GROUP BY dates_without_gaps.day;
        -- PostgreSQL
        SELECT dates_without_gaps.day, COALESCE(SUM(statistics.count), 0)
        FROM generate_series(
         CURRENT_DATE - INTERVAL '14 days',
         CURRENT_DATE,
         '1 day'
        ) as dates_without_gaps(day)
        LEFT JOIN statistics ON(statistics.day = dates_without_gaps.day)
        GROUP BY dates_without_gaps.day;

一些统计计算的结果会有差距,因为沒有信息是保存特定日期。

但不是用应用程序代码回填这些漏洞,可以重組数据库查詢:创建一系列无间隙值作为源加入统计数据。

对于 PostgreSQL,generate_series 函数可能用於创建序列,而对于 MySQL,同样需要使用递归公用表表达式 (CTE)。

更多内容: Fill Gaps in Statistical Time Series Results

2.15 使用 For-Each 循环连接表

        -- MySQL, PostgreSQL
        SELECT customers.*, recent_sales.*
        FROM customers
        LEFT JOIN LATERAL (
         SELECT *
         FROM sales
         WHERE sales.customer_id = customers.id
         ORDER BY created_at DESC
         LIMIT 3
        ) AS recent_sales ON true;

连接表时,两个表的行根据某些条件连接在一起。

但是,连接条件只能包括不同表的所有匹配行。

这是无法控制的,每次迭代的行数不同,例如限制为每個客戶购买产品,只为最后三個客戶购买产品。

特殊的橫向连接类型结合了连接和子查询。將执行子查询连接源表的每一行。

在该子查询中,您可以例如只选择最后一個一位顾客购买了三类产品。

由于您已经选择了仅匹配的销售每个客戶,一个特殊的 true 连接条件表示將使用所有行。

你可以现在在你的数据中进行 for-each 循环。您已经了解了 SQL 的圣杯!

更多内容: For each loops with LATERAL Joins

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
25天前
|
机器学习/深度学习 自然语言处理 算法
一个抽取式摘要的应用示例
【10月更文挑战第4天】本文介绍了生成式和抽取式两种摘要算法。生成式摘要通过自然语言生成技术,使用机器学习模型生成新的、精简的句子;抽取式摘要则从文档中提取最能代表内容的句子。文中详细描述了基于TF-IDF的抽取式摘要算法的实现过程,包括计算TF、IDF、句子权重,以及提取摘要的具体步骤。通过示例代码展示了如何实现一个简单的抽取式摘要系统。
45 0
|
4月前
|
JSON 编解码 物联网
理解时间戳的视频理解大模型CogVLM2开源!视频生成、视频摘要等任务有力工具!
随着大型语言模型和多模态对齐技术的发展,视频理解模型在通用开放领域也取得了长足的进步。
|
5月前
|
JavaScript 定位技术
详尽分享缓冲区点查询(在地图上点击一点,查询一定范围内的信息)
详尽分享缓冲区点查询(在地图上点击一点,查询一定范围内的信息)
23 0
|
6月前
|
数据采集 机器学习/深度学习 自然语言处理
Elasticsearch 8.X 如何动态的为正文添加摘要字段?
Elasticsearch 8.X 如何动态的为正文添加摘要字段?
38 0
如何查询当前年最新的数据以及这条数据的上一年的最新数据?
如何查询当前年最新的数据以及这条数据的上一年的最新数据
63 0
分组分类的查询与保存
分组分类的查询与保存
157 0
分组分类的查询与保存
|
消息中间件 数据采集 分布式计算
数据预处理-数据推送-过滤纯查询数据|学习笔记
快速学习数据预处理-数据推送-过滤纯查询数据
126 0
数据预处理-数据推送-过滤纯查询数据|学习笔记
|
存储 JSON 缓存
你必须知道的23个最有用的Elasticseaerch检索技巧
题记 本文详细论述了Elasticsearch全文检索、指定字段检索实战技巧,并提供了详尽的源码举例。是不可多得学习&实战资料。
241 0
什么是数据、信息、知识和智慧?
2006年1月份左右,我曾经撰写了一篇涉及数据,信息,知识和智慧的文章,在科学网看到倪鹏云老师也在探讨这个问题,于是我将原文一些核心的内容整理出来形成这篇文章与大家进行探讨。 在知识管理中,有一个经典的知识阶层图,本文将以这个图作为阐述的顺序依据。
什么是数据、信息、知识和智慧?
|
存储 安全
内容安全人脸检索简介的使用限制
本文介绍了接入人脸检索功能的操作流程。
372 0