MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SQLite过滤、限定与排序技巧

2021-06-025.9k 阅读

SQLite过滤技巧

WHERE 子句基础

在SQLite中,WHERE子句是实现数据过滤的核心工具。它允许我们在SELECTUPDATEDELETE语句中指定条件,从而筛选出符合特定条件的数据行。

例如,我们有一个名为employees的表,包含idnameagesalary字段。如果我们只想获取年龄大于30岁的员工信息,我们可以使用以下SELECT语句:

SELECT * FROM employees
WHERE age > 30;

在上述代码中,WHERE age > 30就是过滤条件,只有满足该条件(即年龄大于30岁)的员工记录才会被查询出来。

比较运算符的使用

  1. 等于(=):用于判断字段值是否等于指定值。比如,我们想找出name为“John”的员工:
SELECT * FROM employees
WHERE name = 'John';
  1. 不等于(<> 或 !=):筛选出字段值不等于指定值的数据。例如,找出薪资不等于5000的员工:
SELECT * FROM employees
WHERE salary <> 5000;

或者

SELECT * FROM employees
WHERE salary != 5000;
  1. 大于(>)、小于(<)、大于等于(>=)、小于等于(<=):常用于数值类型字段的比较。例如,找出年龄小于等于40岁的员工:
SELECT * FROM employees
WHERE age <= 40;

逻辑运算符

  1. AND:用于连接多个条件,只有当所有条件都满足时,数据行才会被选中。假设我们想找出年龄大于30岁且薪资大于5000的员工:
SELECT * FROM employees
WHERE age > 30 AND salary > 5000;
  1. OR:同样用于连接多个条件,但只要其中一个条件满足,数据行就会被选中。比如,找出年龄小于25岁或者薪资大于8000的员工:
SELECT * FROM employees
WHERE age < 25 OR salary > 8000;
  1. NOT:对条件进行取反。例如,找出年龄不大于35岁的员工(即年龄小于等于35岁):
SELECT * FROM employees
WHERE NOT age > 35;

这等价于

SELECT * FROM employees
WHERE age <= 35;

模糊匹配

  1. LIKE:用于模糊匹配字符串。它使用通配符%_%表示任意长度的任意字符序列(包括零长度),_表示任意单个字符。

例如,找出名字以“J”开头的员工:

SELECT * FROM employees
WHERE name LIKE 'J%';

如果想找出名字只有三个字符且第二个字符为“o”的员工:

SELECT * FROM employees
WHERE name LIKE '_o_';
  1. GLOB:与LIKE类似,但使用不同的通配符规则。*表示任意长度的任意字符序列,?表示任意单个字符。例如,找出名字以“J”开头的员工:
SELECT * FROM employees
WHERE name GLOB 'J*';

范围查询

  1. BETWEEN...AND:用于查找字段值在指定范围内的数据。注意,该范围是闭区间,即包括边界值。例如,找出年龄在30到40岁之间的员工:
SELECT * FROM employees
WHERE age BETWEEN 30 AND 40;
  1. IN:用于判断字段值是否在指定的一组值中。比如,找出名字为“John”、“Jane”或“Bob”的员工:
SELECT * FROM employees
WHERE name IN ('John', 'Jane', 'Bob');

SQLite限定技巧

LIMIT 子句

LIMIT子句用于限制查询结果返回的行数。这在只需要获取部分数据时非常有用,比如分页查询或者只查看前几条数据。

基本语法是LIMIT [number],其中number是要返回的行数。例如,我们只想查看employees表中的前5条记录:

SELECT * FROM employees
LIMIT 5;

OFFSET 与 LIMIT 结合

OFFSET用于指定从结果集的哪一行开始返回,结合LIMIT可以实现分页功能。OFFSET后面跟着的数字表示偏移量,即从结果集的第几行开始。

假设我们每页显示10条记录,要查看第二页的数据,我们可以这样写:

