Introduction to SQL for Data Science
作者:禅与计算机程序设计艺术
1.简介
随着大数据的不断增长、数据量的增加、计算设备的普及,以及互联网的发展,数据处理变得越来越复杂、越来越重要。数据的获取、整合、存储、分析等各个环节均需要用到各种编程语言、数据库管理系统(Database Management System,DBMS),这些技术都是由专业工程师来进行研发、维护和应用的。
SQL(Structured Query Language)是一种用于管理关系型数据库的标准化语言。它具备强大的查询功能,能够对数据库中的数据快速、有效地检索、汇总、分析、过滤、更新和修改。本文将会从基础语法、数据类型、运算符、函数、条件语句、分组排序、聚集函数、子查询、视图、触发器等方面,详细介绍SQL在数据科学领域的应用。
本文不会涉及复杂的数据建模过程,只从实际应用角度出发,重点介绍SQL的基本语法、数据类型、运算符、函数、条件语句、分组排序、聚集函数、子查询、视图、触发器等技术。读者可以充分利用这部分知识学习和应用于实际项目。
2.基本概念术语说明
2.1 数据模型
在数据分析和处理过程中,数据通常被组织成不同的表格形式。这种形式称为数据模型。数据模型的目的是对数据的逻辑结构进行抽象,从而方便地对其进行表示、管理、操纵和查询。
关系型数据模型与NoSQL数据模型:
- 关系型数据库(Relational Database)
 
关系型数据库是最常用的一种数据模型。它以表格的形式存储数据,每行对应一条记录,每列代表一个属性或字段。关系型数据库可以按照结构化查询语言(Structured Query Language,SQL)对数据进行检索、插入、删除、更新等操作。
- NoSQL数据模型
 
NoSQL(Not Only Structured Query Language)是一个泛指非关系型的数据库,其支持无模式、高可扩展性和分布式数据库设计理念。它提供了一系列数据模型,包括文档型、键值对型、图形型、列存型和时间序列型等。其中文档型和键值对型是最常见的两种NoSQL数据模型。
 * 文档型数据库(Document-Oriented Database)
        以文档的方式存储数据。它将数据存储为一系列集合,每个集合中存储多个文档。文档可以是嵌套的、拥有多个键值的形式。
MongoDB、Couchbase、Firebase都是典型的文档型数据库。
 * 键值对型数据库(Key-Value Store)
        以键值对的方式存储数据。它将数据存储为键值对,其中每个值都是一个字节数组。键值对型数据库不能保证数据之间的关系,只能通过键来访问数据。Redis、Memcached就是典型的键值对型数据库。
2.2 SQL语言简介
SQL(Structured Query Language,结构化查询语言)是用于管理关系型数据库的标准语言。它是一种声明式的语言,它的一般工作流程如下:
- 创建并定义数据库对象(如表、视图、索引、约束等)。
 - 使用SELECT语句从数据库表或视图中检索数据。
 - 使用INSERT、UPDATE或DELETE语句向数据库表中插入、更新或删除数据。
 - 对数据执行各种操作,如排序、过滤、聚集、分组等。
 
SQL语言共有四种主要的类别:DML(Data Manipulation Language)、DDL(Data Definition Language)、DCL(Data Control Language)和TCL(Transaction Control Language)。
- DML:用于操作数据库表中的数据,包括SELECT、INSERT、UPDATE、DELETE等。
 - DDL:用于定义、修改、删除数据库对象,包括CREATE、ALTER、DROP、TRUNCATE等。
 - DCL:用于控制数据库用户权限、事务等,包括GRANT、REVOKE、COMMIT、ROLLBACK等。
 - TCL:用于管理事务,包括BEGIN TRANSACTION、END TRANSACTION等。
 
