INSERT INTO temp table 在 SQL 服务器中失败,但相同的语句在 PostgreSQL、MySQL 和 SQLite 中运行完美

INSERT INTO temp table fails in SQL Server, but same statement runs perfectly in PostgreSQL, MySQL, & SQLite

我们正在编写一个内部 'speed test' 程序,用于学习/培训目的(和好奇心),可与多个后端一起使用 - PostgreSQL、SQL 服务器, MySQL, & SQLite.

该程序在相应的后端创建了一个临时文件 table(这是主要源文件 table 的简单副本),因此我们可以在此测试 UPDATE 和 INSERT 命令(没有破坏主要源数据)。它还创建了一个包含 1000 条记录的临时子集 table。然后它 运行 进行了几个计时测试,这些测试针对特定 SQL 操作遍历 1000 个子集记录:SELECT、UPDATE 和 INSERT。

所有测试 运行 在 PostgreSQL、MySQL 和 SQLite 中完美。 SQL 服务器 运行s SELECT & UPDATE 测试正常,但在所有 1000 个 INSERT 命令上都出错 - 仅指定了几个列/值。

(PostgreSQL & SQLite 将 NULL 值分配给所有未在 INSERT 命令中指定的列。MySQL 仅将 NULL 值分配给具有 DATE 或 TEXT 数据的未指定列类型。MySQL 将空值 0 分配给未指定的数字列,并将一串空格(正确长度)分配给 'VarChar' 列。)

SQL 服务器 INSERT INTO 完美地填充了它的主要来源 tables(我们在几个不同的来源上重复测试)。但是此过程为 table 中的每一列指定了一个值(主键除外)。

所以我们怀疑在测试本身(只指定了几列)中 INSERT 操作的失败可能是由于 SQL 服务器从主服务器带来了“非空”约束来源 table 进入临时 table,但不是默认值。当然,也可能完全是别的原因。但无论哪种方式,我们都被难住了。

我们用于在 SQL 服务器中创建(成功)大型临时文件 table 的 SQL 命令是:

select * into #TmpJobs from Jobs

所有其他后端都符合 ANSI。所以对于这些,我们使用命令:

create temporary table TmpJobs as select * from Jobs

SQL 服务器甚至拒绝了下面这个非常简单的插入命令(字段名全部大写或小写都没有区别):

insert into #TmpJobs (JobCode) values ('J1')

我们正在使用 SQLExpress 2019。通过 SSMS 查看时,管理 > SQL 服务器日志 > 当前文件中没有条目提供有关失败的 INSERT 命令的诊断信息。

任何人都可以看到我们遗漏了什么,并建议我们如何让它用于简单的 INSERT 操作(不为所有字段指定值)吗?

11 月 5 日添加: 感谢您的回复“Dale K”。 正如您所建议的,我在 SSMS 中有 运行 以下内容,这提供了更有用的诊断信息:

Use AdminsoftTest
select * into #TmpJobs from AdminsoftApps.Jobs
insert into #TmpJobs (JobCode) values ('J1')

这给出了第 3 行的错误消息:

Cannot insert the value NULL into column 'ClientCode', table 'tempdb.dbo.#TmpJobs___...
; column does not allow nulls. INSERT fails.

因此,“NOT NULL”列标准似乎确实已转移到临时 table,但不是默认值。

有谁知道将临时 table 的所有列设置为“可空”的简单方法?

(或者,有没有人知道一个好的模板存储过程可以为临时 table 的所有列设置合理的空默认值(数字等为 0)?)

解决方案:为每列添加一个“默认”table约束

在应用程序中,我们在临时 table 中添加了一个遍历所有字段名称的循环,其中 运行 SQL 服务器命令添加了一个“默认值” table每列的约束,主键列 (RowID) 除外。仅指定一些列的 INSERT 命令然后运行完美。

注意:SQL当我们试图将默认值 0 设置为 RowID 时,服务器返回了一个错误。所以,这个 'RowID' table 约束(主键的)似乎是从源 table 复制过来的(除非 SQL 服务器总是将名为“RowID”的列视为作为主键)。

以下是我们应用程序中成功的 SQL 命令序列的摘要:

select * into AdminsoftApps.#TmpJobs from AdminsoftApps.Jobs
alter table #TmpJobs add constraint DefaultJobCode default ' ' for JobCode
alter table #TmpJobs add constraint DefaultClientCode default ' ' for ClientCode
...  etc. (There are 45 fields in the Jobs table)

然后我们的应用程序的程序行(在 1,000 条记录的循环中)工作:

insert into AdminsoftApps.TmpJobs (JobCode, ClientCode, ...) values ( ... )

在我们应用程序的 INSERT 测试中,我们仅为每个插入的记录指定了 10 个列和值。添加新的默认 table 约束后,SQL 服务器接受所有 1,000 个 INSERT 命令,并在每个新记录中使用默认空值很好地填充所有其他字段。

以下(仅指定 SELECT 中的几列)在 SSMS 中有效:

Use AdminsoftTest
drop table #TmpJobs
select JobCode, JobName, ClientCode, AddrRecID into AdminsoftApps.#TmpJobs from AdminsoftApps.Jobs
alter table #TmpJobs add constraint DefaultJobCode default ' ' for JobCode
alter table #TmpJobs add constraint DefaultJobName default ' ' for JobName
alter table #TmpJobs add constraint DefaultClientCode default ' ' for ClientCode
alter table #TmpJobs add constraint DefaultAddrRecID default ' ' for AddrRecID
insert into AdminsoftApps.#TmpJobs (JobCode) values ('J1')