SELECT * FROM employees
LIMIT 10 OFFSET 10;

这里LIMIT 10表示每页显示10条记录,OFFSET 10表示从第11条记录开始(因为偏移量从0开始计数)。

聚合函数与限定

  1. COUNTCOUNT函数用于统计满足条件的行数。例如,统计年龄大于30岁的员工数量:
SELECT COUNT(*) FROM employees
WHERE age > 30;
  1. SUM:用于计算数值字段的总和。比如,计算所有员工的薪资总和:
SELECT SUM(salary) FROM employees;
  1. AVG:计算数值字段的平均值。例如,计算员工的平均年龄:
SELECT AVG(age) FROM employees;
  1. MINMAX:分别用于找出字段的最小值和最大值。例如,找出员工的最小年龄和最大年龄:
SELECT MIN(age), MAX(age) FROM employees;

在使用聚合函数时,也可以结合LIMITOFFSET。比如,假设我们有一个销售记录表sales,包含amount字段,我们想找出销售总额最高的前5个记录:

SELECT SUM(amount) AS total_amount
FROM sales
GROUP BY some_grouping_field
ORDER BY total_amount DESC
LIMIT 5;

SQLite排序技巧

ORDER BY 子句基础

ORDER BY子句用于对查询结果进行排序。可以按照一个或多个字段进行排序,并且可以指定升序(ASC)或降序(DESC)排列。

例如,我们想按照员工的年龄升序排列显示员工信息:

SELECT * FROM employees
ORDER BY age ASC;

如果要按照薪资降序排列:

SELECT * FROM employees
ORDER BY salary DESC;

多字段排序

可以使用多个字段进行排序。比如,先按照年龄升序排列,如果年龄相同,再按照薪资降序排列:

SELECT * FROM employees
ORDER BY age ASC, salary DESC;

与聚合函数结合排序

当使用聚合函数时,也可以对聚合结果进行排序。例如,我们计算每个部门的平均薪资,并按照平均薪资降序排列:

SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;

自定义排序顺序

在某些情况下,我们可能需要自定义排序顺序。虽然SQLite本身没有直接提供完全自定义排序函数,但可以通过一些技巧实现类似效果。

比如,我们有一个状态字段status,取值为“new”、“in_progress”、“completed”,我们想按照特定顺序“new”、“in_progress”、“completed”对记录进行排序。我们可以使用CASE语句:

SELECT * FROM tasks
ORDER BY 
    CASE status
        WHEN 'new' THEN 1
        WHEN 'in_progress' THEN 2
        WHEN 'completed' THEN 3
        ELSE 4
    END ASC;

在这个例子中,通过CASE语句为不同的status值分配了不同的顺序值,然后按照这个顺序值进行升序排序,从而实现了自定义排序顺序。

排序性能优化

  1. 索引的使用:为排序字段创建索引可以显著提高排序性能。例如,如果经常按照age字段进行排序,那么为age字段创建索引:
CREATE INDEX idx_age ON employees(age);
  1. 减少排序字段数量:尽量减少ORDER BY子句中字段的数量。排序多个字段通常比排序单个字段更消耗性能。

  2. 合理使用覆盖索引:如果查询中涉及的字段和排序字段可以通过一个索引覆盖,那么可以使用覆盖索引来提高性能。例如,如果查询是SELECT age, salary FROM employees ORDER BY age,可以创建一个包含agesalary的复合索引:

CREATE INDEX idx_age_salary ON employees(age, salary);

通过以上过滤、限定与排序技巧,我们可以更灵活、高效地从SQLite数据库中获取所需的数据。无论是小型应用还是大型项目,合理运用这些技巧都能极大提升数据处理的效率和质量。在实际应用中,需要根据具体的业务需求和数据特点,选择最合适的方法来优化查询。

复杂过滤条件下的排序

