为什么 SQL 服务器以不同的方式对待相同的 SQL?

Why does SQL Server treat this same SQL differently?

我有一个 CREATE TABLE 脚本。当我通过 命令行 运行 它时,它不会创建可为空的列。当我 运行 它通过 SQL 服务器的查询浏览器 手动 时,它确实如此。我错过了什么?

通过命令行

我正在尝试使用 Doctrine 2 的命令行模式工具在 SQL Server 2014 (1) 中创建我的模式。

这是它根据我实体的注释创建的 SQL (2)(注意,address_2 可以为空)(间距和箭头是我的):

# dump the SQL Doctrine will use
vendor/bin/doctrine-module orm:schema-tool:create --dump-sql
CREATE TABLE company (
    id INT IDENTITY NOT NULL, 
    address_1 NVARCHAR(255) NOT NULL, 

    address_2 NVARCHAR(255),   <-- it should be optional

    city NVARCHAR(31) NOT NULL, 
    email NVARCHAR(255), 
    name NVARCHAR(255) NOT NULL, 
    phone NVARCHAR(12), 
    state NVARCHAR(2) NOT NULL, 
    zip NVARCHAR(5) NOT NULL, 
    PRIMARY KEY (id)
);

# execute the command
vendor/bin/doctrine-module orm:schema-tool:create

根据 SQL Server Profiler,SQL 服务器收到以下命令(注意,address_2 应该仍然可以为空)(间距和箭头是我的):

set textsize 20971520 
go
set textsize 2147483647
set quoted_identifier on

go
SET ANSI_WARNINGS ON
go
SET ANSI_PADDING ON
go
SET ANSI_NULLS ON
go
SET QUOTED_IDENTIFIER ON
go
SET CONCAT_NULL_YIELDS_NULL ON
go
CREATE TABLE company (
    id INT IDENTITY NOT NULL, 
    address_1 NVARCHAR(255) NOT NULL, 

    address_2 NVARCHAR(255),   <-- it should be optional

    city NVARCHAR(31) NOT NULL, 
    email NVARCHAR(255), 
    name NVARCHAR(255) NOT NULL, 
    phone NVARCHAR(12), 
    state NVARCHAR(2) NOT NULL, 
    zip NVARCHAR(5) NOT NULL, 
    PRIMARY KEY (id)
)
go

但是,当您检查创建的 table 时,address_2NOT NULL

这是 table 浏览器的屏幕截图以及 SQL Server's equivalent to MySQL's SHOW CREATE TABLE 的输出(间距和箭头是我的):

Table created via command line

USE [mydatabase]
GO

