收集整理高頻實用的 MySQL SQL 技巧,涵蓋數字輔助表構建、不連續 ID 分組填充、生日/年齡計算、工作日統計、常用 DDL/DML 模板等,可直接複製適配業務。
CREATE TABLE `num` (`id` INT UNSIGNED NOT NULL PRIMARY KEY);
DELIMITER //
CREATE PROCEDURE pnum(cnt INT UNSIGNED)
BEGIN
DECLARE i INT UNSIGNED DEFAULT 1;
INSERT INTO num VALUES (1);
WHILE i * 2 < cnt DO
INSERT INTO num SELECT i + id FROM num;
SET i = i * 2;
END WHILE;
END//
DELIMITER ;
CALL pnum(1000000); -- 秒級生成 100 萬行
原理:倍增法,log2(N) 輪迴圈完成。
場景:表 a(id) 值為 1,2,3,100,101,110,111,需找出缺口並分組。
-- 1. 產生連續行號
SET @q = 0;
SELECT id, @q := @q + 1 AS rn FROM a;
-- 2. 差值分組:連續段的 id - rn 恆定
SET @a = 0;
SELECT MIN(id) AS start_id, MAX(id) AS end_id
FROM (
SELECT id, @a := @a + 1 AS rn, id - @a AS diff
FROM a
) t
GROUP BY diff;
-- 3. 找出缺口範圍
SELECT a + 1 AS gap_start,
(SELECT MIN(a) - 1 FROM pincer w WHERE w.a > q.a) AS gap_end
FROM pincer q
WHERE NOT EXISTS (SELECT 1 FROM pincer p WHERE p.a = q.a + 1)
AND a < (SELECT MAX(a) FROM pincer);
SELECT id, num, ranknum, diff
FROM (
SELECT id, num, ranknum, num - ranknum AS diff
FROM (
SELECT id, num,
IF(@id = id, @rn := @rn + 1, @rn := 1) AS ranknum,
@id := id
FROM tt, (SELECT @rn := 0, @id := NULL) v
) x
) y
GROUP BY id, diff
HAVING COUNT(*) >= 2;
SELECT name, birthday,
IF(cur > today, cur, nxt) AS next_birthday
FROM (
SELECT name, birthday, today,
DATE_ADD(cur, INTERVAL IF(DAY(birthday)=29 AND DAY(cur)=28, 1, 0) DAY) AS cur,
DATE_ADD(nxt, INTERVAL IF(DAY(birthday)=29 AND DAY(nxt)=28, 1, 0) DAY) AS nxt
FROM (
SELECT name, birthday, today,
DATE_ADD(birthday, INTERVAL diff YEAR) AS cur,
DATE_ADD(birthday, INTERVAL diff + 1 YEAR) AS nxt
FROM (
SELECT CONCAT(last_name, ' ', first_name) AS name,
birth_date AS birthday,
YEAR(NOW()) - YEAR(birth_date) AS diff,
NOW() AS today
FROM employees
) a
) b
) c;
SELECT FLOOR(DATEDIFF(CURDATE(), birth_date) / 365.25) AS age FROM employees;
SELECT
DATE_ADD('1900-01-01', INTERVAL FLOOR(DATEDIFF(dt, '1900-01-01')/7)*7 DAY) AS week_start,
DATE_ADD('1900-01-01', INTERVAL FLOOR(DATEDIFF(dt, '1900-01-01')/7)*7 + 6 DAY) AS week_end,
SUM(cost) AS weekly_cost
FROM sales
GROUP BY week_start, week_end;
CREATE PROCEDURE p_get_workdays(s DATETIME, e DATETIME)
BEGIN
SELECT FLOOR(days/7)*5 + days%7
- CASE WHEN 6 BETWEEN wd AND wd + days%7 - 1 THEN 1 ELSE 0 END
- CASE WHEN 7 BETWEEN wd AND wd + days%7 - 1 THEN 1 ELSE 0 END AS workdays
FROM (SELECT DATEDIFF(e, s) + 1 AS days, WEEKDAY(s) + 1 AS wd) t;
END;
呼叫:CALL p_get_workdays('2024-01-01', '2024-01-31');
| 類別 | 語句 |
|---|---|
| 建庫/刪庫 | CREATE DATABASE db_name; / DROP DATABASE db_name; |
| 建表 | CREATE TABLE t (c1 INT PRIMARY KEY, c2 VARCHAR(50) NOT NULL) ENGINE=InnoDB; |
| 複製表結構 | CREATE TABLE t_new LIKE t_old; |
| 複製表結構+資料 | CREATE TABLE t_new AS SELECT * FROM t_old; |
| 加欄位 | ALTER TABLE t ADD COLUMN c3 INT DEFAULT 0; |
| 加索引 | CREATE INDEX idx_c2 ON t(c2); |
| 建視圖 | CREATE VIEW v AS SELECT c1, c2 FROM t WHERE c2 > 100; |
| 增 | INSERT INTO t(c1,c2) VALUES (1,'a'),(2,'b'); |
| 刪 | DELETE FROM t WHERE c1 = 1; |
| 改 | UPDATE t SET c2='c' WHERE c1 = 2; |
| 查 | SELECT * FROM t WHERE c2 LIKE '%keyword%'; |
| 分頁 | SELECT * FROM t ORDER BY c1 LIMIT 10 OFFSET 20; |
| 聚合 | SELECT COUNT(*), SUM(c1), AVG(c1), MAX(c1), MIN(c1) FROM t; |
| 分組 | SELECT c2, COUNT(*) FROM t GROUP BY c2 HAVING COUNT(*) > 1; |
| 多表連接 | SELECT a.*, b.name FROM a JOIN b ON a.b_id = b.id; |
| 集合運算 | (SELECT * FROM t1) UNION (SELECT * FROM t2); / UNION ALL / INTERSECT / EXCEPT (MySQL 8.0+) |
| 外連接 | LEFT JOIN / RIGHT JOIN (MySQL 無 FULL JOIN,用 UNION 模擬) |
| 修改資料庫名 | RENAME DATABASE old_db TO new_db; (需 5.1.7+,或用 mysqldump 重導) |
-- 批量生成 TRUNCATE 語句
SELECT CONCAT('TRUNCATE TABLE ', TABLE_SCHEMA, '.', TABLE_NAME, ';')
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test_db' AND TABLE_TYPE = 'BASE TABLE';
-- 批量生成 ADD INDEX 語句
SELECT CONCAT('ALTER TABLE ', TABLE_NAME, ' ADD INDEX idx_', COLUMN_NAME, '(', COLUMN_NAME, ');')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'test_db' AND COLUMN_KEY = '' AND DATA_TYPE IN ('int','bigint','varchar');
GROUP BY 選擇非聚合欄位需設定 sql_mode=ONLY_FULL_GROUP_BY(MySQL 5.7+ 預設開啟)NOT IN 子查詢含 NULL 會導致空結果,改用 NOT EXISTS 或 LEFT JOIN ... IS NULLDELETE 分批提交,避免鎖升級與 Undo Log 膨脹COUNT(*) vs COUNT(col):前者含 NULL,後者不含WHERE varchar_col = 123 應寫 WHERE varchar_col = '123'