当我们有复杂的过滤条件时,排序的逻辑同样重要。例如,假设我们的employees表中还有一个department字段,我们要找出“Sales”部门中年龄大于30岁的员工,并按照薪资降序排列:

SELECT * FROM employees
WHERE department = 'Sales' AND age > 30
ORDER BY salary DESC;

在这种情况下,先通过WHERE子句过滤出符合条件的员工,然后再按照salary字段进行降序排序。

限定与排序的结合

在实际应用中,限定和排序经常会一起使用。比如,我们要找出薪资最高的前10名员工:

SELECT * FROM employees
ORDER BY salary DESC
LIMIT 10;

这里先按照薪资降序对所有员工进行排序,然后通过LIMIT限定只返回前10名员工。

基于表达式的排序

SQLite允许我们基于表达式进行排序。例如,假设我们的employees表中有hourly_wagehours_worked字段,我们想根据员工的总工资(hourly_wage * hours_worked)进行降序排序:

SELECT *, hourly_wage * hours_worked AS total_pay
FROM employees
ORDER BY total_pay DESC;

在这个查询中,我们首先通过表达式hourly_wage * hours_worked计算出每个员工的总工资,并将其命名为total_pay,然后按照total_pay进行降序排序。

处理空值排序

在SQLite中,空值(NULL)在排序中有特殊的处理方式。默认情况下,升序排序时,空值会被排在最后;降序排序时,空值会被排在最前。

例如,假设我们的employees表中有一个commission字段,部分员工可能没有佣金(commissionNULL),我们按照commission升序排序:

SELECT * FROM employees
ORDER BY commission ASC;

在这个查询结果中,commissionNULL的员工会被排在结果集的最后。

如果我们希望空值排在最前,可以通过CASE语句来实现。例如,要让commissionNULL的员工在升序排序中排在最前:

SELECT * FROM employees
ORDER BY 
    CASE 
        WHEN commission IS NULL THEN 0
        ELSE 1
    END ASC,
    commission ASC;

在这个查询中,通过CASE语句为commissionNULL的记录分配了一个较小的值(0),从而使其在排序中排在前面,然后再按照commission本身进行升序排序。

排序与性能分析

  1. 使用EXPLAIN QUERY PLAN:这是一个非常有用的工具,可以帮助我们了解SQLite如何执行查询以及排序操作。例如,对于查询SELECT * FROM employees ORDER BY age;,我们可以使用:
EXPLAIN QUERY PLAN SELECT * FROM employees ORDER BY age;

执行这个语句后,SQLite会返回查询计划,我们可以从中了解到是否使用了索引进行排序,如果没有使用索引,可能需要考虑创建索引来优化性能。

  1. 索引的选择与优化:在为排序字段创建索引时,需要考虑索引的选择性。选择性越高(即索引列的值越不重复),索引对排序性能的提升越明显。例如,如果一个字段只有很少的几个不同值,那么为其创建索引可能对排序性能提升不大。

同时,要避免创建过多不必要的索引,因为索引会占用额外的存储空间,并且在数据插入、更新和删除时会增加维护成本。

不同数据类型的排序特性

  1. 文本类型排序:SQLite对文本类型的排序基于当前数据库的编码方式。默认情况下,排序是区分大小写的。例如,“A”和“a”会被视为不同的值,在排序中会按照字符编码顺序排列。

如果我们想进行不区分大小写的排序,可以使用COLLATE NOCASE。例如,要按照不区分大小写的方式对员工名字进行排序:

SELECT * FROM employees
ORDER BY name COLLATE NOCASE;
  1. 数值类型排序:数值类型(如INTEGERREAL)的排序比较直观,按照数值大小进行升序或降序排列。但是需要注意数据类型的转换问题。例如,在比较INTEGERREAL类型的值时,SQLite会自动进行类型转换,以确保正确的排序。

  2. 日期和时间类型排序:虽然SQLite本身没有专门的日期和时间类型,但可以使用文本格式(如“YYYY - MM - DD”)来存储日期。在对日期文本进行排序时,只要格式正确,就可以按照日期的先后顺序进行排序。