更深入地了解 NOT NULL 列约束

似乎 SQL 服务器自动将 NOT NULL 约束应用于由 SELECT ...INTO...

创建的临时 table 的每一列

在我们的测试中,它拒绝了试图将这些更改为 NULL 的“ALTER TABLE ALTER COLUMN FieldName NULL”语句。 (当时那些列中的现有数据中没有 NULL 值。)




清除 NOT NULL 列约束的尝试失败:

下面 'answer' 的其余部分仅供那些有兴趣进一步阅读我们试图清除 IS NULL 约束的人使用:

我们首先 运行 在 SSMS 中进行以下简单测试,以验证 SQL 服务器自动将 NOT NULL 约束应用于由 [=98 创建的临时 table 的所有列=] ...进入...:

Use AdminsoftTest
select '   ' as CharField1,  '   ' as CharField2, 100 as NumericField1
       into #TmpTest from AdminsoftApps.Jobs where RowID <= 3
insert into #TmpTest (CharField1) values ('J1')

注意:SELECT 中的所有列都是从字面值创建的 - none 是从源作业 table 带来的,它仅用于用 3 初始化 #TmpTest空记录。

这产生了以下错误消息:

(3 rows affected)
Msg 515, Level 16, State 2, Line 3
Cannot insert the value NULL into column 'CharField2', table 'AdminsoftTest.dbo.#TmpTest';
 column does not allow nulls. INSERT fails.
The statement has been terminated.

所以似乎 SQL 服务器确实自动将 IS NULL 约束应用于所有列 - 甚至是从文字值创建的新列(不在 [=98 中指定的源 table 中=]).

似乎 SQL 服务器拒绝更改由 SELECT ...INTO... 创建的临时 table 的这些 IS NULL 列约束的任何尝试,因为以下也失败了:

Use AdminsoftTest
drop table #TmpTest
select '   ' as CharField1,  '   ' as CharField2, 100 as NumericField1
       into #TmpTest from AdminsoftApps.Jobs where RowID <= 3
alter table #TmpTest alter column CharField2 NULL

此错误消息是:

Msg 156, Level 15, State 1, Line 5
Incorrect syntax near the keyword 'NULL'.

然而,上面第 5 行的语法似乎符合 https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-ver15 中的规范(可能是我们搞砸了,如果有人能给我更正的语法,我将不胜感激此命令 - SQL 服务器接受并正确应用。)

使用 SSMS 进行测试的怪癖(仅供兴趣)

令我们惊喜的是我们可以让它在应用程序中运行,因为我们使用 SSMS 进行的(更简单的)测试非常不一致且不成功 - 并且令人困惑。

使用文字值创建多个新列(并且没有来自源 table 的字段)的 SSMS 测试有效。但是,那些混合了一个活动字段和从文字创建的列的方法不起作用。

这是添加 table 约束的示例测试代码 - 并提供了一个在 SSMS 中有效的插入示例:

Use AdminsoftTest
select '   ' as CharField1,  '   ' as CharField2, 100 as NumericField1
       into #TmpTest from AdminsoftApps.Jobs where RowID <= 3
alter table #TmpTest add constraint CharField2Default default ' ' for CharField2
alter table #TmpTest add constraint NumericField1Default default 0 for NumericField1
insert into #TmpTest (CharField1) values ('J1')

但是,由于某些原因,SQL 服务器使用试图将值插入来自原始源的字段的命令吐出虚拟机 table:

Use AdminsoftTest
drop table #TmpTest
select JobCode, '   ' as CharField1,  '   ' as CharField2, 100 as NumericField1
       into #TmpTest from AdminsoftApps.Jobs where RowID <= 3
alter table #TmpTest add constraint JobCodeDefault default ' ' for JobCode
alter table #TmpTest add constraint CharField1Default default ' ' for CharField1
alter table #TmpTest add constraint CharField2Default default ' ' for CharField2
alter table #TmpTest add constraint NumericField1Default default 0 for NumericField1
insert into #TmpTest (JobCode) values ('J1')

这产生了一个错误:

Msg 207, Level 16, State 1, Line 9
Invalid column name 'JobCode'.

但是,以下工作:

Use AdminsoftTest
drop table #TmpTest
select JobCode, '   ' as CharField1,  '   ' as CharField2, 100 as NumericField1
       into #TmpTest from AdminsoftApps.Jobs where RowID <= 3
alter table #TmpTest add constraint JobCodeDefault default ' ' for JobCode
alter table #TmpTest add constraint CharField1Default default ' ' for CharField1
alter table #TmpTest add constraint CharField2Default default ' ' for CharField2
alter table #TmpTest add constraint NumericField1Default default 0 for NumericField1
insert into #TmpTest (CharField1) values ('J1')

这很不一致。它在第 5 行接受 'JobCode' 列,但在第 9 行拒绝它。它接受从字符串文字创建的新列的值,但不接受来自原始来源 table.如果有任何反馈可以解释为什么 SSMS 无法将值插入 'JobCode' 列(并且可能来自原始来源 table 的临时 table 中的任何其他列),我将不胜感激。

我希望这些额外的信息对某人有所帮助...

问候 格雷姆·埃文斯