begin try in SQL-Batch-Statements

Es kommt öfter vor, dass man im Batch mehrere SQL-Statements ausführen möchte, sei es in einer Stored Procedure oder im Execute SQL-Task von SSIS.

Meistens hat man folgende Anforderung:

Läuft ein Statement auf einen Fehler, soll ein Rollback der Statements gemacht werden. Außerdem soll natürlich dem aufrufenden System der Fehler gemeldet werden.

Lässt man einen Batch einfach so laufen, wird dieses Ziel nicht erreicht, da im Fehlerfall auch die Statements nach dem Statement, das den Fehler verursacht, ausgeführt werden.

Beispiel:

set nocount on
select 1
select 1/0
select 2

liefert:

———–
1

———–
Msg 8134, Level 16, State 1, Line 3
Divide by zero error encountered.

———–
2

In Versionen vor SQL Server 2005 musste man die Error-Variable auslesen, etwa so:

set nocount on
declare @fehler as int
set @fehler = 0
select 1
set @fehler = @fehler + @@error
select 1/0
set @fehler = @fehler + @@error
select 2
set @fehler = @fehler + @@error
if @fehler>0 begin
print ‚Ein Fehler ist aufgetreten‘
end

was folgendes Ergebnis liefert:

———–
1

———–
Msg 8134, Level 16, State 1, Line 6
Divide by zero error encountered.

———–
2

Ein Fehler ist aufgetreten

Das Problem ist, man muss die Zeile „set @fehler = @fehler + @@error“ nach jedem Statement schreiben, da sie nach jedem (!) Statement zurückgesetzt wird.

Leichter geht das in SQL 2005 mit begin try … end try – angelehnt an Konstrukte aus Programmiersprachen wie C#:

set nocount on
begin try

select 1
select 1/0
select 2

end try
begin catch
print ‚Ein Fehler aufgetreten‘
end catch

Am Ergebnis

———–
1

———–

Ein Fehler aufgetreten

sieht man, dass nach dem fehlerhaften Statement die Bearbeitung beendet wird.

Nun fehlen nur noch 2 Anforderungen:

Dass keine Datenmanipulation statt findet, erreicht man über eine Transaktion, die im catch-Block zurückgerollt (rollback) wird.

Dass der Aufruf dennoch den Fehler mitbekommt, erreicht man über einen raiserror.

Das fertige Skript sieht dann so aus:

set nocount on

begin tran
begin try

/* hier die eigentlichen SQL-Statements schreiben */
select 1
select 1/0
select 2

end try
begin catch
if @@trancount > 0 begin
rollback tran
end
declare @fehler_text nvarchar(4000)
set @fehler_text = ERROR_MESSAGE()
declare @fehler_severity int
set @fehler_severity = ERROR_SEVERITY()
declare @fehler_state int
set @fehler_state = ERROR_STATE()
RAISERROR (@fehler_text, — Message text.
@fehler_severity, — Severity.
@fehler_state — State.
)

end catch
if @@trancount > 0 begin
commit tran
end

Measure-Meta-Informationen des Cube auslesen: AMO oder ADOMD

Um an die Meta-Informationen des Cubes heranzukommen, gibt es mehrere Zugriffsmöglichkeiten, einmal mit AMO (Analysis Services Management Objects) oder ADOMD (das hauptsächlich für die Ausführung von MDX-Abfragen verwendet wird).

Beigefügt habe ich eine C#-Sollution, die über ADOMD eine CSV-Datei mit allen Measures erzeugt.

Wenn Zeit ist, werde ich diesen Artikel später noch erklären. Vorerst nur soviel:

Der Versuch mit ADOMD auf die Daten zuzugreifen, ist gescheitert, weil dort die berechneten Measures, die ich ebenfalls dokumentieren wollte, nicht einzeln abfragbar sind. Ich habe in einem Blogeintrag von letztem Oktober bereits beschrieben, wie man mit AMO an die MDX-Skripte herankommt. In diesen Skripten gibt es auch CREATE MEMBER-Skripte, die dann durch den Cube in berechnete Measures umgesetzt werden. Wenn man also AMO verwenden will, müsste man diese Skripte parsen (hierzu ein interessanter Blog-Eintrag auf geekswithblogs.net). Wenn man nur an dem Inhalt interessiert ist und die berechneten Measures nicht ändern will, kann man aber darauf getrost verzichten und – wie ich auch in der beigefügten Sollution gemacht habe – stattdessen ADOMD verwenden.

Berechnete Elemente in MDX-Abfragen mit Anwendung in Reporting Services 2005

Lange habe ich gesucht, um ein sinnvolles Beispiel für ein berechnetes Dimensions-Element zu finden (berechnete Measures sind ja ständig zu finden).

Ein Beispiel ist eine Matrix in Reporting Services 2005 (in 2008 ist mit der Tablix ja alles (bzw. vieles) besser).

Ich habe über das Problem bereits zwei Artikel auf sqlservercentral.com veröffentlicht (Reporting Services: Adding extra columns / rows to a matrix und Reporting Services: Read Data from SSAS and SQL Server in One Dataset). Heute möchte ich das Problem auf eine andere Weise lösen.

Nehmen wir an, wir wollen in den Spalten alle Monate sehen und die Summe und einen Planwert und die Differenz zwischen Summe und Planwert. Wenn wir es jetzt schaffen, das ganze in einem MDX-Statement zu laden, so kann auch die Matrix von SQL Server Reporting Services 2005 das anzeigen.

Alle Monate in MDX anzuzeigen, ist einfach. Das Beispiel zeigt je Produkt und Monat den Verkaufswert an (ein konstruiertes, vereinfachtes Beispiel – ich habe sogar auf die Monatsnamen verzichtet und zeige die Monate im Format JJJJMM an):

select non empty [Datum].[Monat].[Monat].members on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate mal Produkte

Die Spalte Summe geht auch einfach. Dazu müssen wir den Alle-Member der Attribut-Hierarchie Monat anzeigen:

select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle Mal Produkt

Für den Planwert konstruieren wir ein einfache Element der Attribut-Hierarchie Monat, das konstant 10 ist:

with member [Datum].[Monat].[Plan] as 10
select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All], [Datum].[Monat].[Plan]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle und Plan mal Produkte

(Die unschöne unbekannt-Zeile kann man natürlich leicht loswerden, aber das ist hier nicht unser Thema)

Das war noch eine leichte Übung. Aber in diesen Berechnungen können wir auch rechnen, wie in jedem MDX. Somit können wir einfach die Differenz anzeigen:

with member [Datum].[Monat].[Plan] as 10
member [Datum].[Monat].[Differenz] as [Datum].[Monat].[All] – [Datum].[Monat].[Plan]
select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All], [Datum].[Monat].[Plan], [Datum].[Monat].[Differenz]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle, Plan, Differenz Mal Produkte

Das finde ich ein sehr schönes Beispiel, wie man berechnete Dimensions-Elemente einsetzt.

Um das ganze in Reporting Services zu verwenden, muss man alles natürlich auf die Zeilen bringen und nur das Measure in den Spalten haben – aber das ist trivial.

Außerdem muss man in der Matrix noch die Sortierung lösen, aber über iif-Berechnungen in dem Sortierungsfeld ist das auch leicht zu lösen.