2.3 SQL常用语句分类
2.3.1 SELECT语句
SELECT语句用于从数据库表或视图中选择数据。语法格式如下:
    SELECT [DISTINCT] column_name(s) 
    FROM table_name
    [WHERE condition] 
    [ORDER BY column_name [ASC|DESC]] 
    [LIMIT {[offset],} row_count]
    
      
      
      
      
    
    代码解读
        column_name:要查询的列名。[DISTINCT]:去除重复项。table_name:要查询的表名。[WHERE condition]:指定查询条件,根据指定的条件来筛选数据。[ORDER BY column_name [ASC|DESC]]:按指定列排序,默认升序。[LIMIT {[offset],} row_count]:限制返回结果的行数,可指定偏移量和数量。
示例:
    -- 查询 employee 表中的所有数据
    SELECT * FROM employee; 
    
    -- 查询 employee 表中的 name 和 salary 列的数据
    SELECT name, salary FROM employee;
    
    -- 查询 employee 表中 name 为 'John' 的数据
    SELECT * FROM employee WHERE name='John';
    
    -- 查询 employee 表中 salary 降序排列的数据
    SELECT * FROM employee ORDER BY salary DESC;
    
    -- 查询 employee 表中 name 为 'John' 或 'Mary' 的数据
    SELECT * FROM employee WHERE name IN ('John', 'Mary');
    
    -- 查询 employee 表中第 1~3 行的数据
    SELECT * FROM employee LIMIT 3 OFFSET 0;
    
    -- 查询 employee 表中第 4~7 行的数据
    SELECT * FROM employee LIMIT 3 OFFSET 3;
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        2.3.2 INSERT INTO语句
INSERT INTO语句用于向数据库表中插入新的数据。语法格式如下:
    INSERT INTO table_name (column1, column2,...) VALUES (value1, value2,...);
    
    
    代码解读
        table_name:要插入的表名。(column1, column2,...):要插入的列名列表。(value1, value2,...):要插入的值列表。
示例:
    -- 在 employee 表中插入一条数据
    INSERT INTO employee (id, name, age, salary) 
    VALUES (1, 'John', 30, 5000);
    
    -- 在 department 表中插入多条数据
    INSERT INTO department (id, name) 
    VALUES 
      (1, 'Sales'),
      (2, 'Marketing'),
      (3, 'Finance'),
      (4, 'Operations');
    
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        2.3.3 UPDATE语句
UPDATE语句用于更新数据库表中的数据。语法格式如下:
    UPDATE table_name SET column1=value1, column2=value2,... 
    [WHERE condition];
    
      
    
    代码解读
        table_name:要更新的表名。(column1=value1, column2=value2,...):要更新的列和值。[WHERE condition]:指定更新条件,根据指定的条件来确定要更新的数据。
示例:
    -- 更新 employee 表中 id 为 1 的员工信息
    UPDATE employee SET age=35, salary=6000 WHERE id=1;
    
    -- 将 department 表中所有部门名称改为小写字母
    UPDATE department SET name=LOWER(name);
    
      
      
      
      
    
    代码解读
        2.3.4 DELETE语句
DELETE语句用于删除数据库表中的数据。语法格式如下:
    DELETE FROM table_name 
    [WHERE condition];
    
      
    
    代码解读
        table_name:要删除的表名。[WHERE condition]:指定删除条件,根据指定的条件来确定要删除的数据。
示例:
    -- 删除 employee 表中 id 为 1 的员工信息
    DELETE FROM employee WHERE id=1;
    
    -- 清空 department 表中的所有数据
    DELETE FROM department;
    
      
      
      
      
    
    代码解读
        2.3.5 ALTER TABLE语句
ALTER TABLE语句用于修改数据库表的结构。语法格式如下:
    ALTER TABLE table_name {ADD COLUMN column_definition | DROP COLUMN column_name};
    
    
    代码解读
        table_name:要修改的表名。{ADD COLUMN column_definition | DROP COLUMN column_name}:要添加或删除的列名及其定义或名称。
示例:
    -- 添加 salary 列到 employee 表中
    ALTER TABLE employee ADD COLUMN salary INTEGER DEFAULT 0;
    
    -- 删除 age 列
    ALTER TABLE employee DROP COLUMN age;
    
      
      
      
      
    
    代码解读
        2.3.6 CREATE INDEX语句
