SQL插入数据三种方式详解:INSERT INTO VALUES、SELECT INTO与INSERT INTO SELECT

发布时间:2026/10/11 19:34:23
SQL插入数据三种方式详解:INSERT INTO VALUES、SELECT INTO与INSERT INTO SELECT 简介这份PDF资料面向SQL初学者与需要巩固数据库基础的开发者系统梳理了插入数据的三种常用写法基础INSERT INTO VALUES、INSERT INTO SELECT以及省略目标列的简写形式并针对每种方法给出语法示例与适用场景。资料还整理了检查列约束、批量插入、事务处理、错误处理与性能优化等实用小贴士帮助读者在实际操作中避开非空列遗漏、列顺序不一致等常见陷阱。资源包共1个PDF文件大小约34KB内容紧凑适合快速查阅与复习。目前已有664人学习下载可作为日常数据库开发与数据迁移场景的参考手册帮助读者根据具体需求选择更合适的插入方式提升数据管理效率。1. 三种插入方式选错了就是给自己埋雷很多人写INSERT写了几年其实只写过一种。日常业务里往表里塞一条记录INSERT INTO ... VALUES确实够用可一旦碰上数据迁移、表结构复制、跨库同步还硬用单条 VALUES 去怼那就是拿业务时间换自己的加班时长。SQL 插入数据这件事表面看就三条路INSERT INTO ... VALUES、SELECT ... INTO、INSERT INTO ... SELECT。但真正落到生产环境选哪条、列顺序怎么对、非空列漏没漏、事务包没包每一步都能让插入翻车。这篇笔记拆的就是这三种常用方法把语法、参数、适用边界和几个血泪坑一次讲清适合刚接触 SQL 的开发和需要做数据搬运的运维同学照着复现。2. INSERT INTO VALUES单条插入的地基也是批量插入的起点2.1 语法结构与参数含义最基础的插入语句长这样INSERT INTO table1 (id, name, address) VALUES (1, ygl, beijing);拆开看INSERT INTO是固定动作table1是目标表括号里的id, name, address是目标列清单VALUES后面括号里是对应列的值。列清单和值清单必须一一对应数量对不上直接报错。字符串值要用单引号包住数字不用。这条语句在 T-SQL 和 PL/SQL 里都通用是插入数据最没有歧义的一种写法。我一般会强制自己写全列名哪怕表里只有三个列。原因很简单表结构是会变的今天你省略列名靠顺序蒙对明天别人加了一列你的插入语句就可能把值塞进错误的列而且不报错数据静默错位这种问题排查起来非常折磨。2.2 批量插入的写法与性能边界单条 VALUES 一次只插一行如果循环一千次去插一千行网络往返和事务开销会把性能拖垮。常见做法是把多行合并到一条语句里INSERT INTO table1 (id, name, address) VALUES (1, ygl, beijing), (2, lisi, shanghai), (3, wangwu, guangzhou);这种写法在 MySQL、PostgreSQL、SQL Server 2012 及以上都支持。它的优势是减少语句解析次数和网络交互几百到几千行的批量插入用这种方式很划算。但要注意单条语句的值列表不能无限长MySQL 受max_allowed_packet限制SQL Server 对单条语句也有批处理大小约束。超过几千行建议分批提交每批 500 到 1000 行配合事务控制。参数上还要留意自增列。如果id是自增主键插入时不要把id写进列清单让数据库自己分配INSERT INTO table1 (name, address) VALUES (ygl, beijing);强行给自增列指定值在 SQL Server 里需要SET IDENTITY_INSERT table1 ON在 MySQL 里虽然允许但容易造成自增计数器混乱。除非做数据迁移要保留原 ID否则不要碰自增列。2.3 事务包裹与错误处理批量插入最怕插到一半失败前面进去的数据成了脏数据。标准做法是用事务把整批操作包起来BEGIN TRANSACTION; INSERT INTO table1 (name, address) VALUES (ygl, beijing); INSERT INTO table1 (name, address) VALUES (lisi, shanghai); COMMIT;如果中间任何一条失败执行ROLLBACK回滚整批。SQL Server 里还可以配合TRY...CATCHBEGIN TRY BEGIN TRANSACTION; INSERT INTO table1 (name, address) VALUES (ygl, beijing); COMMIT; END TRY BEGIN CATCH ROLLBACK; THROW; END CATCH;这里的关键参数是事务隔离级别和锁范围。大批量插入时如果隔离级别是SERIALIZABLE锁会升级阻塞其他会话。一般业务插入用默认的READ COMMITTED就够。另外插入前检查目标表的约束——主键冲突、唯一索引冲突、非空约束——这些是插入失败的高频原因提前用SELECT验证一遍比事后回滚省事得多。3. SELECT INTO 与 INSERT INTO SELECT数据搬运的两把刀3.1 SELECT INTO 自动建表方便但有前提T-SQL 里有一条很顺手的语句SELECT id, name, address INTO table2 FROM table1;它的行为是如果table2不存在就按照SELECT出来的列结构和数据类型自动创建然后把数据插进去。注意是“如果不存在”。如果table2已经存在这条语句直接报错不会追加数据。这一点和INSERT INTO SELECT有本质区别。自动建表时源表的约束、索引、默认值不会带过来。主键、外键、唯一索引统统丢失只有列名和数据类型被复制。所以SELECT INTO适合做临时表、中间表、数据快照不适合直接当正式表的复制方案。我见过有人用SELECT INTO建了一张业务表跑了半个月才发现没有主键重复数据进了一堆回头补约束时已经很难清理。另外SELECT INTO在 PL/SQLOracle里没有直接对应写法Oracle 要用CREATE TABLE ... AS SELECT。跨数据库方言时这一点要特别注意。3.2 INSERT INTO SELECT 指定列灵活但列清单不能漏当目标表已经存在要把源表数据插进去用这条INSERT INTO table2 (id, name, address) SELECT id, name, address FROM table1 WHERE id 100;和SELECT INTO比它不建表只插数据。优势在于SELECT部分可以写得很复杂——多表 JOIN、WHERE 过滤、GROUP BY 聚合、窗口函数去重都能往上堆。目标列清单可以只写部分列但前提是没写的列允许 NULL 或者有默认值。这里有一个硬性规则目标表的非空列必须全部出现在列清单里否则插入直接失败。比如table2的name列是NOT NULL你只写了(id, address)数据库会拒绝执行。这个规则在数据迁移时最容易踩因为源表和目标表的非空约束往往不一致。3.3 省略列清单的简写顺序必须完全一致还有一种写法是省略目标列清单INSERT INTO table2 SELECT id, name, address FROM table1;这种简写形式默认你按目标表的全部列顺序插入。也就是说SELECT后面的列顺序必须和table2的列定义顺序完全一致一个都不能错位。如果table2的列顺序是(id, address, name)而你SELECT出来是(id, name, address)数据就会串列——name的值进了address列address的值进了name列。更麻烦的是如果两列数据类型兼容数据库不会报错数据静默错位等到业务发现时已经过去很久。我个人的习惯是永远不写简写形式。多打几个列名换来的是可读性和安全性。尤其是在生产环境做数据迁移列清单就是你的后悔药写全了顺序错了数据库会报错不写顺序错了数据库沉默。3.4 三种方式的选择对照方式目标表不存在时目标表已存在时能否指定列典型场景INSERT INTO VALUES报错插入指定值可以单条/批量写入SELECT INTO自动建表并插入报错通过 SELECT 控制临时表、快照INSERT INTO SELECT报错插入查询结果可以数据迁移、复制选型逻辑很直接写入已知值用 VALUES建中间表用 SELECT INTO往已有表搬数据用 INSERT INTO SELECT。三者不要混用尤其是别拿 SELECT INTO 去追加重已存在的表那是方向性错误。4. 避坑与排查插入数据时最容易翻车的五个点4.1 现象插入报“列名或所提供值的数目与表定义不匹配”原因通常是列清单和值清单数量不一致或者省略列清单时SELECT的列数和目标表列数不同。解决方法是把列清单写全用SELECT *先看一眼目标表结构确认列数和顺序。不要靠记忆靠sp_help table2或DESC table2。4.2 现象插入成功但数据串列原因就是省略了目标列清单且SELECT列顺序和目标表定义顺序不一致。解决方法是永远写全列清单。如果已经发生串列只能根据业务逻辑反向清洗没有通用后悔药。4.3 现象批量插入中途失败前面数据已入库原因是没有用事务包裹。解决方法是把批量插入放进BEGIN TRANSACTION/COMMIT失败时ROLLBACK。注意DDL 语句如SELECT INTO建表在部分数据库里不能回滚所以建表和插数据最好分开处理。4.4 现象插入速度极慢一条比一条慢原因可能是逐条提交、目标表索引过多、触发器逻辑复杂、或者锁等待。解决方法是改批量插入、插入前禁用非聚集索引迁移场景、检查触发器、用SET NOCOUNT ON减少网络往返。对于超大批量考虑BULK INSERT或LOAD DATA INFILE。4.5 现象非空列没赋值插入直接失败原因是目标表有NOT NULL约束而列清单里漏了该列且该列没有默认值。解决方法是插入前查INFORMATION_SCHEMA.COLUMNS确认非空列清单确保全部覆盖。迁移场景下源表允许 NULL 的列在目标表可能是NOT NULL这种差异要提前对齐。提示每次做数据迁移前先用SELECT TOP 10把源数据拉出来看一眼再对照目标表结构过一遍列清单比直接跑全量插入安全得多。5. 进阶技巧用窗口函数去重后再插入数据迁移时经常碰到源表有重复数据直接插进目标表会撞唯一索引。这时候可以在SELECT里用窗口函数先去重再插入。以 SQL Server 为例INSERT INTO table2 (id, name, address) SELECT id, name, address FROM ( SELECT id, name, address, ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn FROM table1 ) t WHERE t.rn 1;这段代码的逻辑是按id分组给每组内的行编号只取编号为 1 的那行也就是每个id只保留一条记录。PARTITION BY id是分组依据ORDER BY id决定保留哪一条——如果想保留最新的一条把ORDER BY改成时间字段倒序即可。参数上要注意ROW_NUMBER()必须配合OVER子句PARTITION BY后面跟去重维度ORDER BY后面跟优先级字段。MySQL 8.0 以上、PostgreSQL、Oracle 都支持窗口函数MySQL 5.7 不支持需要用GROUP BY配合自连接模拟。验证插入结果是否正确的习惯动作插入后立刻用SELECT COUNT(*)对比源表和目标表的行数再用EXCEPT或MINUS查差集SELECT id, name, address FROM table1 EXCEPT SELECT id, name, address FROM table2;返回空集说明数据完全一致。这个验证步骤我每次迁移后都会跑一遍花不了几秒钟但能挡住大部分“以为插完了其实漏了”的问题。从那以后我每次做INSERT INTO SELECT都强制走一遍行数对比和差集校验希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询