CREATE TABLE AS SELECT可一步建表并插入数据,但不复制主键、索引、外键、默认值等约束,仅部分数据库保留NOT NULL;MySQL 8.0+和PostgreSQL支持,SQL Server需用SELECT INTO替代。

用 CREATE TABLE AS SELECT 快速建表并填充数据
直接用 CREATE TABLE AS SELECT 是最简方式:它基于查询结果自动推导列名、类型和非空约束(部分数据库),一步完成建表和 INSERT。但要注意,主键、索引、默认值、外键等不会被复制,也不能指定新表的存储引擎或字符集(MySQL)或分布键(PostgreSQL 分布式扩展)。
常见错误是误以为新表会继承原表所有约束——实际只会保留 NOT NULL(PostgreSQL/Oracle 支持,MySQL 8.0+ 部分支持),其他一律丢失。
- PostgreSQL 示例:
CREATE TABLE orders_2024 AS SELECT * FROM orders WHERE created_at >= '2024-01-01'; - MySQL 示例(8.0+):
CREATE TABLE logs_backup AS SELECT * FROM logs WHERE level = 'ERROR'; - SQL Server 不支持该语法,需拆成
SELECT INTO(仅限新建临时表或数据库内无同名表时)
想保留主键/索引?必须分两步:CREATE + INSERT
当新表需要完整结构(比如带 PRIMARY KEY、UNIQUE、INDEX 或自增列),就不能依赖 AS SELECT。得先用 SHOW CREATE TABLE(MySQL)或 \d+(psql)导出原表 DDL,手动修改表名,再执行建表语句;之后再 INSERT INTO ... SELECT。
容易踩的坑是字段顺序或类型微调后,INSERT INTO t1 SELECT * FROM t2 会因列数不匹配或隐式转换失败——务必显式写出字段列表。
- 安全写法:
INSERT INTO new_orders (id, user_id, amount, created_at) SELECT id, user_id, amount, created_at FROM orders WHERE ...; - MySQL 中若目标表有
AUTO_INCREMENT列,且你想保留原值,需先SET sql_mode = ''(禁用严格模式)或在 INSERT 前SET @@auto_increment_offset = 1;(不推荐) - PostgreSQL 若目标表含
GENERATED ALWAYS AS IDENTITY,INSERT 时必须显式指定OVERRIDING SYSTEM VALUE才能写入自定义值
跨库/跨实例迁移时,别忽略字符集与排序规则
用 CREATE TABLE AS SELECT 在不同数据库实例间复制表,常出现中文乱码或比较异常——因为新表默认使用目标库的 default_character_set 和 collation,而非源表的设置。
例如 MySQL 中,源表是 utf8mb4_unicode_ci,目标库默认是 latin1_swedish_ci,即使数据能存进去,ORDER BY 或 = 比较也会出错。
- 建表时显式指定:
CREATE TABLE t2 (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - PostgreSQL 中注意
LC_COLLATE和LC_CTYPE是数据库级设定,无法单表覆盖,迁移前需确认目标库初始化参数一致 - SQL Server 的
COLLATION可在列级指定,但SELECT INTO不继承,必须在建表时补全
大数据量下 INSERT 很慢?试试批量控制与禁用日志
当源表超百万行,直接 INSERT INTO ... SELECT 可能锁表太久、事务日志暴涨,甚至 OOM。关键不是“怎么写 SQL”,而是“怎么降低开销”。
MySQL 中可临时关闭唯一检查和自动提交;PostgreSQL 可用 COPY 替代(更快且支持压缩);SQL Server 推荐用 INSERT ... SELECT TOP (N) 分批。
- MySQL 批量优化:
SET unique_checks=0; SET autocommit=0; INSERT ...; COMMIT; SET unique_checks=1; - PostgreSQL 更快方案:
COPY (SELECT * FROM orders WHERE ...) TO '/tmp/orders_2024.csv' WITH CSV;再COPY new_orders FROM ... - 避免在事务中塞进 100 万行 —— 即使没报错,回滚段也可能撑爆
最常被忽略的是权限问题:执行 CREATE TABLE AS SELECT 需要源表的 SELECT 权限,以及目标 schema 的 CREATE 权限;而分步操作中,第二步 INSERT 还额外需要目标表的 INSERT 权限。开发环境顺手跑通,上线就报 ERROR 1142 (42000): INSERT command denied 的情况太常见了。

















