为什么这个SQL函数在insert语句中的工作方式不同?
我在“voorwerpnummer”列中得到一个主键冲突。重复值为1,但函数不应给出相同的数字两次。当我尝试在“Voorwerp”表中没有任何内容的情况下测试函数时,它返回1,如果我在“Voorwerp”表中用一行测试它,它返回2;应该这样。为什么在这个insert查询中两次返回1?我正在使用SQL Server 2016为什么这个SQL函数在insert语句中的工作方式不同?,sql,sql-server,tsql,Sql,Sql Server,Tsql,我在“voorwerpnummer”列中得到一个主键冲突。重复值为1,但函数不应给出相同的数字两次。当我尝试在“Voorwerp”表中没有任何内容的情况下测试函数时,它返回1,如果我在“Voorwerp”表中用一行测试它,它返回2;应该这样。为什么在这个insert查询中两次返回1?我正在使用SQL Server 2016 -- Empty tables DELETE FROM Bestand DELETE FROM Voorwerp_in_rubriek DELETE FROM Voorw
-- Empty tables
DELETE
FROM Bestand
DELETE
FROM Voorwerp_in_rubriek
DELETE
FROM Voorwerp
-- Delete function if exists
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[GetVoorwerpnummer]')
AND type IN ( N'FN', N'IF', N'TF', N'FS', N'FT' ))
DROP FUNCTION [dbo].[GetVoorwerpnummer]
GO
-- Returns the next item number
CREATE FUNCTION dbo.GetVoorwerpnummer()
RETURNS INTEGER
AS BEGIN
DECLARE @Highest INTEGER
-- Find the highest number
SET @Highest = (SELECT TOP 1 voorwerpnummer FROM EenmaalAndermaal.dbo.Voorwerp ORDER BY voorwerpnummer DESC)
-- Take 0 if there is none
SET @Highest = ISNULL(@Highest, 0)
-- Add 1
SET @Highest = (@Highest + 1);
RETURN @Highest
END
GO
INSERT INTO EenmaalAndermaal.dbo.Voorwerp (looptijd, looptijdbegin, startprijs, verzendkosten, verkoopprijs, beschrijving, betalingsinstructie, betalingswijzenaam, landnaam, plaatsnaam, titel, verzendinstructies, voorwerpnummer, wel_niet_indicator, verkoper, koper)
SELECT
10 AS looptijd,
GETDATE() AS looptijdbegin,
LEFT(Prijs,8) AS startprijs,
'1,20' AS verzendkosten,
NULL AS verkoopprijs,
'Beschrijving van product' AS beschrijving,
NULL AS betalingsinstructie,
'paypal' AS betalingswijzenaam,
LEFT(land,40) AS landnaam,
LEFT(Locatie,40) AS plaatsnaam,
LEFT(Titel,40) AS titel,
NULL AS verzendinstructies,
dbo.GetVoorwerpnummer() AS voorwerpnummer,
0 AS wel_niet_indicator,
LEFT(Verkoper, 40) AS Verkoper,
NULL AS koper
FROM EBAY.dbo.Items
这是因为SQL是基于集合的,而不是基于行的 首先执行
SELECT
语句。这将产生一个数据集,所有数据集都具有相同的voorwerpnummer
(因为尚未向eenmalandermal.dbo.Voorwerp
添加任何内容)。您可以通过执行SELECT
语句来测试这一点。整个集合都被插入到目标表中,因此后续调用GetVoorwerpnummer()
将导致更高的voorwerpnummer
(显然,如果INSERT
会成功,如果voorwerpnummer
是主键,则不会成功)
要更正它,我将执行以下操作:
-- Execute function once to fill variable, so it isn't needlessly executed for every row
DECLARE @Voorwerp INT = dbo.GetVoorwerpnummer();
INSERT INTO EenmaalAndermaal.dbo.Voorwerp (looptijd, looptijdbegin, startprijs, verzendkosten, verkoopprijs, beschrijving, betalingsinstructie, betalingswijzenaam, landnaam, plaatsnaam, titel, verzendinstructies, voorwerpnummer, wel_niet_indicator, verkoper, koper)
SELECT
10 AS looptijd,
GETDATE() AS looptijdbegin,
LEFT(Prijs,8) AS startprijs,
'1,20' AS verzendkosten,
NULL AS verkoopprijs,
'Beschrijving van product' AS beschrijving,
NULL AS betalingsinstructie,
'paypal' AS betalingswijzenaam,
LEFT(land,40) AS landnaam,
LEFT(Locatie,40) AS plaatsnaam,
LEFT(Titel,40) AS titel,
NULL AS verzendinstructies,
COALESCE(@Voorwerp, 0) + ROW_NUMBER() OVER(ORDER BY id /*Choose a logical, preferably unique column here*/) AS voorwerpnummer,
0 AS wel_niet_indicator,
LEFT(Verkoper, 40) AS Verkoper,
NULL AS koper
FROM EBAY.dbo.Items
正如Stillgar在下面评论的那样,最好将PK定义为IDENTITY
属性
此外,就个人而言(我不确定这是否是“官方”最佳实践),我更喜欢将主键列保留为表的第一列。最有可能的问题是不提交插入;在提交之前,数据并不“真正”存在 以下是我的作品:
CREATE TABLE dbo.PK_Test
(
PK INT
,TXT VARCHAR(0032)
);
BEGIN TRANSACTION
INSERT INTO dbo.PK_Test VALUES(05, 'First');
COMMIT TRANSACTION;
CREATE FUNCTION dbo.F_PK_Test()
RETURNS INTEGER
AS BEGIN
DECLARE @Highest INTEGER
-- Find the highest number
SET @Highest = (SELECT TOP 1 PK FROM PK_Test ORDER BY PK DESC)
-- Take 0 if there is none
SET @Highest = ISNULL(@Highest, 0)
-- Add 1
SET @Highest = (@Highest + 1);
RETURN @Highest
END;
SELECT * FROM dbo.PK_Test;
BEGIN TRANSACTION
INSERT INTO Adhoc_Area.dbo.PK_Test (PK, Txt) VALUES (Adhoc_Area.dbo.F_PK_Test(), Adhoc_Area.dbo.F_PK_Test());
INSERT INTO Adhoc_Area.dbo.PK_Test (PK, Txt) VALUES (Adhoc_Area.dbo.F_PK_Test(), Adhoc_Area.dbo.F_PK_Test());
COMMIT TRANSACTION;
SELECT * FROM dbo.PK_Test;
如果一次插入一组(多条记录),而不是一条记录,会发生什么?例如:
INSERT INTO tmp.PK_Test从INFORMATION_SCHEMA.TABLES中选择dbo.F_PK_Test(),''作为T
,然后它就会断开,我错过了使它成为set操作的FROM部分。完全是我的错。所以现在的问题是,为什么不使用IDENTITY而不是手动管理唯一的序列号呢?我考虑过这一点,但如果可能的话,我宁愿不对表进行任何更改。也许是序列号?