none
SET @sql = N'SELECT @@IDENTITY AS ID' im SQLServer RRS feed

  • Frage

  • Hallo,

    wie bekomme ich aus obigem Abfragestring den ID? Nicht als Outputwert, sondern für einen internen Vergleichswert.

     

    Gruß Andreas

    Donnerstag, 9. Juni 2011 17:56

Antworten

  • Hallo Andreas,

    Die Fehlermeldung wird immer (an den Client) zurückgegeben, @@ERROR wird nur zusätzlich gesetzt.

    Außerdem gilt @@ERROR (und auch @@ROWCOUNT) werden durch jede Anweisung neu gesetzt,
    deswegen musst Du sie sofort nach der Ausführung sichern, wenn Du später darauf zurückgreifen willst.

    Mehr siehe http://www.sommarskog.se/error_handling_2005.html ff.

    Das wäre aber der kleinere Teil.

    Wenn Du nicht was Wesentliches ausgelassen hast, ist jedoch sp_executesql hier überflüssig.

    Man kann Parmeter direkt verwenden, der Einsatz von sp_executesql wäre nur notwendig,
    wenn dort Teile wie Tabellen- oder Spaltennamen variabel wären.
    Und dann wären oft unterschiedlichen Prozeduren günstiger.

    Zudem vermute ich. dass die Parameter der Prozedur zum Teil vom Typ integer bzw. DateTime sind.
    Für eine zuverlässigen Übergabe solltest Du die Parameter entsprechend der Tabellendefinition deklarieren -
    nur so bemerkst Du Fehler frühzeitig.

    Ansonsten treten Fehler erst bei der Ausführung der Prozedur auf entweder beim Zusammenbau oder während des sp_executesql.
    Andere Dinge wie SQL Injection und Probleme mit der Rechtevergabe mal außen vorgelassen.

    Für eine Flusssteuerung kann man nun über die Auswertung von @@ERROR
    oder aber ab SQL Server 2005 auch über BEGIN TRY arbeiten
    Mehr dazu siehe  Verwenden von TRY...CATCH in Transact-SQL

    Dein Code - mit einigen Annahmen - zunächst mit manueller Fehlerbehandlung:

    CREATE PROCEDURE [dbo].[mySP]
    	@OK1 bit,
    	@OK2 bit,
    	@OK3 bit,
    	@Gehr  nvarchar(50),
    	@ID_S  nvarchar(50),	-- oder doch eher int???
    	@ID_V  nvarchar(50),
    	@ID_D  nvarchar(50),
    	@erfassungsdatum nvarchar(30)	-- besser evtl. DateTime(2) oder Date???
    AS
    	SET NOCOUNT ON;	
    
    	DECLARE @Err int;
    
    	BEGIN TRANSACTION;
    	IF @OK1 = 1
    	BEGIN
    		INSERT INTO Tab1 (ID_S, Status, Erfassungsdatum) 
    		VALUES (@ID_S, 5, + @erfassungsdatum);
    		SET @Err = @@ERROR;
    		IF @Err = 0
    		BEGIN
    		  INSERT INTO Tab1_Daten (ID_S, Wert1, archiv) 
    			VALUES (@ID_S, 1, 0);
    			SET @Err = @@ERROR;
    		END
    		IF @Err <> 0 
    		BEGIN
    			IF @@TRANCOUNT > 0 ROLLBACK;
    			RETURN 1; -- besser immer: 1 Fehler, 0 OK 
    		END
    	END ELSE BEGIN
    	  SELECT ID FROM Tab1_Daten WHERE archiv = 0 AND ID_S = @ID_S;
    	END
    
    	COMMIT;
    	RETURN 0; -- OK
     GO
    
    

    und wenn das lässt sich mit BEGIN TRY weiter vereinfachen:

    CREATE PROCEDURE [dbo].[mySP]
    	@OK1 bit,
    	@OK2 bit,
    	@OK3 bit,
    	@Gehr  nvarchar(50),
    	@ID_S  nvarchar(50),	-- oder doch eher int???
    	@ID_V  nvarchar(50),
    	@ID_D  nvarchar(50),
    	@erfassungsdatum nvarchar(30)	-- besser evtl. DateTime(2) oder Date???
    AS
    	SET NOCOUNT ON;	
    
    	BEGIN TRY
    		BEGIN TRANSACTION;
    	
    		IF @OK1 = 1
    		BEGIN
    			INSERT INTO Tab1 (ID_S, Status, Erfassungsdatum) 
    			VALUES (@ID_S, 5, + @erfassungsdatum);
    
    		  INSERT INTO Tab1_Daten (ID_S, Wert1, archiv) 
    			VALUES (@ID_S, 1, 0);
    		END ELSE BEGIN
    			SELECT ID FROM Tab1_Daten WHERE archiv = 0 AND ID_S = @ID_S;
    		END
    		COMMIT;
    		RETURN 0;
    	END TRY
    	BEGIN CATCH
    		IF @@TRANCOUNT > 0 ROLLBACK;
    		RETURN 1 -- besser immer: 1 Fehler, 0 OK 
    	END CATCH
     GO
    

    Dazu die (Pseudo-)Tabellen und der (rudimentäre) Testcode, die ich verwendet habe:

    USE tempdb;
    GO
    
    CREATE TABLE Tab1 (
    	ID_S int NOT NULL PRIMARY KEY,
    	Status int NOT NULL,
    	Erfassungsdatum Datetime);
    
    CREATE TABLE Tab1_Daten (
    	ID int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
    	ID_S int NOT NULL,
    	Wert1 int NOT NULL,
    	Archiv int NOT NULL);
    GO
    
    
    /*** Hier das jeweilige CREATE PROC einfügen ***/
    
     -- Testcode
     DECLARE @rc int
    EXEC @rc = dbo.mySP 
    	@OK1 = 1,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4711,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = '20110609';
    SELECT @rc;
    
    EXEC @rc = dbo.mySP 
    	@OK1 = 1,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4712,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = '20110609';
    SELECT @rc;
    
    EXEC @rc = dbo.mySP 
    	@OK1 = 0,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4711,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = NULL;
    SELECT @rc
    GO
    
    -- Aufräumen
    DROP PROC mySP;
    GO
    DROP TABLE Tab1, Tab1_Daten
    GO
    
    

    Wie Du sehen solltest, wird insbesondere im Falle mit BEGIN TRY das Ganze deutlich übersichtlicher,
    insbesondere wenn noch zwei weitere Tabellen ähnlich abgehandelt werden.

    Gruß Elmar

    Donnerstag, 9. Juni 2011 21:59
    Beantworter
  • Hallo,
    jetzt hat es geklappt!
    hier mein Construct:

    USE [myDB]
    GO
    /****** Object:  StoredProcedure [dbo].[mySP]    Script Date: 06/10/2011 08:58:09 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO

    Create PROCEDURE [dbo].[mySP]
     @SOK   bit,
     @VOK   bit,
     @DOK   bit,
     @ID_S  float,
     @ID_V  float,
     @ID_D  float,
     @erfassungsdatum Datetime
    AS
     SET NOCOUNT ON; 

     BEGIN TRANSACTION;

     -- Komponente 1
     IF @SOK = 1
      BEGIN TRY
       Insert into ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH

     -- Komponente 2
     IF @VOK = 1
      BEGIN TRY
       Insert ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     
     -- Komponente 3
     IF @DOK = 1
      BEGIN TRY
       Insert into ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     
     Commit
     return 0

    Was denkst du, ist das soweit OK?
    Fehler werden jetzt korrekt abgefangen.

    Gruß Andreas

    Freitag, 10. Juni 2011 08:45

Alle Antworten

  • Hallo Andreas,

    etwas mehr zum Kontext. in dem die Anweisung ausgeführt werden soll, wäre hilfreich.

    Willst Du die Anweisung alleine ausführen, wäre das über INSERT EXEC möglich:

    USE tempdb;
    
    CREATE TABLE Tabelle (id int IDENTITY(1, 1) NOT NULL);
    GO
    CREATE TABLE #temp (id int);
    DECLARE @sql nvarchar(max) = N'SELECT @@IDENTITY AS ID';
    
    INSERT INTO Tabelle DEFAULT VALUES;
    
    INSERT INTO #temp EXEC sp_executesql @sql;
    
    SELECT * FROM #temp;
    GO
    
    DROP TABLE #temp;
    DROP TABLE Tabelle;
    GO
    
    

    Beachte dass das nur funktioniert, wenn dazwischen keine weiteren Einfügevorgänge passieren.

    Gruß Elmar

    Donnerstag, 9. Juni 2011 20:08
    Beantworter
  • Hallo Elmar,

    ich bin gerade dabei das per Transaktion zu lösen, hier mein Code:

    USE [myDB]
    GO
    /****** Object:  StoredProcedure [dbo].[mySP]    Script Date: 06/09/2011 19:42:15 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO

    Create PROCEDURE [dbo].[mySP]
     @OK1 bit,
     @OK2 bit,
     @OK3 bit,
     @Gehr    nvarchar(50),
     @ID_S    nvarchar(50),
     @ID_V    nvarchar(50),
     @ID_D    nvarchar(50),
     @erfassungsdatum nvarchar(30)
    AS
     DECLARE @sql  nvarchar(max)
     DECLARE @rc   int
     DECLARE @NOK  int

     -- start the transaction
     BEGIN TRANSACTION

     If @OK1=1
      begin
       SET @sql = N'Insert into Tab1 (ID_S, Status, Erfassungsdatum) Values (' + @ID_S + ', 5, ''' + @erfassungsdatum + ''')'
       EXEC @rc = sp_executesql @sql
       if @rc <> 0
        begin
         SET @sql = N'Insert into Tab1_Daten (ID_S, Wert1, archiv) Values (' + @ID_S + ', 1, 0)'
         EXEC @rc = sp_executesql @sql
        end
      End
        Else
      begin
       SET @sql = N'Select ID From Tab1_Daten Where archiv = 0 AND ID_S = ' + @ID_S
       EXEC @rc = sp_executesql @sql
      End
      
     IF @@ERROR <> 0 OR @rc <> 0
      BEGIN
       -- Rollback the transaction
       ROLLBACK
       Return 99
      END

     -- obigen Block noch 2mal mit anderen Tabellen
     -- ...

     -- Commit the transaction
     COMMIT  

     RETURN

     

    ich habe den Code 1x durchlaufen und bekam den Return 0. Beim 2. mal mit gleichen Werten hoffte ich dass @@Error greift, aber statt dessen kam die Fehlermeldung:

    "Verletzung der PRIMARY KEY-Einschrnkung"

    Woran liegt das?

     

    Gruß Andreas

    Donnerstag, 9. Juni 2011 20:39
  • Hallo Andreas,

    Die Fehlermeldung wird immer (an den Client) zurückgegeben, @@ERROR wird nur zusätzlich gesetzt.

    Außerdem gilt @@ERROR (und auch @@ROWCOUNT) werden durch jede Anweisung neu gesetzt,
    deswegen musst Du sie sofort nach der Ausführung sichern, wenn Du später darauf zurückgreifen willst.

    Mehr siehe http://www.sommarskog.se/error_handling_2005.html ff.

    Das wäre aber der kleinere Teil.

    Wenn Du nicht was Wesentliches ausgelassen hast, ist jedoch sp_executesql hier überflüssig.

    Man kann Parmeter direkt verwenden, der Einsatz von sp_executesql wäre nur notwendig,
    wenn dort Teile wie Tabellen- oder Spaltennamen variabel wären.
    Und dann wären oft unterschiedlichen Prozeduren günstiger.

    Zudem vermute ich. dass die Parameter der Prozedur zum Teil vom Typ integer bzw. DateTime sind.
    Für eine zuverlässigen Übergabe solltest Du die Parameter entsprechend der Tabellendefinition deklarieren -
    nur so bemerkst Du Fehler frühzeitig.

    Ansonsten treten Fehler erst bei der Ausführung der Prozedur auf entweder beim Zusammenbau oder während des sp_executesql.
    Andere Dinge wie SQL Injection und Probleme mit der Rechtevergabe mal außen vorgelassen.

    Für eine Flusssteuerung kann man nun über die Auswertung von @@ERROR
    oder aber ab SQL Server 2005 auch über BEGIN TRY arbeiten
    Mehr dazu siehe  Verwenden von TRY...CATCH in Transact-SQL

    Dein Code - mit einigen Annahmen - zunächst mit manueller Fehlerbehandlung:

    CREATE PROCEDURE [dbo].[mySP]
    	@OK1 bit,
    	@OK2 bit,
    	@OK3 bit,
    	@Gehr  nvarchar(50),
    	@ID_S  nvarchar(50),	-- oder doch eher int???
    	@ID_V  nvarchar(50),
    	@ID_D  nvarchar(50),
    	@erfassungsdatum nvarchar(30)	-- besser evtl. DateTime(2) oder Date???
    AS
    	SET NOCOUNT ON;	
    
    	DECLARE @Err int;
    
    	BEGIN TRANSACTION;
    	IF @OK1 = 1
    	BEGIN
    		INSERT INTO Tab1 (ID_S, Status, Erfassungsdatum) 
    		VALUES (@ID_S, 5, + @erfassungsdatum);
    		SET @Err = @@ERROR;
    		IF @Err = 0
    		BEGIN
    		  INSERT INTO Tab1_Daten (ID_S, Wert1, archiv) 
    			VALUES (@ID_S, 1, 0);
    			SET @Err = @@ERROR;
    		END
    		IF @Err <> 0 
    		BEGIN
    			IF @@TRANCOUNT > 0 ROLLBACK;
    			RETURN 1; -- besser immer: 1 Fehler, 0 OK 
    		END
    	END ELSE BEGIN
    	  SELECT ID FROM Tab1_Daten WHERE archiv = 0 AND ID_S = @ID_S;
    	END
    
    	COMMIT;
    	RETURN 0; -- OK
     GO
    
    

    und wenn das lässt sich mit BEGIN TRY weiter vereinfachen:

    CREATE PROCEDURE [dbo].[mySP]
    	@OK1 bit,
    	@OK2 bit,
    	@OK3 bit,
    	@Gehr  nvarchar(50),
    	@ID_S  nvarchar(50),	-- oder doch eher int???
    	@ID_V  nvarchar(50),
    	@ID_D  nvarchar(50),
    	@erfassungsdatum nvarchar(30)	-- besser evtl. DateTime(2) oder Date???
    AS
    	SET NOCOUNT ON;	
    
    	BEGIN TRY
    		BEGIN TRANSACTION;
    	
    		IF @OK1 = 1
    		BEGIN
    			INSERT INTO Tab1 (ID_S, Status, Erfassungsdatum) 
    			VALUES (@ID_S, 5, + @erfassungsdatum);
    
    		  INSERT INTO Tab1_Daten (ID_S, Wert1, archiv) 
    			VALUES (@ID_S, 1, 0);
    		END ELSE BEGIN
    			SELECT ID FROM Tab1_Daten WHERE archiv = 0 AND ID_S = @ID_S;
    		END
    		COMMIT;
    		RETURN 0;
    	END TRY
    	BEGIN CATCH
    		IF @@TRANCOUNT > 0 ROLLBACK;
    		RETURN 1 -- besser immer: 1 Fehler, 0 OK 
    	END CATCH
     GO
    

    Dazu die (Pseudo-)Tabellen und der (rudimentäre) Testcode, die ich verwendet habe:

    USE tempdb;
    GO
    
    CREATE TABLE Tab1 (
    	ID_S int NOT NULL PRIMARY KEY,
    	Status int NOT NULL,
    	Erfassungsdatum Datetime);
    
    CREATE TABLE Tab1_Daten (
    	ID int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
    	ID_S int NOT NULL,
    	Wert1 int NOT NULL,
    	Archiv int NOT NULL);
    GO
    
    
    /*** Hier das jeweilige CREATE PROC einfügen ***/
    
     -- Testcode
     DECLARE @rc int
    EXEC @rc = dbo.mySP 
    	@OK1 = 1,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4711,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = '20110609';
    SELECT @rc;
    
    EXEC @rc = dbo.mySP 
    	@OK1 = 1,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4712,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = '20110609';
    SELECT @rc;
    
    EXEC @rc = dbo.mySP 
    	@OK1 = 0,
    	@OK2 = 0,
    	@OK3 = 0,
    	@Gehr = '',
    	@ID_S = 4711,
    	@ID_V = NULL,
    	@ID_D = NULL,
    	@erfassungsdatum = NULL;
    SELECT @rc
    GO
    
    -- Aufräumen
    DROP PROC mySP;
    GO
    DROP TABLE Tab1, Tab1_Daten
    GO
    
    

    Wie Du sehen solltest, wird insbesondere im Falle mit BEGIN TRY das Ganze deutlich übersichtlicher,
    insbesondere wenn noch zwei weitere Tabellen ähnlich abgehandelt werden.

    Gruß Elmar

    Donnerstag, 9. Juni 2011 21:59
    Beantworter
  • Hallo Elmar,

    ich bin noch in der Lernphase, daher meine Fehler.

    Was ich machen möchte:

    Daten in insgesamt 6 Tabellen speichern. Wenn das irgendwie schief läuft, also dass nichts gespeichert wurde oder bei Fehler soll ein RollBack erfolgen.

    Die ID-Werte sind 10-Stellig, könnten aber auch mal 12 Stellig werden wenn der Kunde sich dazu entscheidet. Diese Felder haben den Felddatentyp Float - erfassungsdatum hat Datentyp Datetime.

    Diese SP kommt ursprünglich aus Access als ModulCode, daher der für dich vieleicht ungewöhnliche Aufbau.

    Muss mir jetzt erstmal genau dein Beispiel ansehen, wenn ich noch Fragen habe melde ich mich wieder.

    Gruß Andreas

     

    Freitag, 10. Juni 2011 07:39
  • Hallo,
    jetzt hat es geklappt!
    hier mein Construct:

    USE [myDB]
    GO
    /****** Object:  StoredProcedure [dbo].[mySP]    Script Date: 06/10/2011 08:58:09 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO

    Create PROCEDURE [dbo].[mySP]
     @SOK   bit,
     @VOK   bit,
     @DOK   bit,
     @ID_S  float,
     @ID_V  float,
     @ID_D  float,
     @erfassungsdatum Datetime
    AS
     SET NOCOUNT ON; 

     BEGIN TRANSACTION;

     -- Komponente 1
     IF @SOK = 1
      BEGIN TRY
       Insert into ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH

     -- Komponente 2
     IF @VOK = 1
      BEGIN TRY
       Insert ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     
     -- Komponente 3
     IF @DOK = 1
      BEGIN TRY
       Insert into ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     ELSE
      BEGIN TRY
       Select ...
      END TRY
      BEGIN CATCH
       IF @@TRANCOUNT > 0 ROLLBACK;
       RETURN 1
      END CATCH
     
     Commit
     return 0

    Was denkst du, ist das soweit OK?
    Fehler werden jetzt korrekt abgefangen.

    Gruß Andreas

    Freitag, 10. Juni 2011 08:45
  • Hallo Andreas,

    grundsätzlich ist das schon OK.

    Wenn das Ganze nur in der Gesamtheit funktionieren soll, so würde es auch reichen,
    mit einem BEGIN TRY ... BEGIN CATCH Block zu arbeiten. Das verkürzt den Code
    und erhöht die Übersichtlichkeit - was immer gut ist.

    Das COMMIT würde ich vor das END TRY legen, denn auch wenn es anders nicht erreicht
    werden kann, wird dadurch die Absicht klarer (und auch COMMIT kann schief gehen -
    wenn auch nur bei größeren Problemen - Protokoll voll, defekt).

    Gruß Elmar

    Freitag, 10. Juni 2011 14:52
    Beantworter
  • Hallo Andreas,

    ich habe auch mal länger mit Access gearbeitet  - und war auch mal Access Client Server MVP 2001-3
    bis ich mich auf die Seite des SQL Servers (und .NET) geschlagen habe.

    Einige Tipps:
    Du solltest besser decimal als Datentyp einsetzen.
    Bei kaufmännischen Rechnungen hat es zum einen den Vorteil arithmethisch zu runden.
    Die Jet kann dabei nur mit bis zu 28 Stellen (SQL Server 38) genau arbeiten:
    INFO: Verbesserte ODBC-Datentyp Zuordnungen mit Jet 4.0

    Aber auch für Schlüsselfelder ist das gesünder, da in Verbindung
    mit Access sonst schnell der "#deleted" Fehler auftritt:
    Fehler: "#Gelöscht" bei verbundenen ODBC-Tabellen

    was an der Natur von Gleitkommazahlen liegt.

    Und weil der Betreff "@@IDENTITY" enthält:
    Bei SQL Server verwendet man üblicherweise SCOPE_IDENTITY(),
    was den Vorteil hat, auch in Verbindung mit Triggern usw. richtig zu funktionieren.
    (Im Falle von sp_executesql gälte, dass es ein eigener Gültigkeitsbereich ist -
    aber das sollte jetzt nicht notwendig sein).

    Gruß Elmar

    Freitag, 10. Juni 2011 15:05
    Beantworter