CREATE INDEX语句用于创建索引。语法格式如下:
    CREATE INDEX index_name ON table_name (column_name);
    
    
    代码解读
        index_name:索引名称。table_name:要创建索引的表名。(column_name):要创建索引的列名。
示例:
    -- 创建 name 列的索引
    CREATE INDEX idx_employee_name ON employee (name);
    
      
    
    代码解读
        2.3.7 JOIN语句
JOIN语句用于合并两个表中的数据。语法格式如下:
    SELECT column_name(s) 
    FROM table1 
    INNER JOIN table2 
    ON table1.common_column = table2.common_column;
    
      
      
      
    
    代码解读
        column_name(s):要查询的列名。table1、table2:要连接的表名。.common_column:要比较的列名。
示例:
    -- 查询 employee 表中的所有数据,同时显示 department 表中的相应数据
    SELECT e.*, d.name AS dept_name 
    FROM employee e 
    INNER JOIN department d 
    ON e.dept_id = d.id;
    
    -- 查询 customer 表中 order_date 小于等于 '2019-01-31' 的订单信息,同时显示相关产品信息
    SELECT c.*, o.*, p.* 
    FROM customer c 
    INNER JOIN orders o 
    ON c.customer_id = o.customer_id 
    INNER JOIN products p 
    ON o.product_id = p.product_id 
    WHERE o.order_date <= '2019-01-31';
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        2.3.8 UNION语句
UNION语句用于合并两个或多个SELECT语句的结果集。语法格式如下:
    SELECT column_name(s) 
    FROM table_name1 
    UNION ALL 
    SELECT column_name(s) 
    FROM table_name2;
    
      
      
      
      
    
    代码解读
        column_name(s):要查询的列名。table_nameN:要合并的表名。ALL:保留重复行。
示例:
    -- 查询 employee 表和 department 表的所有数据
    SELECT * FROM employee 
    UNION ALL 
    SELECT * FROM department;
    
    -- 查询 product 表中 price 大于等于 5000 的数据,并且按照 price 降序排列;再与 employee 表中对应的员工姓名数据合并显示
    SELECT p.*, e.name 
    FROM product p 
    INNER JOIN employee e 
    ON p.employee_id = e.id 
    WHERE p.price >= 5000 
    ORDER BY p.price DESC;
    
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        2.3.9 CREATE VIEW语句
CREATE VIEW语句用于创建视图。语法格式如下:
    CREATE VIEW view_name AS 
    SELECT statement;
    
      
    
    代码解读
        view_name:视图名称。AS:关键字。SELECT statement:查询语句。
示例:
    -- 创建视图 vw_department_salary,该视图显示了 department 表中每个部门的平均薪水
    CREATE VIEW vw_department_salary AS
    SELECT d.name AS department, AVG(e.salary) AS avg_salary 
    FROM department d 
    INNER JOIN employee e 
    ON d.id = e.dept_id 
    GROUP BY d.name;
    
    -- 查看视图 vw_department_salary 中的数据
    SELECT * FROM vw_department_salary;
    
      
      
      
      
      
      
      
      
      
    
    代码解读
        2.3.10 TRIGGER语句
TRIGGER语句用于创建触发器。语法格式如下:
    CREATE TRIGGER trigger_name 
    BEFORE/AFTER insert/update/delete 
    ON table_name 
    FOR EACH ROW 
    BEGIN
       -- trigger body
    END;
    
      
      
      
      
      
      
    
    代码解读
        trigger_name:触发器名称。BEFORE/AFTER:触发时机,分别表示在事件之前或之后触发。insert/update/delete:事件类型。table_name:要监听的表名。FOR EACH ROW:触发器作用范围,每次触发针对单行数据。BEGIN:关键字。-- trigger body:触发器主体,可以编写任意的SQL语句。
示例:
    -- 创建触发器 tg_employee_biu,在 employee 表上新增、更新或删除数据时,自动更新 vw_department_salary 视图
    CREATE OR REPLACE TRIGGER tg_employee_biu
    AFTER INSERT OR UPDATE OR DELETE ON employee
    FOR EACH ROW
    EXECUTE PROCEDURE update_vw_department_salary();
    
    CREATE FUNCTION update_vw_department_salary() RETURNS TRIGGER AS
