🪟 第7章 SQL函数的使用
📄 一句话概括

系统函数不处理数据,只回答「我在哪儿」:当前库、登录名、客户端、版本。 改数据之前先跑一句 SELECT DB_NAME(),确认没连错库——这一句能省掉很多事故。 现代查元数据用 sys 视图,比 OBJECT_ID、COL_LENGTH 这些函数清楚得多。

📝 本章和前面几章的关系

系统函数是「元数据」这一层的东西——它们查的不是业务数据,是数据库自己的信息。 教材 7.5 分三小节:常用系统函数、元数据函数、系统函数在排查里的用法。 前两节是清单,第三节才是重点——它讲的是「怎么用这些函数定位问题」。 演示里跑不出 SQL Server 那套(DB_NAME()、HOST_NAME() 都没有), 但 SQLite 有等价的:sqlite_version()、PRAGMA database_list、sqlite_master。 思路是一样的:先确认「我在哪个库、有哪些对象」,再动手。

一、常用的七个

函数 返回什么 什么时候用
@@VERSION 版本字符串 查兼容性
DB_NAME() 当前库名 改数据前确认
HOST_NAME() 客户端机器名 查是谁连的
SUSER_NAME() 当前登录名 查权限问题
OBJECT_ID('表') 对象内部编号 判断对象在不在
NEWID() 生成 GUID 要全局唯一值时
ISNULL(x,y) 空值替换 报表里填空

DB_NAME() 是最该养成习惯的一个。 连着生产库和测试库的时候, 敲 DELETE 之前先看一眼这个——这一眼能省掉的事故,比任何规范都实在。 OBJECT_ID('表名') 返回 NULL 表示表不存在。 用它做「建表前先判断」比 IF EXISTS (SELECT * FROM sys.tables ...) 短。 NEWID() 生成的是 GUID,全局唯一但不是递增的。所以拿它当主键会让插入变慢—— 新行随机分布在索引里,每次都造成页分裂。要唯一又要递增,用「NEWSEQUENTIALID()」或者自增列。

二、元数据函数

函数 返回什么 注意
COL_LENGTH('表','列') 列占多少字节 NULL 表示列不存在
COL_NAME(id, 列号) 列名 配合 syscolumns 用
OBJECTPROPERTY(id,'IsTable') 是不是表 1 是,0 不是
SCHEMA_NAME(id) 架构名 一般是 dbo

这四个现在用得少了,因为有了 sys 视图。查表用 sys.tables、查列用 sys.columns、 查对象用 sys.objects——比这些函数清楚,也能一次查出一批。 但老代码里这些函数到处都是。 看到 COL_LENGTH 返回 NULL, 就该想到「列不存在」——这个直觉能省很多时间。 本课后面讲高级应用(第 10 到 17 章)时会用 sys 视图。那时候再细说—— 现在只要知道「有这些东西、它们干什么用」。

三、排查里的用法:四行一句话

这是本节最实用的一段。 出问题时,先把这四行发过去:

SELECT DB_NAME()     AS 当前库,
       SUSER_NAME()   AS 登录名,
       HOST_NAME()    AS 客户端,
       @@VERSION      AS 版本

比说「我连不上」有用得多。 对方一眼能看出:是不是连错库了、是不是权限不对、 是不是版本不兼容——这三个是最常见的原因。 建议把这四行抄进自己的常用脚本。 比背函数有用, 而且每次出问题都能用上——这是投入产出比最高的一句 SQL。 HOST_NAME() 在查「谁在锁表」的时候特别好用。查到锁的来源进程号,再用 HOST_NAME() 一对照, 就知道是哪台机器——比在服务器上翻连接列表快。

四、动手 Demo:查「我在哪儿、我是谁」

这一段先列 SQL Server 的七个系统函数(本沙箱不执行),再用 SQLite 的等价物真跑一遍:版本号、 挂着的库、表数量——思路完全一样。

import sqlite3

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

print(u"版本:", c.execute(u"SELECT sqlite_version()").fetchone()[0])

print(u"挂着的库:")
for r in c.execute(u"PRAGMA database_list"):
    print(u"  ", r[1], r[2] or u"(临时库)")

n = c.execute(u"SELECT COUNT(*) FROM sqlite_master "
              u"WHERE type = 'table'").fetchone()[0]
print(u"表数量:", n)

实跑输出:

--------------------------------------------------------------------
系统函数:排查时的第一把工具
--------------------------------------------------------------------


  ① SQL Server 的常用系统函数
  --------------------------------------------------------------------
