SQL基础

目录
警告
本文最后更新于 2023-11-25,文中内容可能已过时。

1. 创建数据库表

​ 创建数据表(Table)之前需要先创建数据库(Database),或者必许在已存在的数据库上创建数据表。

  1. 数据库创建语句CREATE DATABASE 数据库名

    1
    
    CREATE DATABASE shop;
  2. 数据表创建

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    CREATE TABLE <表名>
    (
    	<列名1> <数据类型> <该列所需约束>,
        ......
        <该表的约束1>, <该表的约束2>, .....
    );
    
    CREATE TABLE Product
    (
    	product_id		CHAR(4)			NOT NULL,	// 商品编号
        product_name 	VARCHAR(100)	NOT NULL,	// 商品名称
        product_type	VARCHAR(32)		NOT NULL,	// 商品种类
        sale_price		INTEGER,					// 销售单价
        purchase_price 	INTEGER,					// 进货单价
        regist_date		DATE,						// 登记日期
        PRIMARY KEY (product_id));
    )

    表的删除和更新

    表的删除

    1
    
    DROP TABLE 表名;

    表定义的更新(ALTER TABLE

    1. 添加列

      1
      
      ALTER TABLE <表名> ADD COLUMN <列的定义>;
    2. 删除列

      1
      
      ALTER TABLE <表名> DROP COLUMN <列名>;
    3. 向表中插入数据

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      INSERT INTO <表名> VALUES('VALUE1','VALUE2',...);
      
      START TRANSACTION;
      INSERT INTO Product VALUES ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20');
      INSERT INTO Product VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
      INSERT INTO Product VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
      INSERT INTO Product VALUES ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
      INSERT INTO Product VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
      INSERT INTO Product VALUES ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20');
      INSERT INTO Product VALUES ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28');
      INSERT INTO Product VALUES ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11');
      COMMIT;

    表的修改

    1. 变更表名

      1
      
      RENAME TABLE <原表名> TO <新表名>;

2. 查询基础

2.1 SELECT语句基础

列的查询

1
2
3
SELECT <1>, <2>, .... FROM <表名>;

SELECT product_id, product_name, purchase_price FROM Product;

查询出表中所有的列

1
2
3
SELECT * FROM <表名>;

SELECT * FROM Product;

为列设定别名

别名可以使用中文,需要双引号括起来("")

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SELECT <1> AS <别名1>, .... FROM <表名>;

SELECT  product_id AS id, 
       product_name AS name, 
       purchase_price AS price 
  FROM Product;
  
SELECT product_id AS "商品编号", 
       product_name AS "商品名称", 
       purchase_price AS "进货单价"
  FROM Product;

常数的查询

1
2
3
SELECT '商品' AS string, 38 AS number, '2009-02-24' AS date,
       product_id, product_name
  FROM Product;

从结果中删除重复行

​ 在使用 DISTINCT 时,NULL 也被视为一类数据。NULL 存在于多行中时,也会被合并为一条 NULL 数据。

1
2
3
4
5
6
7
SELECT DISTINCT <1>, <2> FROM <表名>;

SELECT DISTINCT product_type 
  FROM Product;
  
SELECT DISTINCT product_type, regist_date 
  FROM Product;

条件查询(WHERE

WHERE字句要紧跟在FROM字句之后。

1
2
3
4
5
SELECT <列名>,... FROM <表名> WHERE <条件表达式>;

SELECT product_name, product_type 
  FROM Product
 WHERE product_type = '衣服';

注释

  • 单行注释,--
  • 多行注释,/**/之间

2.2 算术运算符和比较运算符

  • 算术运算符

    1
    2
    3
    
    SELECT product_name, sale_price,
           sale_price * 2 AS "sale_price_x2" 
      FROM Product;

    四则运算:

    含义运算符
    加法运算+
    减法运算-
    乘法运算*
    除法运算/

    SELECT子句中可以使用常数或者表达式。所有包含 NULL 的计算,结果肯定是 NULL。

  • 比较运算符

    1
    2
    3
    
    SELECT product_name, product_type 
      FROM Product
     WHERE sale_price <> 500;
    运算符含义
    =和~相等
    <>和~不相等
    >=大于等于~
    >大于~
    <=小于等于~
    <小于~
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    SELECT product_name, product_type, sale_price 
      FROM Product
     WHERE sale_price >= 1000;
    
    SELECT product_name, product_type, regist_date 
      FROM Product
     WHERE regist_date < '2009-09-27';
    
    SELECT product_name, sale_price, purchase_price 
      FROM Product
     WHERE sale_price - purchase_price >= 500;

    注意:一定要让不等号在左,等号在右。

    字符串类型的数据原则上按照字典顺序进行排序,不能与数字的大小顺序混淆。

    不能对NULL使用比较运算符:SQL 提供了专门用来判断是否为 NULL 的 IS (NOT) NULL 运算符。

2.3 逻辑运算符

  • NOT运算符

    NOT 不能单独使用,必须和其他查询条件组合起来使用。

    1
    2
    3
    
    SELECT product_name, product_type, sale_price 
      FROM Product
     WHERE NOT sale_price >= 1000;

    NOT运算符用来否定某一条件,但是不能滥用。

  • AND运算符和OR运算符

    在 WHERE 子句中使用 AND 运算符或者 OR 运算符,可以对多个查询条件进行组合。

    • AND 运算符在其两侧的查询条件都成立时整个查询条件才成立,其意思相当于“并且”。

    • OR 运算符在其两侧的查询条件有一个成立时整个查询条件都成立,其意思相当于“或者”。

      1
      2
      3
      4
      
      SELECT  product_name, purchase_price 
        FROM Product
       WHERE  product_type = '厨房用具'
         AND sale_price >= 3000;

    多个查询条件进行组合时,需要使用AND运算符或者OR运算符。

  • 通过括号强化处理

    AND 运算符优先于 OR 运算符。

    1
    2
    3
    4
    5
    
    SELECT product_name, product_type, regist_date 
      FROM Product
     WHERE product_type = '办公用品'
       AND (   regist_date = '2009-09-11'
            OR regist_date = '2009-09-20');
  • 逻辑运算符和真值

NOTANDOR 称为逻辑运算符。真值就是TRUE或者FALSE

​ 使用 AND 运算符进行的逻辑运算称为逻辑积,使用 OR 运算符进行的逻辑运算称为逻辑和

3. 聚合与排序

3.1 对表进行聚合查询

聚合函数

常用函数:

函数名作用
COUNT计 算 表 中 的 记 录 数( 行 数 )
SUM计算表中数值列中数据的合计值
AVG计算表中数值列中数据的平均值
MAX求出表中任意列中数据的最大值
MIN求出表中任意列中数据的最小值

用于汇总的函数称为聚合函数或者聚集函数。

1
SELECT COUNT(参数) FROM <表名>;

计算NULL之外的数据的行数

1
2
SELECT COUNT(purchase_price) // NULL不会计算进去
  FROM Product;

对于 COUNT 函数来说,参数列不同计算的结果也会发生变化。

COUNT函数的结果根据参数的不同而不同。COUNT(*)会得到包含NULL的数据行数,而COUNT(<列名>)会得到NULL之外的数据行数。

计算合计值

使用SUM()函数。

1
2
SELECT SUM(sale_price), SUM(purchase_price) 
  FROM Product;

所有的聚合函数,如果以列名为参数,那么在计算之前就已经把NULL 排除在外了。因此,无论有多少个 NULL 都会被无视。

计算平均值

使用AVG ()函数,语法与SUM()函数相同

1
2
SELECT AVG(sale_price) 
  FROM Product;

计算最大值和最小值

最大值使用MAX()函数,最小值使用MIN()函数。

1
2
3
4
5
SELECT MAX(sale_price), MIN(purchase_price) 
  FROM Product;
  
SELECT MAX(regist_date), MIN(regist_date) 
  FROM Product;

使用聚合函数删除重复值(关键字DISTINCT)

1
2
SELECT COUNT(DISTINCT product_type) 
  FROM Product;

想要计算值的种类时,可以在COUNT函数的参数中使用DISTINCT

不仅限于 COUNT 函数,所有的聚合函数都可以使用 DISTINCT

在聚合函数的参数中使用DISTINCT,可以删除重复数据。

3.2 对表进行分组

GROUP BY子句

使用聚合函数和GROUP BY子句时需要注意以下 4点:

  1. 只能写在SELECT子句之中
  2. GROUP BY子句中不能使用SELECT子句中列的别名
  3. GROUP BY子句的聚合结果是无序的
  4. WHERE子句中不能使用聚合函数
1
2
3
4
5
6
7
SELECT <列名1>, <列名2>, <列名3>, ……
  FROM <表名>
 GROUP BY <列名1>, <列名2>, <列名3>, ……;
 
SELECT product_type, COUNT(*) 
  FROM Product
 GROUP BY product_type;

GROUP BY 子句中指定的列称为聚合键或者分组列GROUP BY 子句也和 SELECT 子句一样,可以通过逗号分隔指定多列。

GROUP BY 子句的书写位置也有严格要求,一定要写在FROM 语句之后(如果有 WHERE 子句的话需要写在 WHERE 子 句 之 后 )。

聚合键中包含NULL的情况

当聚合键中包含 NULL 时,也会将NULL 作为一组特定的数据。聚合键中包含NULL时,在结果中会以“不确定”行(空行)的形式表现出来。

使用WHERE子句时GROUP BY的执行结果

1
2
3
4
SELECT <列名1>, <列名2>, <列名3>, ……
  FROM <表名>
 WHERE 
 GROUP BY <列名1>, <列名2>, <列名3>, ……;

像这样使用 WHERE 子句进行汇总处理时,会先根据 WHERE 子句指定的条件进行过滤,然后再进行汇总处理。

1
2
3
4
SELECT purchase_price, COUNT(*) 
  FROM Product
 WHERE product_type = '衣服'
 GROUP BY purchase_price;

GROUP BY WHERE 并用时 SELECT 语句的执行顺序:

1
FROM  WHERE  GROUP BY  SELECT

与聚合函数和GROUP BY子句有关的常见错误

  1. SELECT子句中书写了多余的列

    在使用 COUNT 这样的聚合函数时,SELECT 子句中的元素有严格的限制。实际上,使用聚合函数时,SELECT 子句中只能存在以下三种元素。

    • 常数
    • 聚合函数
    • GROUP BY子句中指定的列名(也就是聚合键)

    使用GROUP BY子句时,SELECT子句中不能出现聚合键之外的列名。

  2. GROUP BY子句中写了列的别名

    GROUP BY 子句中是不能使用别名的。

  3. GROUP BY子句的结果能排序吗

    不能排序,结果是随机的。

  4. WHERE子句中使用聚合函数

    只有SELECT子句和HAVING子句(以及ORDER BY子句)中能够使用聚合函数。

3.3 为聚合结果指定条件

HAVING子句

对集合指定条件就需要使用HAVING 子句,HAVING 子句必须写在 GROUP BY 子句之后

1
2
3
4
SELECT <列名1>, <列名2>, <列名3>, …… 
  FROM <表名>
 GROUP BY <列名1>, <列名2>, <列名3>, …… 
HAVING <分组结果对应的条件>

使用HAVING子句时SELECT语句的顺序。

1
SELECT  FROM  WHERE  GROUP BY  HAVING
1
2
3
4
SELECT product_type, AVG(sale_price) 
  FROM Product
 GROUP BY product_type
HAVING AVG(sale_price) >= 2500;

HAVING子句的构成要素

HAVING 子句中能够使用的 3 种要素:

  • 常数
  • 聚合函数
  • GROUP BY子句中指定的列名(即聚合键)

HAVING 子句是用来指定“组”的条件的。因此,“行”所对应的条件还是应该写在 WHERE 子句当中。

WHERE 子句 = 指定行所对应的条件
HAVING 子句 = 指定组所对应的条件

聚合键所对应的条件不应该书写在HAVING子句当中,而应该书写在WHERE子句当中。

3.4 对查询结果进行排序

ORDER BY子句

1
2
3
4
5
6
7
SELECT <列名1>, <列名2>, <列名3>, …… 
  FROM <表名>
 ORDER BY <排序基准列1>, <排序基准列2>, ……
 
SELECT product_id, product_name, sale_price, purchase_price 
  FROM Product
ORDER BY sale_price;

子句的书写顺序:

1
 1. SELECT 子句  2. FROM 子句  3. WHERE 子句  4. GROUP BY 子句  5. HAVING 子句  6. ORDER BY 子句

指定升序或降序

ASCDESC 是 ascendent(上升的)和descendent(下降的)这两个单词的缩写。

1
2
3
SELECT product_id, product_name, sale_price, purchase_price 
  FROM Product
ORDER BY sale_price DESC;

未指定ORDER BY子句中排列顺序时会默认使用升序进行排列。

NULL的顺序

使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。

在排序键中使用显示用的别名

GROUP BY 子句中不能使用SELECT 子句中定义的别名,但是在 ORDER BY 子句中却是允许使用别名的。

1
2
3
SELECT product_id AS id, product_name, sale_price AS sp, purchase_price
  FROM Product
ORDER BY sp, id;

使用 HAVING 子句时 SELECT 语句的顺序:

1
FROM  WHERE  GROUP BY  HAVING  SELECT  ORDER BY

SELECT 子句的执行顺序在 GROUP BY 子句之后,ORDER BY 子句之前。

ORDER BY子句中可以使用的列

ORDER BY 子句中也可以使用存在于表中、但并不包含在 SELECT子句之中的列

1
2
3
SELECT product_name, sale_price, purchase_price 
  FROM Product
ORDER BY product_id;

ORDER BY子句中可以使用SELECT子句中未使用的列和聚合函数。

不要使用列编号

4. 数据更新

4.1 数据的插入(INSERT语句的使用方法)

INSERT语句

1
INSERT INTO <表名> (1, 2, 3, ……) VALUES (1, 2, 3, ……);
1
INSERT INTO Product (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20');
多行INSERT
1
2
3
INSERT INTO ProductIns VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'),
   ('0003', '运动T恤', '衣服', 4000, 2800, NULL),
   ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
列清单的省略
1
2
3
4
-- 包含列清单
INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
-- 省略列清单
INSERT INTO ProductIns VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
插入NULL

INSERT 语句中想给某一列赋予 NULL 值时,可以直接在 VALUES子句的值清单中写入 NULL

要插入 NULL 的列一定不能设置 NOT NULL 约束。向设置了 NOT NULL 约束的列中插入 NULL 时,INSERT 语句会出错,导致数据插入失败。

插入默认值
  • 通过显式方法插入默认值

    VALUES 子句中指定 DEFAULT 关键字

    1
    
    INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0007', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28');
  • 通过隐式方法插入默认值

    插入默认值时也可以不使用 DEFAULT 关键字,只要在列清单和 VALUES 中省略设定了默认值的列就可以了。

    • 通过隐式方法设定默认值

      1
      
      INSERT INTO Product (product_id, product_name, product_type, purchase_price, regist_date) VALUES ('0007', '擦菜板', '厨房用具', 790, '2009-04-28'); // 省略了sale_price列及对应的值
    • 未设定默认值的情况

      1
      2
      3
      4
      
      -- 省略purchase_price列(无约束):会赋予“NULL”
      INSERT INTO ProductIns (product_id, product_name, product_type, sale_price, regist_date) VALUES ('0008', '圆珠笔', '办公用品', 100, '2009-11-11');
      -- 省略product_name列(设置了NOT NULL约 束 ):错 误 !
      INSERT INTO ProductIns (product_id, product_type, sale_price, purchase_price, regist_date) VALUES ('0009', '办公用品', 1000, 500, '2009-12-12');

      省略 INSERT 语句中的列名,就会自动设定为该列的默认值(没有默认值时会设定为 NULL)。

    从其他表中复制数据

    创建ProductCopy表(表结构与Product完全一致,只是表名不一样而已):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    CREATE TABLE ProductCopy
    (
        product_id   CHAR(4)      NOT NULL, 
        product_name  VARCHAR(100) NOT NULL, 
        product_type VARCHAR(32)  NOT NULL, 
        sale_price  INTEGER,
        purchase_price INTEGER,
        regist_date   DATE,
        PRIMARY KEY (product_id)
    );
    INSERT … SELECT语句
    1
    2
    
    -- 将商品表中的数据复制到商品复制表中
    INSERT INTO ProductCopy (product_id, product_name, product_type, sale_price, purchase_price, regist_date) SELECT product_id, product_name, product_type, sale_price, purchase_price, regist_date FROM Product;

    多种多样的SELECT语句

    ProductType表:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    CREATE TABLE ProductType
    (
        product_type       VARCHAR(32) NOT NULL,
        sum_sale_price     INTEGER,
        sum_purchase_price INTEGER,
        PRIMARY KEY (product_type)
    );
    
    INSERT INTO ProductType (product_type, sum_sale_price, sum_purchase_price)
    SELECT product_type, SUM(sale_price), SUM(purchase_price)
    FROM Product
    GROUP BY product_type;

    4.2 数据的删除(DELETE语句的使用方法)

    DROP TABLE语句和DELETE语句

    1. DROP TABLE 语句可以将表完全删除
    2. DELETE 语句会留下表(容器),而删除表中的全部数据
    DELETE语句的基本语法
    1
    
    DELETE FROM <表名>;

    DELETE语句的删除对象并不是表或者列,而是记录(行)。

    指定删除对象的DELETE语句(搜索型DELETE)

    1
    
    DELETE FROM <表名> WHERE <条件>;

    可以通过WHERE子句指定对象条件来删除部分数据。与 SELECT 语句不同的是,DELETE 语句中不能使用 GROUP BYHAVINGORDER BY 三类子句,而只能使用WHERE 子 句 。

    删除和舍弃
    • 只能删除表中全部数据的TRUNCATE语句

      1
      
      TRUNCATE <表名>;

      DELETE 不同的是,TRUNCATE 只能删除表中的全部数据,而不能通过WHERE 子句指定条件来删除部分数据。也正是因为它不能具体地控制删除对象,
      所以其处理速度比 DELETE 要快得多。

    4.3 数据的更新(UPDATE语句的使用方法)

    UPDATE语句的基本语法

    1
    2
    
    UPDATE <表名> 
    SET <列名> = <表达式>;

    指定条件的UPDATE语句(搜索型UPDATE)

    1
    2
    3
    
    UPDATE <表名>
    	SET <列名> = <表达式> 
    WHERE <条件>;

    使用NULL进行更新

    使用 UPDATE 也可以将列更新为 NULL(该更新俗称为 NULL 清空)。

    1
    2
    3
    
    UPDATE Product
       SET regist_date = NULL 
     WHERE product_id = '0008';

    使用UPDATE语句可以将值清空为NULL(但只限于未设置NOT NULL约束的列)。

    多列更新

    • 使用逗号对列进行分隔排列

      1
      2
      3
      4
      
      UPDATE Product
         SET sale_price = sale_price * 10,
             purchase_price = purchase_price / 2
       WHERE product_type = '厨房用具';
    • 将列用()括起来的清单形式

      1
      2
      3
      
      UPDATE Product
         SET (sale_price, purchase_price) = (sale_price * 10, purchase_price / 2)
       WHERE product_type = '厨房用具';

    4.4 事务

    什么是事务

    事务就是需要在同一个处理单元中执行的一系列更新处理的集合 。

    创建事务

    1
    2
    3
    4
    5
    6
    
    事务开始语句;
    	DML语句;
    	DML语句;
    	DML语句;
    	......
    事务结束语句(COMMIT或者ROLLBACK;

    使用事务开始语句和事务结束语句,将一系列 DML 语句(INSERT/UPDATE/DELETE 语句)括起来,就实现了一个事务处理。

    各个DBMS的事务开始语句:

    • SQL Server、PostgreSQL

      1
      
      BEGIN TRANSACTION
    • MySQL

      1
      
      START TRANSACTION
    • Oracle、DB2

    更新商品信息的事务

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    START TRANSACTION;
        -- 将运动T恤的销售单价降低1000日元
        UPDATE Product
           SET sale_price = sale_price - 1000
         WHERE product_name = '运动T恤';
        -- 将T恤衫的销售单价上浮1000日元
        UPDATE Product
           SET sale_price = sale_price + 1000
         WHERE product_name = 'T恤衫';
    COMMIT;

    COMMIT——提交处理

    COMMIT 是提交事务包含的全部更新处理的结束指令,相当于文件处理中的覆盖保存。一旦提交,就无法恢复到事务开始前的状态了。

    COMMIT的流程 =直线进行
    1
    
    1.事务开始语句 ------> 2.执行更新语句(DML) ------> 3.执行COMMIT

    结束后的状态:②中的所有更新都被反映到了数据库中

    虽然我们可以不清楚事务开始的时间点,但是在事务结束时一定要仔细进行确认。

    ROLLBACK——取消处理

    ROLLBACK 是取消事务包含的全部更新处理的结束指令,相当于文件处理中的放弃保存。一旦回滚,数据库就会恢复到事务开始之前的状态。

    ROLLBACK的流程 =掉头回到起点
    1
    
    1.事务开始语句 ------> 2.执行更新语句(DML) ------> 3.执行ROLLBACK

    结束后的状态:和①执行前相同

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    START TRANSACTION;
        -- 将运动T恤的销售单价降低1000日元
        UPDATE Product
           SET sale_price = sale_price - 1000
         WHERE product_name = '运动T恤';
        -- 将T恤衫的销售单价上浮1000日元
        UPDATE Product
           SET sale_price = sale_price + 1000
         WHERE product_name = 'T恤衫';
    ROLLBACK;
    事务处理何时开始

    实际上,几乎所有的数据库产品的事务都无需开始指令。这是因为大部分情况下,事务在数据库连接建立时就已经悄悄开始了,并不需要用户再明确发出开始指令。

    事务类别:

    • 每条 SQL语句就是一个事务(自动提交模式)
    • 直到用户执行COMMIT或者ROLLBACK为止算作一个事务

    默认使用自动提交模式的 DBMSSQL ServerPostgreSQLMySQL

ACID特性

DBMS 的事务都遵循四种特性,将这四种特性的首字母结合起来统称为 ACID 特性。这是所有 DBMS 都必须遵守的规则。

  • 原子性(Atomicity)

    原子性是指在事务结束时,其中所包含的更新处理要么全部执行,要么完全不执行,也就是要么占有一切要么一无所有。

  • 一致性(Consistency)

    一致性指的是事务中包含的处理要满足数据库提前设置的约束,如主键约束或者 NOT NULL 约束等。

  • 隔离性(Isolation)

    隔离性指的是保证不同事务之间互不干扰的特性。

  • 持久性(Durability)

    持久性也可以称为耐久性,指的是在事务(不论是提交还是回滚)结束后,DBMS 能够保证该时间点的数据状态会被保存的特性。即使由于系统故障导致数据丢失,数据库也一定能通过某种手段进行恢复。

复杂查询

视图

视图和表

Buy Me a Coffee ~~
hiifong 支付宝支付宝
hiifong 微信微信
0%