为什么 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_2
是 NOT 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) 在查询浏览器中工作。
脚注
- 我正在将 Doctrine 从 CentOS 机器连接到 SQL 服务器。我正在使用 FreeTDS 连接到 SQL 服务器,并且我正在使用第三方 DBlib Doctrine 驱动程序。我知道这很绕哈哈。
- 我没有包括该实体,因为我不确定它是否有帮助。 Doctrine生成的SQL是正确的
如果您没有在 CREATE TABLE
中明确指定列是 NULL
还是 NOT NULL
,事情就会变得复杂。没必要,你可能会说。
SQL 服务器的开发人员以他们的智慧(或者,更可能是为了迎合向后兼容性)认为适合通过不少于三个选项来管理它:
- 如果(会话级)选项
ANSI_NULL_DFLT_ON
是 ON
,则未指定的列可以为空。
- 如果(会话级别)选项
ANSI_NULL_DFLT_OFF
为 ON
,则未指定的列不可为空。
- 这些选项是互斥的,因此它们不能都是
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支持,那值得考虑。
我有一个 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_2
是 NOT 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) 在查询浏览器中工作。
脚注
- 我正在将 Doctrine 从 CentOS 机器连接到 SQL 服务器。我正在使用 FreeTDS 连接到 SQL 服务器,并且我正在使用第三方 DBlib Doctrine 驱动程序。我知道这很绕哈哈。
- 我没有包括该实体,因为我不确定它是否有帮助。 Doctrine生成的SQL是正确的
如果您没有在 CREATE TABLE
中明确指定列是 NULL
还是 NOT NULL
,事情就会变得复杂。没必要,你可能会说。
SQL 服务器的开发人员以他们的智慧(或者,更可能是为了迎合向后兼容性)认为适合通过不少于三个选项来管理它:
- 如果(会话级)选项
ANSI_NULL_DFLT_ON
是ON
,则未指定的列可以为空。 - 如果(会话级别)选项
ANSI_NULL_DFLT_OFF
为ON
,则未指定的列不可为空。 - 这些选项是互斥的,因此它们不能都是
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
orNOT NULL
inCREATE TABLE
andALTER TABLE
statements.
如果 Doctrine 的作者这样做了,您可能会完全忘记上面的文字。
1 虽然 FreeTDS 仍然是一个流行的选择,但我想指出的是,微软最近投入了更多资源来支持他们以前萎靡不振的跨平台 ODBC 支持。 Microsoft ODBC Driver for SQL Server on Linux 在编写 Linux 的功能和兼容性方面的最佳连接选项时,它应该避免 SET
选项具有意想不到的古老默认值的问题。如果你的软件有ODBC支持,那值得考虑。