/****** Object:  Table [dbo].[company]    Script Date: 11/11/2016 9:47:03 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[company](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [address_1] [nvarchar](255) NOT NULL,

    [address_2] [nvarchar](255) NOT NULL,   <-- it's not optional!

    [city] [nvarchar](31) NOT NULL,
    [email] [nvarchar](255) NOT NULL,
    [name] [nvarchar](255) NOT NULL,
    [phone] [nvarchar](12) NOT NULL,
    [state] [nvarchar](2) NOT NULL,
    [zip] [nvarchar](5) NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

通过查询浏览器

现在,有趣的部分来了。

如果您 运行 与 SQL 服务器收到的 完全相同的命令(根据 SQL Server Profiler)在 SQL服务器查询浏览器(间距和箭头是我的):

set textsize 20971520 
go
set textsize 2147483647
set quoted_identifier on

go
SET ANSI_WARNINGS ON
go
SET ANSI_PADDING ON
go
SET ANSI_NULLS ON
go
SET QUOTED_IDENTIFIER ON
go
SET CONCAT_NULL_YIELDS_NULL ON
go
CREATE TABLE company (
    id INT IDENTITY NOT NULL, 
    address_1 NVARCHAR(255) NOT NULL, 

    address_2 NVARCHAR(255),   <-- it should be optional

    city NVARCHAR(31) NOT NULL, 
    email NVARCHAR(255), 
    name NVARCHAR(255) NOT NULL, 
    phone NVARCHAR(12), 
    state NVARCHAR(2) NOT NULL, 
    zip NVARCHAR(5) NOT NULL, 
    PRIMARY KEY (id)
)
go

address_2字段为NULL:

Table created via query browser

USE [mydatabase]
GO

/****** Object:  Table [dbo].[company]    Script Date: 11/11/2016 9:49:41 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[company](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [address_1] [nvarchar](255) NOT NULL,

    [address_2] [nvarchar](255) NULL,  <-- it's optional!

    [city] [nvarchar](31) NOT NULL,
    [email] [nvarchar](255) NULL,
    [name] [nvarchar](255) NOT NULL,
    [phone] [nvarchar](12) NULL,
    [state] [nvarchar](2) NOT NULL,
    [zip] [nvarchar](5) NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

结论

这到底是怎么回事? 相同 SQL 脚本如何产生 不同 结果?

我在网上搜索过帮助,但我似乎无法找到问题的解释或解决方案。任何帮助将不胜感激!

在我看来:

  • SET 标志无关紧要,因为查询在 SQL 服务器查询浏览器中与它们一起工作。另外,我调查了它们,其中 none 似乎改变了 T-SQL 对可选列的行为。
  • 我在 *nix 机器上将 Doctrine 连接到 SQL 服务器所经历的困难应该无关紧要 (1),因为到达 SQL 服务器的命令(如 SQL Server Profiler) 在查询浏览器中工作。

脚注

  1. 我正在将 Doctrine 从 CentOS 机器连接到 SQL 服务器。我正在使用 FreeTDS 连接到 SQL 服务器,并且我正在使用第三方 DBlib Doctrine 驱动程序。我知道这很绕哈哈。
  2. 我没有包括该实体,因为我不确定它是否有帮助。 Doctrine生成的SQL是正确的

如果您没有在 CREATE TABLE 中明确指定列是 NULL 还是 NOT NULL,事情就会变得复杂。没必要,你可能会说。

SQL 服务器的开发人员以他们的智慧(或者,更可能是为了迎合向后兼容性)认为适合通过不少于三个选项来管理它:

  • 如果(会话级)选项 ANSI_NULL_DFLT_ONON,则未指定的列可以为空。
  • 如果(会话级别)选项 ANSI_NULL_DFLT_OFFON,则未指定的列不可为空。
  • 这些选项是互斥的,因此它们不能都是 ON。但是,它们可以都是 OFF。如果是,则可空性由数据库级选项 ANSI_NULL_DEFAULT 控制,它可以是 ON(未指定的列可以为空)或 OFF(未指定的列不可为空)。

大多数客户端将通过 ODBC 或 OLE DB 连接,它们都将设置 ANSI_NULL_DFLT_ON(毕竟它不是无缘无故地称为 ANSI)。这意味着如果未指定任何内容,大多数 "modern" 客户端将始终获得可为空的列。但是通过(古老的)DB-Library 连接的客户端,显然在这个特定设置中还有 FreeTDS,没有指定这些选项,因此数据库设置开始起作用。1

在创建数据库时,设置取自 model 数据库并且是 documented。他们强调向后兼容性,对于新创建的数据库,ANSI_NULL_DEFAULT 默认为 OFF 也就不足为奇了。但至少在这里你有机会介入并改变事情,

ALTER DATABASE [db] SET ANSI_NULL_DEFAULT ON

这样做是否合适取决于您的设置。如果可能,您应该通过设置 ANSI_NULL_DLFT_ON 选项的驱动程序进行连接(或者您自己明确地这样做),以便所有新旧应用程序都能获得它们所期望的。在数据库上设置默认值(甚至在 model 上以便所有数据库继承它们)可能是一件好事,因为它确保了一致性,也可能是一件坏事,因为它会破坏旧客户端。

Books Online wisely notes on this subject:

For a more reliable operation of Transact-SQL scripts that are used in databases with different nullability settings, it is better to specify NULL or NOT NULL in CREATE TABLE and ALTER TABLE statements.

如果 Doctrine 的作者这样做了,您可能会完全忘记上面的文字。


1 虽然 FreeTDS 仍然是一个流行的选择,但我想指出的是,微软最近投入了更多资源来支持他们以前萎靡不振的跨平台 ODBC 支持。 Microsoft ODBC Driver for SQL Server on Linux 在编写 Linux 的功能和兼容性方面的最佳连接选项时,它应该避免 SET 选项具有意想不到的古老默认值的问题。如果你的软件有ODBC支持,那值得考虑。