SQL者,结构化查询语言也。此乃吾辈与关系型数据库如MySQL、PostgreSQL、SQLite等交谈之语也。
服务器为何物?数据库安在何处?
初学者多以为数据库悬于“云”中。非也。
数据库者,服务器上运行之软件也。服务器,不过为运行于数据中心、昼夜不息之强计算机耳——如亚马逊AWS、谷歌云、DigitalOcean等。当是软件存汝之数据时,则书之于服务器之SSD或硬盘中之实文件也。
MySQL与PostgreSQL存数据为.ibd文件或WAL(预写日志)文件。MongoDB则存为.bson之檔。此檔置於硬盤,猶君之筆記型電腦上檔案無異。
當君於後端程式寫此:
const pool = new Pool({ host: 'localhost', port: 5432 })
君告後端,當開一連結至資料庫聆聽之端口。此即之。此乃一TCP連結——猶君之應用程式與資料庫間之電話。
何故择Docker以载PostgreSQL乎?
非直于吾之系统安装PostgreSQL,吾乃用Docker。Docker创一隔离之境(名曰容器),使吾不乱吾笔记本电脑之设。
此乃吾用以创持久数据之PostgreSQL容器之命也:
Mac / Linux / WSL:
docker run -d --name sql_class \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=sql_class_01_db \
-p 5433:5432 \
-v "$(pwd)/pgdata:/var/lib/postgresql/data" \
postgres
Windows (PowerShell):
docker run -d --name sql_class -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=sql_class_01_db -p 5433:5432 -v "${PWD}/pgdata:/var/lib/postgresql/data" postgres
是故-v之标识,将尔当前目录中pgdata之文件夹,映射于容器之存储。故纵容器被毁,尔之数据犹存于尔之笔记本电脑。
欲验容器是否运行:
docker ps
欲去此容器:
docker rm -f sql_class
为何习原SQL?吾有Prisma与Drizzle,何须之?
此乃吾初问。习原SQL,Prisma可代吾成之,何故习之?
吾所察得者如是:
如Prisma、Drizzle之ORM,能将尔之JavaScript/TypeScript代码化而为SQL之字符串,而送诸数据库。尔若不知SQL,则不知Prisma实为之何也。
N+1之问题,确为实有。ORM时或生百问,而非一优化之问。尔惟通SQL,乃能察此。
夫繁难之询,合六表、制财报,则ORM之文法难继。众工复归乎素SQL。
然SQL五十年未易。Sequelize兴衰,Prisma出,Drizzle现,SQL犹故。
DDL——制表之道
DDL者,数据定义之语也。此乃定数据库之形制,非其数据,惟其形制耳。
四主DDL之令,曰CREATE,曰ALTER,曰DROP,曰TRUNCATE。
今示一表,乃吾习作所创。
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50),
email VARCHAR(100) UNIQUE NOT NULL,
phone_number CHAR(10) UNIQUE,
age INT CHECK (age > 12),
current_status VARCHAR(20) DEFAULT 'active'
CHECK (current_status IN ('active', 'graduated', 'dropped_out', 'on_leave')),
has_joined_masterji BOOLEAN DEFAULT FALSE,
current_score NUMERIC(5, 2) CHECK (current_score >= 0 AND current_score <= 100),
enrollment_date DATE DEFAULT CURRENT_DATE
);
SERIAL者,自增之整数也——PostgreSQL自为之。NOT NULL者,此列不可空也。UNIQUE者,两行不得同值也。CHECK者,特制之条件也——此中年龄须逾十二。DEFAULT者,若无所供,则自定其值也。
者,欲于既成之表增新列也。
ALTER TABLE students ADD COLUMN batch_name VARCHAR(50) DEFAULT 'Web Dev 2026';
查询之选——览尔之数据
SELECT乃取诸表之数据者也.
-- Get everything
SELECT * FROM ipl_players;
-- Get specific columns
SELECT name, team, auction_price_crores FROM ipl_players;
以WHERE为滤:
-- Basic condition
SELECT * FROM ipl_players WHERE team = 'Mumbai Indians';
-- Multiple conditions
SELECT * FROM ipl_players WHERE role = 'All-Rounder' AND wickets_taken > 10;
-- Either condition
SELECT * FROM ipl_players WHERE team = 'CSK' OR team = 'RCB';
-- Range
SELECT * FROM ipl_players WHERE auction_price_crores BETWEEN 5 AND 12;
-- From a list
SELECT * FROM ipl_players WHERE team NOT IN ('Mumbai Indians', 'CSK', 'RCB');
-- Pattern matching — names starting with R
SELECT * FROM ipl_players WHERE name LIKE 'R%';
-- Check for empty values
SELECT * FROM ipl_players WHERE team IS NULL;
% 谓之零或更多字符。_ 谓之恰一字符。LIKE 乃区分大小写。ILIKE 乃不区分大小写(唯 PostgreSQL 可用)。
排序与限制:
-- Sort by price, highest first
SELECT name, auction_price_crores FROM ipl_players ORDER BY auction_price_crores DESC;
-- Top 3 results
SELECT name, auction_price_crores FROM ipl_players ORDER BY auction_price_crores DESC LIMIT 3;
-- Skip first 3, get next 3 (pagination)
SELECT name, auction_price_crores FROM ipl_players ORDER BY auction_price_crores DESC LIMIT 3 OFFSET 3;
DML — 插入、更新、删除
DML,乃数据操作之语也。此乃汝与表内之数据实际相交互之法也。
INSERT:
-- Single row
INSERT INTO canteen_menu (item_name, category, price)
VALUES ('Vada Pav', 'Snacks', 15);
-- Multiple rows at once
INSERT INTO canteen_menu (item_name, category, price) VALUES
('Masala Chai', 'Beverages', 10),
('Samosa', 'Snacks', 12),
('Maggi', 'Snacks', 25);
UPDATE:
UPDATE canteen_menu
SET price = 20
WHERE item_name = 'Vada Pav';
DELETE:
DELETE FROM canteen_menu WHERE item_name = 'Cold Coffee';
恒用WHERE配UPDATE及DELETE。若忘WHERE之句,SQL将损益表中之每一行。吾于实践中学此,艰而得之.
聚合函数—计、总、均、极、极
聚合函数取全列,返一数.
-- How many rows?
SELECT COUNT(*) FROM smart_watch_sales;
-- Total revenue
SELECT SUM(units_sold * price_per_unit) AS total_revenue FROM smart_watch_sales;
-- Average price
SELECT AVG(price_per_unit) AS avg_price FROM smart_watch_sales;
-- Cheapest and most expensive
SELECT MIN(price_per_unit) AS cheapest, MAX(price_per_unit) AS costliest FROM smart_watch_sales;
GROUP BY 可使汝分此计算于类:
-- Total units sold per brand
SELECT brand, SUM(units_sold) AS total_units_sold
FROM smart_watch_sales
GROUP BY brand
ORDER BY total_units_sold DESC;
HAVING 于组立之后滤之。WHERE 不可为之,盖 WHERE 先于分组而行也:
-- Only show brands that sold more than 20 units total
SELECT brand, SUM(units_sold) AS total_units
FROM smart_watch_sales
GROUP BY brand
HAVING SUM(units_sold) > 20;
並合 — 联多表也
此乃关系之库显其能处也
吾等分置数据于异表,以避重。学子一表,实习一表。并合使汝于一问中合之。
外键者,联表之由也。实习之表有student_id之列,引据学子之表student_id也:
CREATE TABLE internships (
internship_id SERIAL PRIMARY KEY,
student_id INT REFERENCES students(student_id) ON DELETE SET NULL,
company_name VARCHAR(100),
role VARCHAR(50),
stipend INT,
status VARCHAR(20)
);
ON DELETE SET NULL者,若生徒被删,则实习之记存,然student_id为空。ON DELETE CASCADE则亦删实习之记。
内联——惟相合之行:
SELECT s.name, s.branch, i.company_name, i.role
FROM students s
INNER JOIN internships i ON s.student_id = i.student_id;
无实习者,不至于此。
左联 — 所有学生,无论是否匹配者:
SELECT
s.name,
s.branch,
COALESCE(i.company_name, 'No Internship') AS company_name,
COALESCE(i.stipend, 0) AS stipend
FROM students s
LEFT JOIN internships i ON s.student_id = i.student_id;
COALESCE以默认值易空。无实习者,将显"无实习"代空。
欲寻未往任何处申请者
SELECT s.name, s.email
FROM students s
LEFT JOIN internships i ON s.student_id = i.student_id
WHERE i.internship_id IS NULL;
RIGHT JOIN则返回右表所有行。实践中,开发者鲜少使用RIGHT JOIN,反其道而行之,改用LEFT JOIN,读来更顺。
FULL OUTER JOIN — 两表之全:
SELECT s.name AS student_name, i.company_name
FROM students s
FULL OUTER JOIN internships i ON s.student_id = i.student_id;
此显诸生与诸实习。若两方无匹,则得虚值.
索引——询之速也
吾于表入百万人,以试此法.
无索引者,按名索之,PostgreSQL自首至尾遍察每一行。此谓次第之索.
EXPLAIN ANALYZE SELECT marks FROM marks WHERE name = '809E15792322';
EXPLAIN ANALYZE示汝查询之策与执行之时。未加索引时,吾之查询于百万行中需时约四十至五十毫秒。
索引既立:
CREATE INDEX idx_name ON marks (name);
同查询已降至毫秒之下。PostgreSQL今以B-树之结构,直寻行,不复遍扫诸物。
一索引之覆,更进一筹。若汝之询求marks与之偕name,可于索引中并储之。
CREATE INDEX idx_name ON marks (name) INCLUDE (marks);
今PostgreSQL无需遍历主表,直取索引便得。此谓索引独扫。
交易与ACID
交易者,合诸SQL操作为一工之单元也。或尽成,或尽败。
典例若银行转账:
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE owner = 'Shubham';
UPDATE accounts SET balance = balance + 500 WHERE owner = 'Hitesh';
COMMIT;
若服务器于两次UPDATE语句之间崩溃,则数据库将回滚所有变更。Shubham之账户非空,Hitesh亦能得款。
倘有变故,可手动撤销诸事:
BEGIN;
UPDATE accounts SET balance = balance - 5000 WHERE owner = 'Shubham';
ROLLBACK;
ACID者,成此功之要也:
原子性——全或无。若第二步败,第一步亦复。吾以SELECT 1/0(除零之误)置交易之中,其事尽废。
一致性——数据库惟自一有效态迁至另一有效态。吾设CHECK (balance >= 0) 载于账簿之表。吾尝欲自一千之户扣五千,PostgreSQL乃掷误,拒此行焉。
隔离 — 未决之交易,他连不可见。吾启二终端以试之。终端一始交易,更余额而未COMMIT。终端二犹见旧额。直至终端一运行COMMIT 终端二见新数乎.
耐久性 — 既立,数据永存。PostgreSQL先于返回成功应答,将立交事务写入磁盘之先见日志(WAL)。纵使服务器立交后一息崩溃,重启时数据犹在.
此博客所有代码,皆存于吾GitHub之库:github.com/abdulrdeveloper/Learn-SQL
吾之更多著述,可于abdulrdeveloper.me
观之blog.abdulrdeveloper.me