例如,假设我们有一个orders表,包含order_date字段,格式为“YYYY - MM - DD”,我们想按照订单日期升序排列:

SELECT * FROM orders
ORDER BY order_date;

通过了解不同数据类型的排序特性,我们可以更好地优化查询,确保数据按照我们期望的方式进行排序。

递归查询中的排序

在SQLite的递归查询中,排序同样是一个重要的操作。例如,假设我们有一个表示组织结构的表employees,其中id是员工的唯一标识,manager_id表示该员工的上级经理的id(如果是顶级经理,manager_idNULL)。我们想通过递归查询获取整个组织结构,并按照员工的层级和员工id进行排序:

WITH RECURSIVE employee_hierarchy AS (
    SELECT id, name, manager_id, 1 AS level
    FROM employees
    WHERE manager_id IS NULL
    UNION ALL
    SELECT e.id, e.name, e.manager_id, eh.level + 1
    FROM employees e
    JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT id, name, level
FROM employee_hierarchy
ORDER BY level, id;

在这个递归查询中,我们首先定义了一个递归CTE(Common Table Expression)employee_hierarchy,它从顶级经理开始,逐步递归获取所有员工及其层级。然后,在最终的SELECT语句中,我们按照level(层级)升序排列,如果层级相同,则按照id升序排列。

分区内排序

在某些情况下,我们可能需要在分区内进行排序。虽然SQLite没有像一些大型数据库那样直接支持分区表,但我们可以通过一些技巧来模拟分区内排序。

假设我们有一个sales表,包含region(地区)、sale_date(销售日期)和sales_amount(销售金额)字段。我们想在每个地区内,按照销售日期对销售金额进行排序:

SELECT region, sale_date, sales_amount,
       ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_date) AS row_num
FROM sales;

在这个查询中,我们使用了窗口函数ROW_NUMBER()PARTITION BY region表示按照region字段进行分区,ORDER BY sale_date表示在每个分区内按照销售日期进行排序。ROW_NUMBER()函数会为每个分区内的行分配一个从1开始的连续序号,这样我们就实现了分区内排序。

临时表与排序优化

在处理复杂的排序需求时,临时表有时可以帮助我们优化性能。例如,假设我们有一个非常大的表large_table,我们需要对其进行多次不同条件的排序和计算。直接在原表上进行操作可能会导致性能问题。

我们可以先将需要的数据提取到一个临时表中,然后在临时表上进行排序和计算。例如:

-- 创建临时表
CREATE TEMP TABLE temp_table AS
SELECT * FROM large_table
WHERE some_condition;

-- 在临时表上进行排序和计算
SELECT column1, column2, some_calculation
FROM temp_table
ORDER BY column1;

通过这种方式,我们减少了对原表的直接操作,并且在较小的临时表上进行排序和计算通常会更快。同时,临时表在会话结束时会自动删除,不会占用过多的长期存储空间。

事务中的排序操作

在SQLite的事务中,排序操作也需要特别注意。事务的一致性和隔离性原则会影响排序结果。

例如,假设我们在一个事务中对employees表进行数据插入和排序查询:

BEGIN;
INSERT INTO employees (name, age, salary) VALUES ('New Employee', 30, 5000);
SELECT * FROM employees ORDER BY salary DESC;
COMMIT;

在这个事务中,插入新员工后,查询按照薪资降序排列。由于事务的隔离性,在事务提交之前,其他并发事务不会看到新插入的数据,所以排序结果是基于当前事务内的数据状态。

如果不使用事务,在插入和查询之间可能会有其他事务插入数据,导致排序结果不准确。因此,在涉及排序的操作中,合理使用事务可以确保数据的一致性和查询结果的稳定性。

跨表排序

当我们需要从多个表中获取数据并进行排序时,需要注意连接条件和排序字段的选择。