$$
    DECLARE 
    v_avg_salary NUMERIC(10,2);
    BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE vw_department_salary 
        SET avg_salary = COALESCE((SELECT AVG(salary) FROM employee WHERE dept_id = NEW.dept_id), 0)
        WHERE department = (SELECT name FROM department WHERE id = NEW.dept_id);
    ELSIF TG_OP = 'UPDATE' THEN
        SELECT AVG(salary) INTO v_avg_salary FROM employee WHERE dept_id = OLD.dept_id;
        IF (v_avg_salary <> NULL AND v_avg_salary <> 0) THEN
            UPDATE vw_department_salary 
            SET avg_salary = ((COALESCE((SELECT COUNT(*) FROM employee WHERE dept_id = OLD.dept_id), 0)*OLD.avg_salary + COALESCE((SELECT COUNT(*) FROM employee WHERE dept_id = NEW.dept_id), 0)*NEW.salary)/(COALESCE((SELECT COUNT(*) FROM employee WHERE dept_id = OLD.dept_id), 0)+COALESCE((SELECT COUNT(*) FROM employee WHERE dept_id = NEW.dept_id), 0)))
            WHERE department = (SELECT name FROM department WHERE id = NEW.dept_id);
        END IF;    
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE vw_department_salary 
        SET avg_salary = COALESCE((SELECT AVG(salary) FROM employee WHERE dept_id = OLD.dept_id), 0)
        WHERE department = (SELECT name FROM department WHERE id = OLD.dept_id);   
    END IF;
    
    RETURN NEW;
    END;
   
$$
LANGUAGE plpgsql;
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        3.核心算法原理和具体操作步骤以及数学公式讲解
3.1 算法概述
3.2 概率论
3.3 数据预处理
3.4 特征工程
3.5 机器学习
3.6 模型评估
3.7 模型调优
4.具体代码实例和解释说明
4.1 SQL SELECT语句
    SELECT DISTINCT city FROM customers WHERE country = 'USA';
    
    
    代码解读
        - 这里使用DISTINCT关键词来去掉重复的城市名称。
- 从customers表中选取country为“USA”的城市名称。
 
 
    SELECT MAX(age) FROM employees;
    
    
    代码解读
        - 这里使用MAX函数来找到年龄最大的人员。
- 从employees表中查找年龄最大的员工。
 
 
    SELECT MIN(salary) FROM employees WHERE job_title LIKE '%Manager%';
    
    
    代码解读
        - 这里使用MIN函数找到管理人员的最低工资。
- 从employees表中查找工资最低的管理人员。
 
 
    SELECT COUNT(*) FROM customers;
    
    
    代码解读
        - 这里使用COUNT函数统计顾客数量。
- 从customers表中查找所有顾客数量。
 
 
    SELECT customer_name, email FROM customers WHERE customer_name NOT LIKE '%Smith%' AND email LIKE '%gmail.com';
    
    
    代码解读
        - 这里使用NOT LIKE关键词排除以Smith结尾的顾客,然后使用LIKE关键词筛选出email以gmail.com结尾的顾客。
- 从customers表中查找顾客姓名不以“Smith”结尾且邮箱地址以“@gmail.com”结尾的顾客信息。
 
 
    SELECT SUM(price*quantity) as total_revenue FROM sales;
    
    
    代码解读
        - 这里使用SUM函数计算销售额总和。
- 从sales表中查找销售额总和。
 
 
    SELECT CONCAT(first_name,' ',last_name) as full_name FROM employees;
    
    
    代码解读
        - 这里使用CONCAT函数拼接全名。
- 从employees表中查找所有的员工全名。
 
 
    SELECT category_name, AVG(sale_amount) as average_sale FROM sales GROUP BY category_name;
    
    
    代码解读
        - 这里使用AVG函数计算每个品类的销售额平均值。
