2017-05-24 8 views
1

「voorwerpnummer」列に主キー違反があります。重複値は1ですが、関数は決して同じ数を2回与えてはいけません。 'Voorwerp'テーブルに何も置かずに関数をテストしようとすると、それは1を返し、 'Voorwerp'テーブルの1つの行でテストすると2を返します。それはすべきです。なぜこの挿入クエリ内で1が2回返されますか?私はSQL Server 2016を使用しています。このSQL関数がinsert文内で異なる動作をするのはなぜですか?

-- 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 

答えて

1

これは、SQLが行ベースではなく、セットベースで動作するためです。

最初にSELECTステートメントが実行されます。その結果、データセットはすべてvoorwerpnummerとなります(まだEenmaalAndermaal.dbo.Voorwerpに何も追加されていないため)。これは、SELECTステートメントだけを実行することでテストできます。その全体のセットはINSERTがターゲットテーブルに入っているので、GetVoorwerpnummer()を後で呼び出すと、が成功すると明らかにvoorwerpnummerとなります(voorwerpnummerがPKの場合は表示されません)。それを修正するには

、私は次の操作を行います:

-- 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 

以下Stilgarがコメントしたように、IDENTITY属性としてPKを定義することをお勧めします。

また、個人的には(「公式」のベストプラクティスであるかどうかはわかりませんが)、プライマリキーの列をテーブルの最初の列として保存することをお勧めします。

0

ほとんどの場合、INSERTをコミットしないでください。コミットするまで、データは「本当に」存在しません。

私のために、次の作品:

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; 
+0

一度にセット(複数のレコード)ではなく、単一のレコードを挿入した場合はどうなりますか?例えば: 'INSERT INTO tmp.PK_Test \t SELECT \tはdbo.F_PK_Test()、 '' そして、それが壊れるT ' – HoneyBadger

+0

AS \t INFORMATION_SCHEMA.TABLES FROM \t、私はそれ集合演算作るものの部分から逃しました。完全に私の悪い。ユニークなシーケンシャル番号を手動で管理するのではなく、IDENTITYを使用しないでください。 – Stilgar

+0

私はそれについて考えましたが、可能であればテーブルに何も変更を加えないでください。 – Skere

関連する問題