加载中...

加载中...

学 SQL 最容易踩的坑,不是语法难,而是你照着 A 数据库的教程写,拿到 B 数据库上跑不通。SQL 有 ISO 标准,但现实中几乎没有哪个数据库完全照标准实现——它们各有方言。这篇教程以标准为主线,把方言差异逐条标出来。文中示例均在 SQLite 3.46.1PostgreSQL 16.3 实测通过。

SQL 方言示意

一、SQL 是什么:五类命令

SQL(Structured Query Language)是操作关系型数据库的语言。按用途分五类:

分类全称干什么代表语句
DDLData Definition Language定义结构CREATE / ALTER / DROP / TRUNCATE
DMLData Manipulation Language操作数据INSERT / UPDATE / DELETE
DQLData Query Language查询数据SELECT
DCLData Control Language权限控制GRANT / REVOKE
TCLTransaction Control Language事务控制BEGIN / COMMIT / ROLLBACK

多数教程只列前四类,把 TCL 漏了。事务是数据库区别于普通文件存储的核心能力,不该漏。

二、方言地图:先记住这几条

这一节是全文最值钱的部分。 下面这些差异,是你跨库写 SQL 时最先撞上的墙。

需求MySQLPostgreSQLSQLiteSQL 标准
自增主键AUTO_INCREMENTSERIALGENERATED ... AS IDENTITYINTEGER PRIMARY KEY(天然自增)GENERATED ALWAYS AS IDENTITY
标识符引号反引号 `双引号 "双引号 "双引号 "
字符串拼接CONCAT(a,b)a \|\| ba \|\| ba \|\| b
分页LIMIT n OFFSET m / LIMIT m,nLIMIT n OFFSET m / FETCH FIRST n ROWS ONLYLIMIT n OFFSET m / LIMIT m,nOFFSET m ROWS FETCH NEXT n ROWS ONLY
字符串聚合GROUP_CONCAT(x SEPARATOR ',')string_agg(x, ',')group_concat(x, ',')
空值替换IFNULL(a,b) / COALESCECOALESCEIFNULL(a,b) / COALESCECOALESCE
当前时间NOW() / CURDATE()now() / current_datedatetime('now') / date('now')CURRENT_TIMESTAMP / CURRENT_DATE
布尔类型TINYINT(1)BOOLEAN无(用 0/1BOOLEAN
TRUNCATE❌(用 DELETE FROM
UPSERTON DUPLICATE KEY UPDATEON CONFLICT ... DO UPDATEON CONFLICT ... DO UPDATEMERGE(较新)
类型转换CAST(x AS t)CAST(x AS t) / x::tCAST(x AS t)CAST(x AS t)

三条实用建议

  1. 写"通用 SQL"时不要用任何私有语法AUTO_INCREMENT、反引号、LIMIT m,n 全是方言,换个库就废。
  2. SQL 标准语法优先。比如分页用 OFFSET ... FETCH NEXT ... ROWS ONLY(PG、SQL Server 支持;MySQL 8 不支持,SQLite 不支持——所以实战里 LIMIT/OFFSET 反而更通用)。
  3. 不确定就查官方文档,别信二手教程。网上一大半教程把 MySQL 语法当"SQL 通用语法"讲。

三、数据库操作

3.1 创建

-- SQL 标准 / MySQL / PostgreSQL
CREATE DATABASE school_db;

-- 带存在性检查(MySQL 专有;PG 不支持 CREATE DATABASE IF NOT EXISTS)
CREATE DATABASE IF NOT EXISTS school_db;

SQLite 没有 CREATE DATABASE。它的"数据库"就是一个文件,sqlite3 school.db 打开即创建(详见 SQLite 篇)。

3.2 切换与查看

USE school_db;           -- MySQL;PG 用 \c school_db;SQLite 用 .open
SHOW DATABASES;          -- MySQL;PG 用 \l;SQLite 用 .databases

3.3 删除

DROP DATABASE school_db;          -- 不可恢复,慎用
DROP DATABASE IF EXISTS school_db;

四、表操作

4.1 建表与数据类型

同样的表,三种方言写法不同:

-- MySQL
CREATE TABLE students (
  id    INT PRIMARY KEY AUTO_INCREMENT,
  name  VARCHAR(50) NOT NULL,
  age   INT,
  email VARCHAR(100) UNIQUE
);

-- PostgreSQL
CREATE TABLE students (
  id    SERIAL PRIMARY KEY,
  name  VARCHAR(50) NOT NULL,
  age   INT,
  email VARCHAR(100) UNIQUE
);

-- SQLite(类型是"建议",VARCHAR(100) 的长度不生效)
CREATE TABLE students (
  id    INTEGER PRIMARY KEY AUTOINCREMENT,
  name  TEXT NOT NULL,
  age   INTEGER,
  email TEXT UNIQUE
);

常用类型对照:

用途MySQLPostgreSQLSQLite
整数INT / BIGINTINTEGER / BIGINTINTEGER
短文本VARCHAR(n)VARCHAR(n) / TEXTTEXT
长文本TEXTTEXTTEXT
小数DECIMAL(p,s)NUMERIC(p,s)REAL / NUMERIC
布尔TINYINT(1)BOOLEAN无(0/1
日期时间DATETIME / TIMESTAMPTIMESTAMP / TIMESTAMPTZTEXT / INTEGER
JSONJSONJSONBTEXT(+ json() 函数)
二进制BLOBBYTEABLOB

关于 DECIMALFLOAT:金额永远用 DECIMAL/NUMERIC,别用 FLOAT。浮点是二进制近似,0.1 + 0.2 != 0.3 在数据库里同样是灾难。

4.2 约束

CREATE TABLE students (
  id         INT PRIMARY KEY,
  name       VARCHAR(50) NOT NULL,
  age        INT CHECK (age >= 0),
  email      VARCHAR(100) UNIQUE,
  class_id   INT,
  FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE
);
约束作用
PRIMARY KEY主键,唯一且非空
NOT NULL非空
UNIQUE唯一
CHECK(表达式)自定义校验
DEFAULT默认值
FOREIGN KEY ... REFERENCES外键

SQLite 的外键默认不生效,需要 PRAGMA foreign_keys = ON。这是最容易造成静默数据损坏的坑,详见 SQLite 篇

4.3 修改表结构(方言差异最大)

-- 添加列(三方通用)
ALTER TABLE students ADD COLUMN major VARCHAR(50);

-- 修改列类型
ALTER TABLE students MODIFY COLUMN age SMALLINT;          -- MySQL
ALTER TABLE students ALTER COLUMN age TYPE SMALLINT;      -- PostgreSQL
-- SQLite:不支持直接改列类型,需"建新表→拷数据→换名"

-- 改列名
ALTER TABLE students CHANGE COLUMN major department VARCHAR(50);  -- MySQL
ALTER TABLE students RENAME COLUMN major TO department;           -- PostgreSQL / SQLite(3.25+)

-- 删除列
ALTER TABLE students DROP COLUMN email;                   -- 三方均可(SQLite 需 3.35+)

4.4 查看与删除

-- 查看表结构
DESCRIBE students;                  -- MySQL
\d students                         -- PostgreSQL(psql 元命令)
PRAGMA table_info(students);        -- SQLite

-- 删除
DROP TABLE students;                -- 删表(含结构)
DROP TABLE IF EXISTS students;      -- 存在才删,建议加
TRUNCATE TABLE students;            -- 清空数据保留结构(MySQL/PG)
DELETE FROM students;               -- SQLite 用这个替代 TRUNCATE

五、CRUD

5.1 插入

INSERT INTO students (name, age, email) VALUES
  ('张三', 18, 'zhangsan@example.com'),
  ('李四', 19, 'lisi@example.com');

一次插多行比循环单行插入快得多,这是所有数据库的共识。

5.2 查询

SELECT * FROM students;                              -- 全部列
SELECT name, age FROM students;                      -- 指定列
SELECT DISTINCT major FROM students;                 -- 去重
SELECT * FROM students WHERE age > 18 AND major = 'CS';
SELECT * FROM students ORDER BY age DESC, name ASC;  -- 多列排序
SELECT * FROM students ORDER BY age DESC LIMIT 5;                       -- 前 5
SELECT * FROM students ORDER BY age DESC LIMIT 5 OFFSET 10;             -- 第 11-15 行

别在生产代码里写 SELECT *。表的列会变,依赖 * 的代码会在加列后悄悄出错,而且会拖慢查询(多读不需要的列)。写死列名。

5.3 更新与删除

UPDATE students SET age = 20 WHERE name = '张三';
DELETE FROM students WHERE name = '王五';

UPDATE/DELETE 不写 WHERE 会作用于全表,且没有确认提示。 实操建议:先用同样 WHERE 跑一遍 SELECT,确认影响范围,再改 UPDATE/DELETE

5.4 UPSERT(方言写法不同)

-- PostgreSQL / SQLite
INSERT INTO students (id, name) VALUES (1, '张三')
ON CONFLICT (id) DO UPDATE SET name = excluded.name;

-- MySQL
INSERT INTO students (id, name) VALUES (1, '张三')
ON DUPLICATE KEY UPDATE name = VALUES(name);

六、NULL:最容易出错的地方

NULL 不是"0",也不是空字符串,它是"未知"。这导致三条反直觉的规则。

6.1 比较要用 IS NULL,不能用 = NULL

SELECT * FROM students WHERE age = NULL;    -- ❌ 永远返回空
SELECT * FROM students WHERE age IS NULL;   -- ✅ 正确
SELECT * FROM students WHERE age IS NOT NULL;

任何值和 NULL 比较,结果都是"未知",WHERE 会把它当假丢掉。

6.2 聚合函数会跳过 NULL

这是 COUNT(*)COUNT(列) 的区别:

SELECT COUNT(*) AS cnt_all, COUNT(age) AS cnt_age FROM students;
3|2

表里 3 行,但 age 有一行是 NULL,所以 COUNT(age) = 2。SUM/AVG/MAX/MIN 同理都会跳过 NULL。AVG 的分母是"非 NULL 的行数",不是总行数——算平均分时极易出错。

6.3 空值替换

SELECT name, COALESCE(age, 0) AS safe_age FROM students;   -- 三方通用,推荐
SELECT name, IFNULL(age, 0) FROM students;                 -- MySQL / SQLite

COALESCE 接受多个参数,返回第一个非 NULL 值,是标准写法,优先用它。

6.4 GROUP BY 里的 NULL

GROUP BY 会把所有 NULL 归成一组。排序时 NULL 的位置各家默认不同(MySQL 升序把 NULL 排最前,PG 排最后),要固定顺序就显式写:

ORDER BY age ASC NULLS LAST      -- PostgreSQL / SQLite(3.30+)
ORDER BY age IS NULL, age        -- MySQL 的变通写法

七、聚合与分组

SELECT
    COUNT(*)          AS total,
    AVG(age)          AS avg_age,
    MIN(age)          AS min_age,
    MAX(age)          AS max_age
FROM students;
SELECT major, COUNT(*) AS cnt, AVG(age) AS avg_age
FROM students
GROUP BY major
HAVING COUNT(*) > 5
ORDER BY cnt DESC;

WHEREHAVING 的区别WHERE 在分组过滤行,HAVING 在分组过滤组。WHERE 里不能引用聚合函数,HAVING 可以。

-- 错的:WHERE 用聚合
SELECT major FROM students WHERE COUNT(*) > 5 GROUP BY major;
-- 对的:
SELECT major FROM students GROUP BY major HAVING COUNT(*) > 5;

八、连接(JOIN)

先建两张表:

CREATE TABLE classes (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE students (id INT PRIMARY KEY, name VARCHAR(50), class_id INT);
连接类型返回什么备注
INNER JOIN两张表都匹配上的行最常用,JOIN 默认即此
LEFT JOIN左表全部 + 右表匹配的,右表无匹配补 NULL极常用
RIGHT JOIN右表全部 + 左表匹配的可用 LEFT JOIN 调换顺序替代
FULL OUTER JOIN两边所有行,各自无匹配处补 NULLMySQL 不支持(用 LEFT UNION RIGHT 模拟)
CROSS JOIN笛卡尔积(m×n 行)慎用,容易炸
自连接表和自己 JOIN处理层级结构(如员工-经理)
-- 内连接:只列出有班级的学生
SELECT s.name, c.name AS class_name
FROM students s
INNER JOIN classes c ON s.class_id = c.id;

-- 左连接:所有学生,没班级的也列出(班级名显示 NULL)
SELECT s.name, c.name AS class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.id;

-- 左连接 + 过滤出"没班级的学生"(反连接模式)
SELECT s.name
FROM students s
LEFT JOIN classes c ON s.class_id = c.id
WHERE c.id IS NULL;

最后那个"反连接"写法很实用——找出"订单表里有、客户表里没有"的脏数据就是靠它。

九、子查询

-- 标量子查询:返回单个值
SELECT * FROM students WHERE age > (SELECT AVG(age) FROM students);

-- IN 子查询:返回一列
SELECT * FROM students
WHERE class_id IN (SELECT id FROM classes WHERE name LIKE 'CS%');

-- EXISTS:判断存在性(通常比 IN 快,且对 NULL 更安全)
SELECT * FROM students s
WHERE EXISTS (SELECT 1 FROM classes c WHERE c.id = s.class_id);

-- 相关子查询:子查询引用外层列
SELECT name,
       (SELECT COUNT(*) FROM students s2 WHERE s2.class_id = s1.class_id) AS classmates
FROM students s1;

IN vs EXISTS:当子查询结果可能含 NULL 时,NOT IN 会返回空集(因为 x != NULL 是未知)——这是经典陷阱。NOT EXISTS 代替 NOT IN 更安全。

十、集合运算

SELECT name FROM students_a
UNION            -- 并集,去重
SELECT name FROM students_b;

SELECT name FROM students_a
UNION ALL        -- 并集,不去重(更快,能保留重复)
SELECT name FROM students_b;

SELECT name FROM students_a
INTERSECT        -- 交集
SELECT name FROM students_b;

SELECT name FROM students_a
EXCEPT           -- 差集(A 有 B 没有)
SELECT name FROM students_b;

方言UNION/UNION ALL 三方通用。INTERSECT/EXCEPTMySQL 需 8.0.31+;老版本 MySQL 用 INNER JOIN / NOT IN 模拟。

十一、CASE 表达式

SQL 里的 if-else,用在 SELECTWHEREORDER BY 里都行:

SELECT name,
       CASE
           WHEN age IS NULL THEN '未知'
           WHEN age >= 30   THEN '资深'
           ELSE '新人'
       END AS tag
FROM students;
-- 按自定义顺序排序(比如状态:待处理 → 处理中 → 已完成)
SELECT * FROM tasks
ORDER BY CASE status
             WHEN 'pending'   THEN 1
             WHEN 'running'   THEN 2
             WHEN 'done'      THEN 3
             ELSE 4
         END;

十二、窗口函数

窗口函数能在不合并行的前提下做聚合/排名,是 2010 年之后 SQL 最重要的增强。MySQL 8.0+、PostgreSQL、SQLite 3.25+ 都支持。

SELECT
    name,
    age,
    ROW_NUMBER() OVER (ORDER BY age)      AS 行号,
    RANK()       OVER (ORDER BY age)      AS 排名_跳号,
    DENSE_RANK() OVER (ORDER BY age)      AS 排名_不跳号
FROM students;
-- 分组内排名:每个班级按年龄排名
SELECT name, class_id, age,
       ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY age DESC) AS rn
FROM students;
-- 移动平均:最近 3 行的平均(时间序列分析常用)
SELECT day, amount,
       AVG(amount) OVER (ORDER BY day ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS ma3
FROM sales;
函数作用
ROW_NUMBER()连续行号,无并列
RANK()并列跳号(1,1,3)
DENSE_RANK()并列不跳号(1,1,2)
LAG(col, n) / LEAD(col, n)取前 n 行 / 后 n 行的值
SUM()/AVG()/COUNT() OVER(...)窗口内聚合
NTILE(n)分成 n 桶

十三、CTE(公共表表达式)

WITH 让复杂查询可读,替代嵌套子查询:

WITH class_stats AS (
    SELECT class_id, COUNT(*) AS cnt, AVG(age) AS avg_age
    FROM students
    GROUP BY class_id
)
SELECT c.name, cs.cnt, cs.avg_age
FROM class_stats cs
JOIN classes c ON c.id = cs.class_id
WHERE cs.cnt > 5;

递归 CTE 能遍历树形结构(组织架构、分类树、BOM):

WITH RECURSIVE org 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, o.level + 1
    FROM employees e
    JOIN org o ON e.manager_id = o.id
)
SELECT * FROM org ORDER BY level;

十四、常用函数速查

用途MySQLPostgreSQLSQLite
转大写UPPER(x)UPPER(x)UPPER(x)
长度LENGTH(x) / CHAR_LENGTH(x)LENGTH(x)length(x)
截取SUBSTRING(x,2,3)SUBSTRING(x FROM 2 FOR 3) / LEFT(x,3)substr(x,2,3)
拼接CONCAT(a,b)a \|\| ba \|\| b
当前时间NOW()now()datetime('now')
格式化日期DATE_FORMAT(d,'%Y-%m')to_char(d,'YYYY-MM')strftime('%Y-%m',d)
日期截断DATE(d)date_trunc('month',d)date(d,'start of month')
空值替换IFNULL(a,b) / COALESCECOALESCE(a,b)IFNULL(a,b) / COALESCE
数字取整ROUND(x,2)ROUND(x,2)round(x,2)
类型转换CAST(x AS CHAR)x::text / CAST(x AS text)CAST(x AS TEXT)
模糊匹配LIKE(默认忽略大小写)LIKE(区分)/ ILIKE(忽略)LIKE(默认忽略 ASCII 大小写)

LIKE 的大小写行为是最容易跨库翻车的地方之一:MySQL 默认不区分,PostgreSQL 默认区分。要么统一用 UPPER(x) LIKE UPPER(y),要么明确用各家的大小写不敏感写法。

十五、索引与查询计划

-- 创建
CREATE INDEX idx_students_name ON students(name);                  -- 单列
CREATE INDEX idx_students_age_name ON students(age, name);         -- 复合(顺序重要)
CREATE UNIQUE INDEX idx_students_email ON students(email);         -- 唯一索引

-- 删除(注意方言)
DROP INDEX idx_students_name ON students;      -- MySQL(要带 ON 表名)
DROP INDEX idx_students_name;                  -- PostgreSQL / SQLite

复合索引的顺序有讲究(age, name) 能加速 WHERE age=?WHERE age=? AND name=?,但不能加速只有 WHERE name=? 的查询(最左前缀原则)。

看执行计划

EXPLAIN SELECT * FROM students WHERE name = '张三';                     -- MySQL / PG
EXPLAIN QUERY PLAN SELECT * FROM students WHERE name = '张三';          -- SQLite
EXPLAIN ANALYZE SELECT * FROM students WHERE name = '张三';             -- PG / MySQL 8(实际执行并计时)

看到 SCAN(SQLite)/ Seq Scan(PG)/ ALL(MySQL 的 type 列)就说明在全表扫描,该考虑加索引了。

十六、视图

CREATE VIEW student_summary AS
SELECT s.id, s.name, COUNT(e.course_id) AS courses
FROM students s
LEFT JOIN enrollments e ON s.id = e.student_id
GROUP BY s.id, s.name;

SELECT * FROM student_summary WHERE courses > 3;

CREATE OR REPLACE VIEW student_summary AS ...;   -- 更新视图定义
DROP VIEW student_summary;

视图是保存下来的查询,不是数据副本——每次查视图都会重新执行底层查询。它可以简化复杂查询、限制列级访问权限(只暴露部分列给某类用户)。

十七、事务

BEGIN;   -- 或 START TRANSACTION
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- 成功提交;失败用 ROLLBACK 撤销

事务的原子性保证了转账这类操作要么全成、要么全不成,不会出现"钱扣了没到账"的中间态。

隔离级别

级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED可能可能
REPEATABLE READ可能
SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;   -- 标准写法
-- 查看/设置(会话级)
SELECT @@transaction_isolation;                   -- MySQL
SHOW transaction_isolation;                       -- PostgreSQL

默认值差异:MySQL InnoDB 默认 REPEATABLE READ,PostgreSQL 默认 READ COMMITTED。这个差异会导致同样的代码在两个库上表现不同,做并发相关的逻辑时务必确认。

十八、权限(DCL)

-- MySQL:用户和主机绑定
GRANT SELECT, INSERT ON school_db.* TO 'app'@'localhost';
REVOKE DELETE ON school_db.* FROM 'app'@'localhost';

-- PostgreSQL:用户是角色,不绑主机
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA public TO app;
REVOKE DELETE ON students FROM app;

MySQL 的 'user'@'host' 是它的特色(同一用户名从不同主机连可以是不同的账号)。SQLite 没有用户概念,文件权限就是它的全部安全模型。

十九、常见错误与最佳实践

19.1 三个高频错误

① 保留字当表名/列名

-- MySQL:反引号
CREATE TABLE `user` (`id` INT, `order` INT);

-- PostgreSQL / SQLite:双引号
CREATE TABLE "user" (id INT, "order" INT);

userordergroupselect 都是保留字。最好的做法是加前缀app_usershop_order),从根上绕开。

② 字符串拼接导致 SQL 注入

-- ❌ 危险:把用户输入直接拼进 SQL
query = "SELECT * FROM users WHERE name = '" + input + "'";

永远用参数化查询(?$1 占位)。这是安全底线,不是风格问题。

③ 在 WHERE 里对索引列做运算

-- ❌ 索引失效:对列做了函数运算
SELECT * FROM orders WHERE YEAR(created_at) = 2026;

-- ✅ 改成范围比较,走索引
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';

19.2 最佳实践清单

  1. 命名有意义student_id 而不是 sid
  2. 不用 SELECT *:写死需要的列
  3. 参数化查询:防注入
  4. 索引按需建:查询频繁的列建,写入频繁的表别乱建(索引会拖慢写入)
  5. 金额用 DECIMAL,不用 FLOAT
  6. UPDATE/DELETE 前先 SELECT 验证 WHERE
  7. 大事务拆小:长事务会长期持锁
  8. 定期备份mysqldump / pg_dump / sqlite3 .backup

二十、速查表

-- —— DDL ——
CREATE DATABASE db;                    -- 非 SQLite
DROP DATABASE IF EXISTS db;
CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(50));
ALTER TABLE t ADD COLUMN x INT;
DROP TABLE IF EXISTS t;
TRUNCATE TABLE t;                      -- 非 SQLite(用 DELETE FROM)

-- —— DML ——
INSERT INTO t (a,b) VALUES (1,2);
UPDATE t SET b=3 WHERE a=1;
DELETE FROM t WHERE a=1;

-- —— DQL ——
SELECT a, b FROM t WHERE a>1 ORDER BY b DESC LIMIT 10 OFFSET 20;
SELECT k, COUNT(*) FROM t GROUP BY k HAVING COUNT(*)>1;
SELECT a.*, b.* FROM a INNER JOIN b ON a.id=b.a_id;
SELECT a FROM t1 UNION ALL SELECT a FROM t2;
SELECT x, ROW_NUMBER() OVER (PARTITION BY k ORDER BY x) FROM t;
WITH cte AS (SELECT * FROM t) SELECT * FROM cte;

-- —— TCL ——
BEGIN; ... COMMIT;   -- 或 ROLLBACK;

-- —— 元信息 ——
DESCRIBE t;            -- MySQL
\d t                   -- PostgreSQL
PRAGMA table_info(t);  -- SQLite
EXPLAIN QUERY PLAN SELECT ...;   -- SQLite

学习资源

  1. 现代 SQL 教程 —— 讲标准 SQL 与各家方言差异,质量很高
  2. SQLZoo —— 交互式练习
  3. Use The Index, Luke —— 索引与查询优化的权威读物(免费)
  4. LeetCode 数据库题库 —— 实战练习
  5. 各数据库官方文档:MySQL / PostgreSQL / SQLite

学 SQL 的正确姿势是:先掌握标准,再记住方言。标准部分(SELECT/JOIN/GROUP BY/事务)在所有数据库通用,值得花时间打牢;方言部分用到再查,本文的方言对照表可以当索引使。

参考&致谢

系列教程

全部文章RSS订阅

数据库系列


SQL命令使用教程:从入门到精通
发布于
2025年11月22日
许可协议。转载请注明来源
评论
数据加载中 ...
 上一篇

阅读全文

PostgreSQL命令行使用教程:掌握 psql 工具
PostgreSQL命令行使用教程:掌握 psql 工具 PostgreSQL命令行使用教程:掌握 psql 工具
psql 是 PostgreSQL 官方命令行客户端,也是最能体现 PG 风格的入口。它的元命令系统(\\d 家族、\\x、\\gset、\\copy)比多数数据库的命令行工具都好用,值得花时间熟悉。本文从连接讲起,覆盖元命令、脚本化、导入
2025-11-22
下一篇 

阅读全文

Zlibrary 图书资源下载与 Calibre 图书管理打造结构化电子图书馆高阶教程
Zlibrary 图书资源下载与 Calibre 图书管理打造结构化电子图书馆高阶教程 Zlibrary 图书资源下载与 Calibre 图书管理打造结构化电子图书馆高阶教程
电子书不仅要下载可看,还要有书签,封面,简洁,作者,出版社等元数据帮助记忆和管理,否则,下载了一堆没法查找和没下载差不多!再加上世界上最大的电子书图书馆 Zlibrary ,这里几乎能找到所有的电子化图书!Calibre 和 Zlibrar
2025-09-05