| 函数 | 返回什么 | 什么时候用 |
|---|---|---|
| @@VERSION | 版本字符串 | 查兼容性 |
| DB_NAME() | 当前库名 | 确认连的哪个库 |
| HOST_NAME() | 客户端机器名 | 查是谁连的 |
| SUSER_NAME() | 当前登录名 | 查权限问题 |
| OBJECT_ID('表') | 对象内部编号 | 判断对象在不在 |
| NEWID() | 生成 GUID | 要全局唯一值时 |
| ISNULL(x,y) | 空值替换 | 报表里填空 |

  这几个函数的价值在「排查」——连不上、找不到表、权限不对,先用它们确认「我在哪儿」。
  DB_NAME() 最常用:改数据之前先跑一句 SELECT DB_NAME(),确认没连错库。
  这一句能省掉的事故,比任何规范都实在。

  ② 本沙箱的等价物
  --------------------------------------------------------------------
  sqlite_version() = 3.53.1
  当前挂着的库:
    main -> (临时库)
  sqlite_master 里的表数:0
  SQLite 没有 DB_NAME(),但 PRAGMA database_list 能列出所有挂着的库。
  SQLite 也没有 OBJECT_ID(),用 sqlite_master 查——那张表记着所有表、索引、视图。
  思路是一样的:先确认「我在哪个库、有哪些对象」,再动手。

  ③ 元数据函数:查表结构的三种办法
  --------------------------------------------------------------------
| 函数 | 返回什么 | 注意 |
|---|---|---|
| COL_LENGTH('表','列') | 列占多少字节 | 0 表示列不存在 |
| COL_NAME(id, 列号) | 列名 | 配合 syscolumns 用 |
| OBJECTPROPERTY(id,'IsTable') | 是不是表 | 1 是,0 不是 |

  现代写法是查系统视图:sys.tables、sys.columns、sys.objects,比这些函数清楚多了。
  但老代码里这些函数到处都是,看懂就行——看到「COL_LENGTH 返回 0」,就知道是列不存在。
  本课后面第 10 到 17 章讲高级应用时,会用 sys 视图查表结构,那时候再细说。

  ④ 一个实用的排查组合
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | SELECT DB_NAME() AS 当前库,
  |        SUSER_NAME() AS 登录名,
  |        HOST_NAME() AS 客户端,
  |        @@VERSION AS 版本

  四行一句,把「我在哪个库、我是谁、从哪来、什么版本」全查出来。
  出问题时先把这四行发过去,比说「我连不上」有用得多——对方一眼能看出是不是环境问题。
  这一句建议抄进自己的常用脚本里,比背函数有用。

这三段做的事跟 SQL Server 那四行一样:确认环境。版本、库、 对象数量——先知道自己在哪儿,再动手。 PRAGMA database_list 在 SQLite 里相当于「列出所有数据库」。 它比 SQL Server 的 sys.databases 简单——只列当前连接挂着的,不是实例上全部的。 sqlite_master 就是 SQLite 的「系统表」。 它记着所有表、索引、视图、 触发器——相当于 SQL Server 的 sys.objects。 所以「查元数据」这件事,各家都有,只是名字不同。SQL Server 用 sys 视图, SQLite 用 sqlite_master,MySQL 用 information_schema——思路一样,写法全不一样。

30 秒实验

  1. 在你的库上跑 SELECT DB_NAME(), SUSER_NAME(), HOST_NAME()。 把这三个值记下来——以后每次改数据前先看一眼,养成习惯。
  2. 跑 SELECT OBJECT_ID('不存在的表'),看它返回什么。 返回 NULL——这就是「判断对象存不存在」的写法。
  3. 想一想:为什么 NEWID() 不适合当主键? 因为它是随机分布的。 新行插在索引中间, 每次都造成页分裂——而自增列总是插在最后,不会有这个问题。

常见误区

  1. 「连着生产库和测试库,靠记忆区分就行」。记忆会错。 改数据前跑一句 SELECT DB_NAME(),两秒钟的事。这一步省不掉——省掉的人后来都补过课。
  2. 「NEWID() 生成的 GUID 当主键最好,永远不重复」。唯一是真的, 但它是随机分布,插入会让索引频繁分裂。数据量大了之后,插入性能会明显下降。
  3. 「查元数据用 OBJECT_ID、COL_LENGTH 这些函数最直接」。现代写法是查 sys 视图。 函数只能一次查一个对象,视图能一次列出一批——而且条件、排序、连接都能用。
自测三问(点开对答案) 1. `DB_NAME()` 有什么用?为什么说它最重要? —— **返回当前连的库名。** 改数据前跑一句, 能确认没连错库——**连着生产和测试的时候,这一句能省掉删错库的事故。** 2. `OBJECT_ID('表名')` 返回 `NULL` 说明什么? —— **说明这个对象不存在。** 所以可以拿它做「建表前先判断」——比查 `sys.tables` 短, 但只能一次判断一个。 3. 为什么 `NEWID()` 不适合当主键? —— **因为它是随机分布的。** 新行插在索引中间, 每次都要分裂页——**自增列总是插在最后,不会有这个问题。** 要唯一又要递增, 用 `NEWSEQUENTIALID()`。

下一篇 7.6 讲自定义函数,也是第 7 章的收尾。 标量函数返回一个值,表值函数返回一张表——后者性能好得多,因为它能被优化器展开进外层查询。

相关笔记