假设我们有两个表employeesdepartmentsemployees表包含idnamedepartment_idsalary字段,departments表包含iddepartment_name字段。我们想获取每个员工及其所在部门名称,并按照部门名称和员工薪资进行排序:

SELECT e.name, d.department_name, e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
ORDER BY d.department_name, e.salary DESC;

在这个查询中,我们通过JOIN操作将两个表连接起来,然后按照department_name升序和salary降序对结果进行排序。

排序与全文搜索

虽然SQLite的全文搜索功能与常规的排序操作有所不同,但在某些场景下可以结合使用。

假设我们有一个documents表,用于存储文本内容,并且我们启用了全文搜索虚拟表。我们想搜索包含特定关键词的文档,并按照文档与关键词的相关性和文档的创建日期进行排序:

-- 创建全文搜索虚拟表
CREATE VIRTUAL TABLE document_search USING fts5(content);

-- 插入数据到全文搜索虚拟表
INSERT INTO document_search (content) SELECT content FROM documents;

-- 搜索并排序
SELECT documents.*, document_search.rank
FROM documents
JOIN document_search ON documents.content MATCH 'keyword'
ORDER BY document_search.rank, documents.created_date DESC;

在这个例子中,我们首先创建了一个全文搜索虚拟表document_search,并将documents表中的内容插入其中。然后,在搜索时,通过MATCH操作找到相关文档,并结合全文搜索的rank(相关性排名)和文档的创建日期进行排序。这样可以确保最相关且最新的文档排在前面。

排序缓存的考虑

在一些频繁进行排序的应用场景中,考虑排序缓存可以提高性能。虽然SQLite本身没有内置的排序缓存机制,但我们可以在应用程序层面实现。

例如,如果我们的应用经常查询某个特定条件下的排序结果,我们可以在应用程序中缓存这些结果。当再次请求相同条件的排序数据时,直接从缓存中获取,而不需要再次执行SQLite查询。

一种简单的实现方式是使用内存缓存(如Python中的functools.lru_cache):

import sqlite3
from functools import lru_cache

@lru_cache(maxsize = 128)
def get_sorted_employees(condition):
    conn = sqlite3.connect('employees.db')
    cursor = conn.cursor()
    query = f"SELECT * FROM employees {condition} ORDER BY salary DESC"
    cursor.execute(query)
    result = cursor.fetchall()
    conn.close()
    return result

在这个Python代码示例中,get_sorted_employees函数使用lru_cache装饰器来缓存查询结果。当相同的condition再次被调用时,直接从缓存中返回结果,而不需要重新执行SQLite查询和排序操作。

动态排序

在某些应用中,排序条件可能需要根据用户输入或其他动态因素进行变化。SQLite可以通过动态SQL来实现这种动态排序。

例如,我们有一个Web应用,用户可以选择按照不同的字段对员工列表进行排序。在Python中,我们可以这样实现:

import sqlite3

def get_sorted_employees(sort_field):
    conn = sqlite3.connect('employees.db')
    cursor = conn.cursor()
    valid_fields = ['name', 'age','salary']
    if sort_field not in valid_fields:
        sort_field = 'name'
    query = f"SELECT * FROM employees ORDER BY {sort_field}"
    cursor.execute(query)
    result = cursor.fetchall()
    conn.close()
    return result

在这个示例中,sort_field参数根据用户输入动态变化。在构建SQL查询时,我们先检查sort_field是否为合法字段,然后动态构建ORDER BY子句,从而实现根据用户选择进行动态排序。

通过以上全面深入的介绍,涵盖了SQLite在过滤、限定与排序方面的各种技巧和应用场景。无论是简单的单表查询,还是复杂的多表连接、递归查询等,都能找到相应的优化方法和实现方式。在实际开发中,根据具体需求灵活运用这些技巧,可以有效提升数据库操作的效率和质量。