🪟 第6章 SQL基础
📄 一句话概括

单行注释 --,多行注释 /* */;两种写法四家数据库都一样,没有方言差异。 /* */ 能不能嵌套看产品:SQL Server 能,其它三家不能——不能的那几家会提前收尾。 GO 不是 SQL 语句,是客户端的分隔符;把它发给数据库会报语法错误。

📝 本章和前面几章的关系

注释这件事看着没内容, 但 GO 那部分很重要——它是「变量作用域被切断」的原因,也是「程序里带 GO 就报错」的原因。 这两件事都归在「批处理」这个概念下。 教材 6.6 分三小节:单行注释、多行注释、批处理分隔符 GO。 前两节五分钟看完,第三节要慢一点。 演示里跑了一段带两种注释的 SQL(能跑,注释在哪儿都一样), 又试了一次「把 GO 当语句发出去」——报错信息就是最好的证据。

一、两种注释

-- 这是单行注释,一直管到行尾
SELECT 1 AS a  -- 行尾也能加

/* 这是多行注释
   可以跨好几行
   也可以只包一段 */
SELECT 2 AS b

单行注释用两个减号,一直管到这一行结束。 所以它能加在语句末尾, 不影响前面的代码——调试的时候经常用它来「临时注释掉一行」。 多行注释用斜杠星号包起来。 它可以跨行,也可以只包一段——甚至可以插在语句中间: SELECT /* 中间 */ 1。 注释在四家数据库里写法都一样。 这是本课少有的「完全没有方言差异」的地方——值得庆幸, 因为注释常常是复制粘贴过来的。 一个实用习惯:注释里写「为什么」,不写「是什么」。-- 按 id 排序 这种注释没价值, 代码本身就写着;-- 这里必须按 id 排,否则分页会重复 才有价值。

二、多行注释能不能嵌套

数据库 能嵌套吗 后果
SQL Server 能 内外配对,不影响
MySQL 不能 内层结束符就收尾
标准 SQL 不能 同上
SQLite 不能 同上

这一行差异很隐蔽。 SQL Server 里 /* a /* b */ c */ 整段都是注释; 其它三家在第一个 */ 就收尾了,后面的 c */ 就变成了代码。 后果是「报语法错误」或者「多出一段莫名其妙的代码」。如果你注释掉的代码里本来就有 /* */, 这个问题就会出现。 所以两条实用建议: 一是注释里尽量别写 */ 这个符号,换个说法; 二是「注释掉一大段代码」时,用单行注释逐行加,比包一层 /* */ 安全。

三、GO 不是 SQL 语句

这是本节最重要的一句话。 GO 由 SSMS、sqlcmd 这些客户端识别,服务器根本不认识它。

SELECT 1
GO
SELECT 2

客户端看到 GO,就把上面的代码打包成一批发过去,然后发下一批。服务器收到的是两次独立的请求, 它不知道中间有个 GO。 证据很简单:把 GO 当语句发给数据库,会报语法错误。演示里就是这么做的—— 报错信息直接说明「数据库不认这个词」。 这解释了两件事: 一是「变量的作用域为什么被切断」(每批独立编译), 二是「为什么程序里带 GO 就报错」(程序是直接发 SQL 的,没有客户端帮你切)。

四、批处理的三个实际影响

影响 说明
变量不跨批 批 1 声明的变量,批 2 看不见
编译分开 每批独立编译,报错只影响那一批
某些语句必须独占一批 CREATE VIEW 后面不能跟别的语句

第一条在 6.3 讲过了。 它是「变量未声明」这个报错的根源。 第三条是硬规定,必须记住:CREATE VIEW、CREATE PROCEDURE、CREATE TRIGGER 这类语句, 必须是批里的第一句。 所以脚本里常见 CREATE VIEW 前面有个 GO。 那不是习惯, 是必须——不加的话报「'CREATE VIEW' 必须是查询批中的第一个语句」。 这个报错信息其实说得很清楚,但第一次见的人往往会去找「视图语法哪里错了」, 结果找了半天——其实只是缺一个 GO。

五、动手 Demo:注释与 GO 的真实反馈

这一段跑一段带两种注释的 SQL,试一次嵌套注释,再把 GO 当语句发给数据库——看它报什么, 这是「GO 不是 SQL」最直接的证据。

import sqlite3

c = sqlite3.connect(u":memory:")

sql = u"""-- 单行注释
SELECT 1 AS a  -- 行尾注释
/* 多行注释
   跨两行 */
, 2 AS b"""
print(c.execute(sql).fetchall())

# 嵌套注释:SQLite 不支持,内层 */ 就收尾了
try:
    print(c.execute(u"SELECT /* a /* b */ 1 */ AS x").fetchone())
except sqlite3.OperationalError as e:
    print(u"嵌套注释:", e)

# GO 不是 SQL 语句
try:
    c.execute(u"SELECT 1 GO")
except sqlite3.OperationalError as e:
    print(u"发 GO 出去:", e)

实跑输出:

--------------------------------------------------------------------
注释与 GO
--------------------------------------------------------------------


  ① 两种注释
  --------------------------------------------------------------------
  跑一段带两种注释的语句,结果:[(1, 2)]
  单行注释用两个减号,一直管到行尾。
  多行注释用斜杠星号包起来,可以跨行,也可以只包一段。
  两种注释在四家数据库里写法都一样,这一点没有方言差异。

  ② 多行注释能不能嵌套
  --------------------------------------------------------------------
| 数据库 | 能嵌套吗 | 后果 |
|---|---|---|
| SQL Server | 能 | 内外配对,不影响 |
| MySQL | 不能 | 内层结束符就收尾了 |
| 标准 SQL | 不能 | 同上 |
| SQLite | 不能 | 同上 |

  嵌套注释试一下:near "/": syntax error
  SQL Server 支持嵌套,所以那段能过;其它三家不行——
  内层的 */ 一出现,整段注释就结束了,后面的代码就被当成语句。
  所以注释里如果要提到 */ 这个符号,干脆别写,换个说法。

  ③ GO 不是 SQL 语句
  --------------------------------------------------------------------
  GO 是「批处理分隔符」,由 SSMS、sqlcmd 这些客户端识别,不是服务器认的。
  它做三件事:把上面的语句打包成一批发过去、清空变量、重新编译。
  这就是证据:数据库不认 GO,只有客户端认。
  所以用程序连数据库(JDBC、pymssql 之类)时,SQL 里不能带 GO——会报语法错误。
  要么在客户端里跑,要么自己按 GO 切开,一段一段发。

  ④ 批处理的三个实际影响
  --------------------------------------------------------------------
| 影响 | 说明 |
|---|---|
| 变量不跨批 | 批 1 声明的变量,批 2 看不见 |
| 编译分开 | 每批独立编译,报错只影响那一批 |
| 某些语句必须独占一批 | CREATE VIEW 后面不能跟别的语句 |

  第三条是硬规定:CREATE VIEW、CREATE PROCEDURE 这类语句必须是批里的第一句。
  所以脚本里常见 CREATE VIEW 前面有个 GO——不是习惯,是必须。
  这一条教材在视图那一章会再说一次,先记住「建视图要单独一批」。

第一段能跑,说明注释不影响语义。 两种注释混在同一段 SQL 里, 结果还是 (1, 2)——注释被解析器直接跳过了。 第二段的报错就是「嵌套不了」的证据。 内层的 */ 一出现,整段注释就结束了, 后面的 1 */ 变成了代码——所以报语法错误。 第三段是本节的核心证据:GO 发给数据库,报「语法错误」。说明它不是 SQL 语句,只有客户端认它。 这两条加起来解释了一个常见困惑: 在 SSMS 里跑得好好的脚本,用程序(JDBC、 Python)跑就报错——因为程序没有客户端那层,不会替你切 GO。

30 秒实验

  1. 在 SSMS 里跑一段带 GO 的脚本,再把同样的内容通过程序发出去。 前者成功,后者报语法错误——这就是「客户端 vs 服务器」的分界线。
  2. 写一个 CREATE VIEW 脚本,前面不加 GO,看它报什么。 报的是「必须是查询批中的第一个语句」——加上 GO 就好了。
  3. 想一想:为什么注释掉的代码里如果有 */,会出问题? 因为多行注释在第一个 */ 就收尾了。 后面的内容变成了代码,不报错才怪。

常见误区

  1. 「GO 是一条 SQL 语句」。不是。它是客户端的分隔符,服务器不认识。 把它发给数据库会报语法错误——程序里带 GO 就是这个原因。
  2. 「多行注释可以随便嵌套」。只有 SQL Server 能嵌套。 其它三家在第一个 */ 就收尾,后面的内容变成代码。注释掉带 /* */ 的代码时尤其要小心。
  3. 「注释越多越好」。注释写「是什么」是噪音,写「为什么」才有价值。 代码本身写着「按 id 排序」,你再注释一遍没有信息量;写清「为什么必须按 id 排」才有用。
自测三问(点开对答案) 1. `GO` 是 SQL 语句吗? —— **不是,是客户端的分隔符。** SSMS、`sqlcmd` 认它,服务器不认。 **所以程序里带 `GO` 会报语法错误**——程序是直接发 SQL 的,没有客户端帮你切。 2. 为什么 `CREATE VIEW` 前面要加 `GO`? —— **因为这类语句必须是批里的第一句。** `CREATE VIEW`、`CREATE PROCEDURE`、 `CREATE TRIGGER` 都受这个限制——**不加 `GO` 就报「必须是查询批中的第一个语句」。** 3. 多行注释嵌套会出什么问题? —— **除 SQL Server 外,其它三家在第一个 `*/` 就收尾了。** 后面的内容变成代码, 报语法错误或者多出一段莫名其妙的语句。**注释里尽量别写 `*/` 这个符号。**

下一篇 6.7 讲运算符。重点是两处不报错的方言差异:+ 是加法还是拼接、 整数除法为什么丢掉小数——这两个都是「结果不对但不报错」的典型。

相关笔记