Benutzer mit den meisten Antworten
SET @sql = N'SELECT @@IDENTITY AS ID' im SQLServer

Frage
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-SQLDein 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
- Als Antwort markiert Robert BreitenhoferModerator Donnerstag, 23. Juni 2011 15:28
-
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
GOCreate 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 0Was denkst du, ist das soweit OK?
Fehler werden jetzt korrekt abgefangen.Gruß Andreas
- Als Antwort markiert Robert BreitenhoferModerator Donnerstag, 23. Juni 2011 15:28
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
-
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
GOCreate 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 TRANSACTIONIf @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
COMMITRETURN
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
-
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-SQLDein 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
- Als Antwort markiert Robert BreitenhoferModerator Donnerstag, 23. Juni 2011 15:28
-
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
-
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
GOCreate 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 0Was denkst du, ist das soweit OK?
Fehler werden jetzt korrekt abgefangen.Gruß Andreas
- Als Antwort markiert Robert BreitenhoferModerator Donnerstag, 23. Juni 2011 15:28
-
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
-
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.0Aber 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