- 从sales表中查出各个品类及其平均销售额。
 
 
    SELECT YEAR(order_date) as year, MONTH(order_date) as month, COUNT(*) as num_orders FROM orders GROUP BY YEAR(order_date), MONTH(order_date);
    
    
    代码解读
        - 这里使用YEAR和MONTH函数统计每个月订单数量。
- 从orders表中查找每个月的订单数量。
 
 
    SELECT customer_name, AVG(purchased_items) as avg_items_purchased FROM purchases GROUP BY customer_name HAVING avg_items_purchased > 2;
    
    
    代码解读
        - 这里使用AVG函数找出购买商品最多的顾客。
- 从purchases表中查找每个顾客的购买商品平均数量,且仅显示购买商品超过2件的顾客信息。
 
 
    SELECT state, AVG(rental_rate) as avg_rental_rate FROM rentals WHERE property_type = 'Condo' GROUP BY state;
    
    
    代码解读
        - 这里使用AVG函数找出州份的平均租金费率。
- 从rentals表中查找某个物业类型的州份的平均租金费率。
 
 
4.2 SQL INSERT INTO语句
    INSERT INTO customers (customer_name, email, address) VALUES ('Jane Doe', 'jdoe@gmail.com', '123 Main St.');
    
    
    代码解读
        - 这里使用INSERT INTO语句向customers表中插入一条顾客信息。
- 插入的记录包含顾客姓名、邮件地址和地址。
 
 
    INSERT INTO orders (customer_id, item_id, quantity, purchase_date) VALUES (1, 1, 1, NOW());
    
    
    代码解读
        - 这里使用INSERT INTO语句向orders表中插入一条订单信息。
- 插入的记录包含顾客ID、商品ID、数量和采购日期。
 
 
4.3 SQL UPDATE语句
    UPDATE customers SET email='<EMAIL>' WHERE customer_id=1;
    
    
    代码解读
        - 这里使用UPDATE语句更新customers表中的某条记录。
- 更新的记录仅包含顾客ID和新的邮件地址。
 
 
    UPDATE departments SET manager_id=NULL WHERE dept_name='HR';
    
    
    代码解读
        - 这里使用UPDATE语句更新departments表中的某条记录。
- 更新的记录仅包含部门名称和清除之前设定的经理。
 
 
    UPDATE orders SET quantity=quantity+1 WHERE order_id=1;
    
    
    代码解读
        - 这里使用UPDATE语句更新orders表中的某条记录。
- 更新的记录仅包含订单号和数量的增加。
 
 
    UPDATE products SET price=price*1.1 WHERE category='Clothing';
    
    
    代码解读
        - 这里使用UPDATE语句更新products表中的某条记录。
- 更新的记录仅包含某个特定类别的商品的价格的提高。
 
 
    UPDATE rentals SET rental_duration=rental_duration+1 WHERE property_id=1;
    
    
    代码解读
        - 这里使用UPDATE语句更新rentals表中的某条记录。
- 更新的记录仅包含某个物业的租赁期限的延长。
 
 
4.4 SQL DELETE语句
    DELETE FROM customers WHERE customer_id=1;
    
    
    代码解读
        - 这里使用DELETE语句删除customers表中的某条记录。
- 需要删除的记录仅包含顾客ID。
 
 
    DELETE FROM orders WHERE order_id>100;
    
    
    代码解读
        - 这里使用DELETE语句删除orders表中的某些记录。
- 需要删除的记录仅包含订单号。
 
 
    DELETE FROM employees WHERE job_title!='Sales Manager';
    
    
    代码解读
        - 这里使用DELETE语句删除employees表中的某些记录。
- 需要删除的记录仅包含不是销售经理的员工。
 
 
    DELETE FROM products WHERE price<500;
    
    
    代码解读
        - 这里使用DELETE语句删除products表中的某些记录。
- 需要删除的记录仅包含价格低于500元的商品。
 
 
    DELETE FROM rentals WHERE rental_duration=1;
    
    
    代码解读
        - 这里使用DELETE语句删除rentals表中的某些记录。
- 需要删除的记录仅包含租赁期限为1天的物业。
 
 
4.5 SQL ALTER TABLE语句
    ALTER TABLE customers ADD phone VARCHAR(15);
    
    
    代码解读
        - 这里使用ALTER TABLE语句给customers表添加phone列。
- 添加的列仅包含电话号码。
 
 
    ALTER TABLE orders MODIFY COLUMN quantity INT UNSIGNED;
    
    
    代码解读
        - 这里使用ALTER TABLE语句更改orders表中的quantity列的数据类型。
- 修改后的列包含整数数据,而且无符号。
 
 
    ALTER TABLE rentals DROP PRIMARY KEY;
    
    
    代码解读
        - 这里使用ALTER TABLE语句删除rentals表中的主键。
- 不需要主键来唯一标识每个物业。
 
 
4.6 SQL CREATE INDEX语句
    CREATE INDEX idx_customer_name ON customers (customer_name);
    
    
    代码解读
        - 这里使用CREATE INDEX语句创建顾客姓名的索引。
- 可以加快检索速度。
 
 
    CREATE UNIQUE INDEX idx_employee_ssn ON employees (social_security_number);
    
    
    代码解读
        - 这里使用CREATE UNIQUE INDEX语句创建社保号的唯一索引。
- 确保每个人的社保号只出现一次。
 
 
    CREATE INDEX idx_sales_item ON sales (category_id, sale_amount);
    
    
    代码解读
        - 这里使用CREATE INDEX语句创建组合索引。
- 可帮助优化查询效率。
 
 
    CREATE INDEX idx_orders_date ON orders (order_date ASC);
    
    
    代码解读
        - 这里使用CREATE INDEX语句创建一个升序索引。
- 可以加速特定日期的搜索。
 
 
4.7 SQL JOIN语句
    SELECT e.*, d.name AS dept_name 
    FROM employees e 
    INNER JOIN departments d 
    ON e.dept_id = d.id;
    
      
      
      
    
    代码解读
        - 这里使用INNER JOIN语句连接employees和departments表。
- 返回employees表中所有记录,并且连接departments表中相同ID的记录。
 
 
    SELECT customer_name, email, items_purchased 
    FROM purchases 
    INNER JOIN (
    SELECT customer_id, SUM(quantity) as items_purchased 
    FROM purchases 
    GROUP BY customer_id
    ) t 
    ON purchases.customer_id = t.customer_id;
    
      
      
      
      
      
      
      
    
    代码解读
        - 这里使用INNER JOIN语句连接purchases和另一个子查询,来计算顾客购买的商品数量。
- 首先使用子查询计算每个顾客购买的总数,然后使用父查询连接起来。
 
 
    SELECT customer_name, state, AVG(rental_rate) as avg_rental_rate 
    FROM rentals r 
    INNER JOIN customers c 
    ON r.customer_id = c.customer_id 
    WHERE property_type = 'Condo' 
    GROUP BY customer_name, state;
    
      
      
      
      
      
    
    代码解读
        - 这里使用INNER JOIN语句连接rentals和customers表,来显示每个州份的平均租金费率。
- 只显示那些类型为Condo的物业,并且显示顾客姓名和所在州份。
 
 
4.8 SQL UNION语句
    SELECT a.state, b.state, c.state 
    FROM states a 
    INNER JOIN states b 
    ON a.population < b.population 
    INNER JOIN states c 
    ON b.population < c.population 
    UNION 
    SELECT a.state, b.state, c.state 
    FROM states a 
    INNER JOIN states b 
    ON a.area < b.area 
    INNER JOIN states c 
    ON b.area < c.area 
    ORDER BY area DESC;
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        - 这里使用UNION语句合并三个SELECT语句的结果集。
- 第一个SELECT语句返回三个州份,它们分别按人口、面积大小进行排序。
 - 第二个SELECT语句返回三个州份,它们分别按人口、面积大小进行排序,但是先后顺序颠倒。
 - 可以查看三个州份中人口或面积最小的三个国家。
 
 
