Quantcast
Viewing all 110 articles
Browse latest View live

Von Anfang an – Startup Script Für SQL Server

Sie wollen nach Start des SQL Server ein bestimmtes SQL Script laufen lassen, um etwa den Zeitpunkt des Starts zu protokollieren oder etwa Prüfabfragen zu starten und zu protokollieren? Dann sind Prozeduren, die automatisch nach Start des SQL Servers Dienstes gestartet werden genau das Richtige.

Vergessen Sie Startparameter oder ähnliches. Es gibt extra eine Prozedur, die wiederum andere Prozeduren für den Startup registriert: SP_PROCOPTION.

Die Syntax dazu ist sehr einfach:

USE MASTER; GOEXEC SP_PROCOPTION @ProcName=’NameDerProzedur’, @OptionName='STARTUP', @OPtionValue='ON'; GOEXEC SP_PROCOPTION NameDerProzedur, 'STARTUP', 'ON'; GO



Nun gibts natürlich ein paar Bedingungen dazu:

  • Die Prozedur kann nur solche Prozeduren registrieren, die in der master Datenbank enthalten sind.
  • Die registrierten Prozeduren werden erst dann gestartet, wenn alle Datenbanken ebenfalls widerhergestellt wurden. (wir würden das Laufen nennen)
  • Da die Datenbanken bereits laufen, lassen sich Protokollierungen anstatt in die master natürlich jederzeit auch in eine andere Datenbank protokollieren.
  • Es können mehrere Prozeduren unabhängig registriert werden. Über die Reihenfolge hat man dann allerdings keine Kontrolle.
  • Man muss Sysadmin sein, um Prozeduren registrieren zu können.
  • Die Prozeduren werden logischerweise weder Inputparameter noch Outputparameter besitzten.

Die so registrierten Prozeduren lassen sich natürlich auch wieder deregistrieren:

USE MASTER;
GO

EXEC SP_PROCOPTION NameDerProzedur, @OptionValue 'OFF';
GO

So weit so gut. Die Ideen für was man das verwenden könnte sind manigfaltig. Vom Versenden von Mails an Datenbankbesitzer, dass die Datenbank wieder läuft, bis hin zu Systemauswertungen ist alles machbar. Hier also nur ein kleines Beispiel, wie man den Start des SQL Server protokollieren könnte:

 

 

Createdatabase Logging; GOuse Logging; GOcreateTable Alerts (id intidentity, Kommentar varchar(4000)default'Server Omega starting', Zeit smalldatetimedefaultgetdate()); GOUse master; GOCreateprocedure dbo.ServerLogging asSet NoCount oninsertinto logging.dbo.Alerts (Kommentar) values (@input) GOCreateprocedure dbo.ServerLogging2 asSet NoCount oninsertinto logging.dbo.Alerts (Kommentar) values (‘****SERVERNEUSTART OMEGA*****’) GO--TEST exec dbo.ServerLogging exec dbo.ServerLogging2 select*from logging.dbo.Alerts--Registrierung Use master; GOEXEC SP_PROCOPTION ServerLogging, 'STARTUP', 'ON'GOEXEC SP_PROCOPTION ServerLogging2, 'STARTUP', 'ON'GO--Neustart des Server !!net stop mssql$OMEGA /Y !!net start mssql$Omega--Prüfen, ob erfolgreich select*from logging.dbo.Alerts

 

Image may be NSFW.
Clik here to view.
image

 

Würde man eine Prozedur registrieren,  bei der die Bedingungen nicht erfüllt sind, würde das so aussehen:

Image may be NSFW.
Clik here to view.
image

 

Viel Spaß damit Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley


Hochzählen mal anders

In diesem Artikel geht es um Sequenzen, eine gute Alternative für den sonst üblichen Identity.

Was ist ein Sequenz?
Wer im Besitz eines SQL-Servers der Version 2012 oder höher ist, könnte sich mal überlegen ob in seiner Datenbank nicht Platz für die ein oder andere Sequenz ist. Man kann sich eine Sequenz wie die Identity-Eigenschaft vorstellen, jedoch kann man den inkrementellen Wert auf unzählig viele Tabellen anwenden, da die Sequenz keine Spalteneigenschaft ist sondern ein selbständiges Objekt das angesprochen werden kann. Zu vergleichen ist es mit einem “globalen” Counter. Alle Tabellen bei denen die Sequenz verwendet wird teilen sich die Wert. Das ist jedoch nicht alles, es besteht z.B. die Möglichkeit mit einer Sequenz einen zirkulierenden Wertebereich immer und immer wieder zu durchlaufen. Es gibt einen weiteren Unterschied zur Identity-Eigenschaft der ab und an sicherlich nützlich sein kann. Und zwar besteht die Möglichkeit die Sequenznummer ohne einen Einfüge Vorgang zu überspringen.

Definition einer Sequenz
Nun zum Syntax der so gut wie selbsterklärend.

CreateSequence TestSeq
Startwith 0
Increment
by 5


Falls man es noch nicht selber herausgefunden hat in welchen Bereich sich die Sequenz bewegt hier die kurze Erklärung. Das “Create Sequence” wird mit dem Startwert “0” erstellt und erhöht sich immer um fünf.  Bei dieser Definition kann der Wert bis auf den Maximum-Wert von Bigint gebracht werden. Nämlich 2^64 = 9223372036854775807.

Die Sequenz ist erstellt, aber wie verwende ich sie nun?

Selectnextvaluefor TestSeq


Die Ausgabe zeigt nun das Ergebnis der Select-Anweisung drei mal hintereinander.

Image may be NSFW.
Clik here to view.
Seq_ergebnis

Bis jetzt haben wir den aktuellen Sequenzwert nur ausgegeben doch jetzt wollen wir ihn in einer Tabelle einfügen. Statement schaut wie folgt aus.

Insert
into TestTab
Selectnextvaluefor TestSeq


Diese Insert-Statement habe ich auch drei mal ausgeführt. Das Ergebnis der Tabelle TestTab sieht so aus.

Image may be NSFW.
Clik here to view.
Seq_tab_erg

Und das war eigentlich schon der ganze Zauber, also fast.

Nun kommen noch ein paar Möglichkeiten wie man eine Sequenz erstellen kann.

1: CreateSequence CycSeq
2:    asDecimal(10,0)
3:    Startwith 120
4:    Increment by 30
5:    Minvalue 90
6:    Maxvalue  300
7:    Cycle
8:    Cache 5


Diesmal eine etwas ausgefallenere Sequenz-Definition. In der zweiten Zeile habe ich einen Datentyp angegeben, hier kann man alle Ganzzahl-Datentypen angeben und Numeric & Decimal nur mit einer Skala von 0. Je nachdem welchem Datentyp ich hier verwende, wird natürlich auch die Obergrenze des Bereiches festgelegt. In Zeile 5 habe ich einen Minimum-Wert gesetzt da es ein immer wiederholender Bereich ist. Logischerweise muss der Start-Wert im Bereich zwischen Min- und Maxvalue liegen (Sonst Fehler). Das Cycle in der vorletzten Zeile beschreibt nichts anderes als das die Sequenz sich immer wiederholt. Sobald der Maxwert erreicht wurde wird mit dem Minwert weitergemacht nicht mit dem Startwert!

Die letzte Zeile beschreibt die Cache-Größe der Sequenz. Was heißt das für uns? Ob der Cache nun gesetzt wird oder nicht kann einen großen Effekt auf die Performance haben. Die Cache-Option macht nichts anderes als kommende Sequenzwerte bereits in die Speicher zuladen und zwar genau so viele Werte wie der angegebene Cache groß ist, in unserem Fall fünf Werte. Nachteil des ganzen“gecaches” ist das Lücken in der Sequenz entstehen können, da die fünf kommenden Werte in den Arbeitsspeicher geladen werden und der nächste Wert der nach dem gecachten Werten kommen auf die Festplatte in eine Systemtabelle geschrieben werden. Kommt es nun zu einem Stromausfall sind alle gecachten Werte weg und der SQL-Server benutzt den Wert der in der Systemtabelle steht. Wer auf diese Risiko verzichten mag sollte anstelle von “Cache” einfach “no Cache” verwenden. Ein nichtangeben der Cache-Klausel bringt nichts da der SQL-Server einfach den Standardwert benutzt.

So viel zu Sequenzen…

P.S: Wer eine Sequenz neustarten will, sollte das so machen Image may be NSFW.
Clik here to view.
Smiley mit herausgestreckter Zunge

AlterSequence Testseq
Restart
with 0

Refactoring von Datenbanken

Refactoring von SourceCode ist mittlerweile weit verbreitet. Oft kommt es vor, dass sich die erste Bennenung von Variablen oder Funktionen als nicht ideal herausstellt. Dann beginnt das groß umbenennen im SourceCode. Zum Glück unterstützt das Visual Studio den Entwickler bei dieser Aufgabe.

SQL Data Tools

Seit Version 2010 des Visual Studio gibt es die Erweiterung “SQL Data Tools”.  Die SQL Data Tools (SSDT) können von der Microsoft Seite heruntergeladen werden (Link-MSDN) Mit Visual Studio 2013 erspart man sich den Download. Die SSDT ermöglichen Datenbankentwicklung im Team. D.h. mehrere Entwickler können gemeinsam an einem Datenbank Projekt arbeiten. Mehr dazu im Vortrag meinem Vortrag bei der VSONE (www.vsone.de) “Datenbankentwicklung im Team - Neue Möglichkeiten mit den SQL Data Tools”

Ein weiteres nützliches Feature ist das Umbenennen der Datenbank Objekte. Oder wissen Sie immer genau in welchen Views oder Stored Procedures , ein Tabellenname oder ein Feldname im SQL Code verwendet wurde?

Es ist gar nicht notwendig, das gesamte Datenbank Projekt in den SSDT entwickelt zu haben. Die SSDT können auch bei bestehenden Datenbank eingesetzt werden. Dazu muss zunächst die Datenbank importiert werden.

Als erstes wird ein neues Datenbankprojekt begonnen.

Image may be NSFW.
Clik here to view.
image

(FILE-NEW-SQL Server – SQL Server Database Project)

Im nächsten Schritt wird die Datenbank importiert.

Image may be NSFW.
Clik here to view.
image

“Projekt -Kontext Menu “- Import – Database

Im folgenden Dialog noch die Datenbankverbindung angeben:

Image may be NSFW.
Clik here to view.
image

Für jedes importierte Datenbank-Objekt wird in der Solution eine SQL Datei angelegt. Als Inhalt steht jeweils das passende Create-Statement. Nach dem Import der Contos-Demo Datenbank sieht der Solution Explorer folgendermaßen aus:

Image may be NSFW.
Clik here to view.
image

Jede Tabelle und jeder View wird hier aufgelistet. Ein Schema, in diesem Fall “dbo” wird als Ordner der Solution Struktur angelegt.

Der View “V_CUstomerOrders wird wie folgt definiert und angezeigt. Die DataTools bieten keinen Editor für View Darstellung. Daher ist die Darstellung der SQL Text:

Image may be NSFW.
Clik here to view.
image

In dem View wird auf das Feld “YearlyIncome” der “Customer”-Tabelle verwiesen. Wenn nun die Aufgabe darin besteht in der Customer Tabelle das Feld umzubenennen, woher wissen Sie an welchen Stellen im SQL Text sie noch Änderungen vornehmen müssen?

Zur Tabellen-Bearbeitung bieten sie SSDT sogar einen Editor. Dieser ist im Bildschirm zweigeteilt. Oben der Editor und im unteren Bereich der SQL Text. Änderungen können an beiden Stellen vorgenommen werden. Aber das Refactoring ist nur im SQL Text implementiert.

Image may be NSFW.
Clik here to view.
image

Den Cursor auf das zu ändernde Feld stellen und im Kontext Menü “Refactor”  - “Rename” wählen.

Image may be NSFW.
Clik here to view.
image

Im Rename-Dialog noch den neuen Namen (YearlyNetIncome] eingeben und optional bestimmen ob die Änderungen angezeigt werden sollen:

Image may be NSFW.
Clik here to view.
image

Danach werden alle zu ändernden Stellen angezeigt. In meinem Fall sind die Views “V_CustomerPromotion, V_Customer, und V_CustomerOders zu ändern. Code der in StoreProcedures oder anderen Datenbank-Objekten enthalten ist, würde hier ebenfalls geändert werden.

Image may be NSFW.
Clik here to view.
image

 

Diese Code Änderungen wurden nun nur in den lokal gespeicherten SQL-Dateien durchgeführt- die Datenbank selbst ist noch nicht verändert. Über das Projekt Kontext Menü kann mit “Publish” die Datenbank am SQL Server selbst verändert werden.

Die SSDT vergleichen hierzu die lokalen SQL Dateien mit der Struktur am Server und erstellen im Publish-Vorgang ein SQL Skript das die Update-Statements enthält um die Server Version an die lokale Version anzupassen.

Fazit

Endlich haben Objektnamen-Änderungen in Datenbank Projekten den Schrecken verloren Image may be NSFW.
Clik here to view.
Smiley

Welcher ist heute? SQL-Datumsfunktionen

Das Datum ist was ganz besonderes was den SQL-Server angeht. Denn das ist nicht immer optimal gelöst Image may be NSFW.
Clik here to view.
Smiley mit herausgestreckter Zunge
. Deshalb stellt uns der SQL-Server eine Handvoll toller Funktionen zur Seite die uns das Arbeiten mit dem  “fantastischen” Datum behilflich sind.

Am besten wir schauen uns erst mal die Datentypen an, die wir bezüglich eines Datums verwenden können.

Datumsdatentypen
Date– Kann nur das Datum speichern (nur für SQL-Server 2008 oder höher nutzbar)
Time– Kann nur die Zeit speichern, Genauigkeit: 100ns  (nur für SQL-Server 2008 oder höher nutzbar)
Datetime– Kann Datum und Zeit speichern, Genauigkeit: 3ms
Datetime2– Kann Datum und Zeit speichern, Genauigkeit: 100ns
Smalldatetime– Kann Datum und Zeit speichern, Genauigkeit 1minute
Datetimeoffset– Kann die Zeit und die Zeitzone anzeigen, Genauigkeit 100ns (nur für SQL-Server 2008 oder höher)

Grundsätzlich sollte man natürlich nur den Datentyp benutzen den man auch wirklich braucht. Alle zusätzlich Abspeicherungsarten brauchen auch mehr Speicher.

Funktionen
GETDATE

Liefert das aktuelle Datum und Zeit zurück im Datetime-Format allerdings auf die Millisekunde genau. Es darf kein Parameter übergeben werden.

Select Getdate()

Ausgabe:

Image may be NSFW.
Clik here to view.
getdate

WICHTIG: Wenn jemand eine Abfrage starten will die z.B.: Alle Bestellungen des heutigen Tages abfragen soll, darf man nicht vergessen das Datum von Getdate() in ein Date-Datentyp zu casten/converten. Bei denjenigen die das nicht beachten sollten, wird die Chance sehr gering einen Datensatz zurück zubekommen. Denn schließlich muss die Bestellung zum gleichen Zeitpunkt (Millisekunden!) eingetragen werden wie die Abfrage ausgeführt wird.

DATEADD

Diese Funktion kann dazu benutzt werden um zu einem bestimmten Datum eine Anzahl von Tagen, Monaten usw. hinzuzufügen oder abzuziehen.
Wenn nun jemand das Datum in 35 Tagen wissen will, könnte er die Funktion Dateadd benutzen.
Allerdings fordert die Funktion drei Parameter. Dateadd(intervall, Anzahl, Datum).

Intervalle
               Hier muss festgelegt werden was ich zum Datum dazu addieren oder subtrahieren will.
               Hier eine “Liste” von alles Intervallen: d, day, dd, dayofyear, dw, dy, hh, hour, iso_week, isowk,
               isoww, m, mcs, mi, microsecond, millisecond, minute, mm, month, ms, n, nanosecond, ns, q, qq, 
               quarter, s, ss, tz, tzoffset, w, week, weekday, wk, ww, y, year, yy, yyyy
               Die meisten Intervalle sind selbsterklärend, um den Blogeintrag jetzt nicht unnötig in die Länge
               zu ziehen ist hier eine Übersicht über fast 
               alle Intervalle http://technet.microsoft.com/de-de/library/ms186819.aspx. Falls er nicht im Link
               zu finden ist Mail an MariusI@ppedv.de.

Anzahl
              
Hier Bedarf es glaub ich keiner großen Erklärung. An dieser Stell muss die Anzahl der Tage oder
               was auch immer für ein Intervall verwendet wurde, die man dazu addieren will.

Datum
               Dort muss das Datum angegeben werden zu dem das ganze hinzu gerechnet werden soll.

Select Dateadd(dd, 35, '27.01.2014')


Ergebnis:

Image may be NSFW.
Clik here to view.
dateadd

Um nun das Datum vor 35 Tagen herauszufinden muss man lediglich ein Minus vor die Anzahl der Tage machen.

DATEDIFF

Mit dieser Funktion ist es möglich Differenz zwischen zwei Daten zu bestimmen. In was die Differenz zurückgeben wird hängt vom übergebenen Intervall ab. Der Funktion müssen drei Parameter mitgegeben werden. Intervall, Erstes Datum und Zweites Datum.

Select Datediff(mm, Getdate(), '24.12.2014')

Ausgabe:

Image may be NSFW.
Clik here to view.
datediff

Wer gerne wissen möchte wie viele Monate es noch bis Weihnachten (2014) ist. Die Abbildung zeigt es. Hier können natürlich auch andere Intervalle angegeben werden.

DATENAME

Diese Funktion liefert den Namen eines Datums zurück. Macht allerdings nur mit zwei Intervallen: DW (Wochentag) und MM (Monat). Schließlich haben nur Monate und Wochentage Namen. Netter Nebeneffekt der Funktion ist das alles in Varchar um konvertiert wird.

Select Datename(DW, Getdate())

Ausgabe:

Image may be NSFW.
Clik here to view.
Datename

Heute ist der 27.01.2014 und es ist Montag (Ja es ist Montag und ich bin in der Lage ein Blogeintrag zu schreiben, manchmal verblüff ich mich selbst).

DATEPART

Mit dieser Funktion ist es möglich einen Teil eines bestehenden Datums zu extrahieren. Die Funktion erwartet zwei Parameter, Intervall und Datum. Der Intervall gibt hier an welcher Teil des Datums extrahiert werden soll.

Select Datepart(yy, Getdate())

Ergebnis:

Image may be NSFW.
Clik here to view.
datepart

Das Ergebnis ist denke ich selbsterklärend.
Alternative zur Datepart-Funktion kann man auch die Funktionen Day(), Month() oder Year() verwenden.
Denen muss man lediglich einen Parameter mitgeben und zwar das Datum.

SelectYear(Getdate())


Die Ausgabe ist die selbe.

Ich hoffe ich konnte dem ein oder anderen die Datumsfunktionen in SQL näherbringen.

Visual Studio 2013 SQL Abfrage Designer

Kurz gesagt- gibt es nicht. Mich wundert ein wenig, der fehlende #Aufschrei.

Das liegt vor allem daran weil der neue Visual Studio 2013 Query Designer ziemlich gut ist. Vor allem die Intellisense Unterstützung bringt Produktivität. Die Funktion findet sich im Server Explorer in dem man eine Verbindung zu einer Datenbank hinzufügt.

Image may be NSFW.
Clik here to view.
image

Auch das Ändern der Tabellenstruktur funktioniert einfach und vor allem transparent mit Anzeige der benötigten SQL Kommandos. So kann man diese kopieren und  ggf. auf Produktivservern reproduzieren.

Wenn es allerdings um Joins geht, wird es unter Umständen etwas anstrengend. Vermutlich geht Microsoft davon aus, das man ohnehin per EF und Datenmodell arbeitet und die schmutzigen Jobs den Datenbank DBA überlässt.

Der wiederum verwendet SQL Management Studio 2012 und kann nach wie vor grafisch seine Relationen zusammen ziehen um z.B. eine neue Sicht anzulegen.

Image may be NSFW.
Clik here to view.
image

Soweit kann man das noch glaubhaft verkaufen. Auch wenn es ein “ich nehm Dir den Startbutton weg” Fettnäpfchen ist.

Verbindet man sich aber per OLEDB Provider zum SQL Server statt mit dem SQLClient

Image may be NSFW.
Clik here to view.
image

ist der Visual Studio Database Designer wieder da.

Image may be NSFW.
Clik here to view.
image

Wir haben ja Fasching, Karneval, Fastnacht. In diesem Sinne HELAU!

UnitTesting von T-SQL StoredProcedures

Die Sql Data Tools (SSDT) die ich bereits im Blog zum Refactoring gezeigt habe (Refactoring von Datenbanken)  bieten noch mehr Möglichkeiten um moderne Software Entwicklung auf T-SQL Code umzulegen. Um den Datenbankcode zu testen, finden wir im Visual Studio nun Unit Tests für Datenbankcode. Damit ist es möglich die Datenbankentwicklung so wie den restlichen Code, testgetrieben durchzuführen.

Zunächst muss die Datenbank in ein Visual Studio Projekt importiert werden. Dieser Schritt ist im oben genannten Blog Eintrag beschrieben. Sobald eine Datenbank als Projekt vorliegt, kann im Visual Studio über den SQL Server Object Explorer im Ordner “Projects” darauf zugegriffen werden.

Image may be NSFW.
Clik here to view.
image

Im Screenshot sind die Stored Procedures der Nordwind-Datenbank zu erkennen. Das Context-Menü einer SP bietet den Menü Punkt “Create Unit Tests….”

Image may be NSFW.
Clik here to view.
image

Der folgende Dialog, legt ein neues Testprojekt zusätzlich zum bereits angelegten Datenbankprojekt an. Achtung: die voreingestellte Sprache des Testprojektes ist VB.NET. Sollte diese Sprache einem Leser des Blogs nicht gefallen, man kann sie auch auf C# ändern. (Wer mich kennt, weiß welche Sprache ich wähle)

Image may be NSFW.
Clik here to view.
image

Als nächstes muss noch die Datenbank Verbindung für die Testläufe gesetzt werden. Entweder werden die Test mit einer bestehenden SQL Datenbank durchgeführt, oder das Projekt wird deployed und dieses als Testdatenbank herangezogen. Wichtig ist, dass als Server (localdb)\projects verwendet wird. die Angabe “.\projects” kann vom Visual Studio nicht aufgelöst werden.

Im Testprojekt wird somit eine neue Testklasse erstellt, die von der Testumgebung des Visual Studios wie ein herkömmlicher UnitTest behandelt wird. Die Besonderheit ist der Editor für T-SQL Code in dem der Test formuliert wird.

Der Test Editor besteht aus einem Editorfenster im oberen Teil und einem Bedingungenfenster im unteren Teil:

Image may be NSFW.
Clik here to view.
image

Mit den Bedingungen kann der Output des Testcodes validiert werden und diese entscheiden über Erfolg und Misserfolg des Tests. Es gibt 8 Möglichkeiten das SQL Ergebnis zu validieren. Ich entscheide mit für einen simplen Testfall, wo die Anzahl der zurückgegebenen Zeilen 3 betragen soll. Dies wird als “Test Condition” hinterlegt:

Image may be NSFW.
Clik here to view.
image

Über das Eigenschaften Fenster kann definiert werden, wieviele Zeilen in welchem Recordset erwartet werden.

Der Testcode sieht so aus, daraus ergibt sich, dass die Zeilen im Recordset 1 sein sollten.

-- database unit test for dbo.CustOrdersDetail
DECLARE @RC ASINT, @OrderID ASINT;
 
SELECT @RC = 0,
       @OrderID = 10881;
 
EXECUTE @RC = [dbo].[CustOrdersDetail] @OrderID;
 

Wenn wir nun den Test laufen lassen kommt es zum erwarteten negativen Ergebnis:

Image may be NSFW.
Clik here to view.
image

Das liegt daran, dass in der Datenbank, nachdem sie ausgeliefert wurde keine Daten enthalten sind.

Dafür gibt es die Möglichkeit SQL Skripts zu hinterlegen die entweder vor jedem Test durchgeführt werden, oder einmal an Beginn eines Testlaufes für mehrere Tests ausgeführt werden. Die Attribute sind wohl von Unit Tests bekannt [TestInitialze] oder [ClassInitialize]

Im Fall eines SQL Unit Tests sind die SQL Skripts über die DropDown Boxen im oberen Rand des Editorfensters zu setzen:

Image may be NSFW.
Clik here to view.
image

Das Sql Skript bereitet die noch leere Datenbank soweit vor, dass der Test erfolgreich durchlaufen kann. In dem Fall wird eine Bestellung mit 3 Bestellzeilen eingetragen. Der mühsame Teil war, alle notwendigen Tabellen mit Demodaten zu befüllen. Das kann in einer kleinen Datenbank schon mal bedeuten 10 oder mehr Tabellen mit Daten zu versehen.  Der wichtigste Teil des Insert Codes:

INSERT "Order Details" VALUES(10881,23,9,30,0.2)
INSERT "Order Details" VALUES(10881,61,28.5,30,0.2)
INSERT "Order Details" VALUES(10881,70,15,50,0.2)

Die restlichen Code zeilen, schicke ich gerne per Email. Da die Order Details Tabelle fast nur aus PK Felder besteht mussten auch noch Orders, Products, Categories, Suppliers, Customers und Employees angelegt werden Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley
zum Glück muss dieses Skript nicht all zu oft erstellt werden.

Der Lohn der Arbeit ist nun ein erfolgreich laufender Unit Test für die Stored Procedure:

Image may be NSFW.
Clik here to view.
image

und nun kann nach Herzenslust der Code der SP verbessert und verfeinert werden und wir können jederzeit unseren Code automatisiert testen.

Ranking in SQL

Oft höre ich in meinen Kursen die Sätze: “Hättest du mir das nicht zwei Wochen früher sagen können?”. Oder der Klassiker: “Ohman, wenn ich das gewusst hätte, hätte ich mir eine Menge Zeit gespart!”. Deshalb hier ein Auszug von Ranking-Funktionen.

Was sind Ranking-Funktionen
Wie der Name eigentlich schon sagt wird eine Rangfolge erstellt. Mit diesen Funktionen lassen sich extra Spalten anzeigen die dann z.B. eine Rangfolge über die Kunden bringt die den meisten Umsatz erbracht haben. Das hört sich bis jetzt noch nicht so berauschend an, denn ich könnte auch nach dem Umsatz sortieren mit Order by, dann würde ich den Kunden mit dem größten Umsatz ganz oben sehen.

Hier mal das Beispiel:

Select Companyname, sum(quantity*Unitprice) as Umsatz 
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby Companyname
orderby Umsatz desc

Ergebnis würde so ausschauen:

Image may be NSFW.
Clik here to view.
orank ausgabe


Schön und gut, nun das ganze mal mit der Rank-Funktion.

Rank()

Select Companyname, sum(quantity*Unitprice) as Umsatz,
rank() over (orderbysum(quantity*unitprice) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby Companyname


So schaut's aus:

Image may be NSFW.
Clik here to view.
rank ausgabe

Wenn wir uns mal den Syntax der Rank-Funktion anschauen ist eigentlich nur zu beachten was in der Klammer steht und nach welcher Spalte platziert werden soll.

Jetzt habe ich zwei Fragen! Was ist wenn ich jetzt noch einen Schritt weiter gehen will und mir die Rangfolge der Kunden pro Land anzeigen lassen will aber immer noch am Umsatz orientiert.

Die Zweite Frage ist: Was passiert mit der Reihenfolge wenn es zwei oder mehr identische Werte gibt?
Wird der Folgerang  übersprungen? Oder zählt er einfach weiter? Was passiert schauen wir uns ein Stück später an, jetzt erst mal zur oberen Frage.

Nun sehen wir die Besonderheit dieser Ranking-Funktion denn sie ist in der Lage ein Ranking auf eine bestimmte Spalte zu machen aber sie noch an einem Unterkriterium zu orientieren.

Ich zeige gleich ein Beispiel das dem oberem gleich ist bis auf die Sache das ich herausfinden will welcher Kunde in jedem Land auf Rank: 1, 2, 3 usw. steht.

Select Companyname, Country, sum(quantity*Unitprice) as Umsatz,
rank() over (partition by Country orderbysum(quantity*unitprice) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby Companyname, country


Was hat sich verändert? Ich habe eine weitere Spalte im Select hinzugefügt, und zwar Country. Außerdem habe in der Klammer nach der over-Klausel das Schlüsselwort partition by eingefügt und das führt dazu das er das Ranking nicht anhand der ganzen Tabelle erstellt, stattdessen für das Country.

Das Ergebnis sollte fast alle Fragen beantworten:

 Image may be NSFW.
Clik here to view.
rank partition


Nun wurde ein Ranking für jedes Country durchgeführt.

Soweit so gut, jetzt komm ich auf die Frage von vorher zurück was passiert wenn zwei Einträge den gleichen Umsatz haben? Wie wird der Rang verteilt?

Habe meine Abfrage ein “bisschen” umgebaut damit man das Phänomen von gleichen Werten beobachten kann.
Die Abfrage liefert nun die Bestellung mit den meisten Positionen zurück.

Select o.orderid,  count(od.OrderID) as Anzahl,
rank() over (orderbycount(o.orderid) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby o.OrderID

Hier mal ein krassen Beispiel:

Image may be NSFW.
Clik here to view.
rank falsch

Diese Platzierung ist meines Erachtens die beste. Ich nehme hier gerne ein Beispiel aus meiner früheren Leichtathletikzeit. 100m Sprint: Es gibt einen 1.Platz, zwei 2.Plätze und somit keinen 3.Platz sondern nur noch einen 4.Platz. Jetzt gibt es noch die Funktion Dense_Rank(). Die macht es anders. Es gibt einen 1.Platz, zwei 2.Plätze und somit einen 3.Platz usw.! Wem die zweite Variante zusagt, der sollte statt Rank() die Dense_Rank()-Funktion benutzen.

Dense_Rank() 

Select o.orderid,  count(od.OrderID) as Anzahl,
dense_rank() over (orderbycount(o.orderid) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby o.OrderID

Und hier das dazugehörige Ergebnis:

Image may be NSFW.
Clik here to view.
dense_rank

Wie man siehst, ignoriert dense_rank() doppelt vergebene Plätze.

So viel zur Platzierung. Zwecks der Vollständig stelle ich auch noch zwei weitere Funktion vor.

Row_Number()

Was macht Row_Number()? Eigentlich, genau das was es sagt. Die Funktion nummeriert durch, beachtet aber nicht die Werte wie Rank() oder Dense_Rank().

Select o.orderid,  count(od.OrderID) as Anzahl,
row_number() over (orderbycount(o.orderid) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby o.OrderID

Ausgabe:

Image may be NSFW.
Clik here to view.
row_number

Die Row_number-Funktion kann natürlich auch mit dem Schlüsselwort Partition by verwendet werden.

Letzte Funktion die ich hier beschreibe hört auf den Namen ntile().

ntile()

Ntile() spaltet sich ein bisschen ab von den bisherigen Funktion, denn bei Ntile muss ein Parameter mitgeben werden. Was macht ntile()? Die Funktion teilt die Summe der Einträge durch die Zahl die der Funktion mitgegeben werden. Nehmen wir an wir haben eine Tabelle mit 100 Zeilen und benutzen
ntile(10), dann sollte es 10 Bereiche geben mit jeweils 10 Zeilen. Das Ergebnis der Abfrage wird es verdeutlichen.

Select o.orderid,  count(od.OrderID) as Anzahl,
ntile(10) over (orderbycount(o.orderid) desc) as Rank
from Customers c 
innerjoin Orders o on c.customerid = o.customerid
innerjoin [order Details] od on od.orderid = o.orderid
groupby o.OrderID

Das Ergebnis sagt alles:

Image may be NSFW.
Clik here to view.
ntile

Meine Tabelle hat 830 Zeilen und ich teile sie in 10 gleich große Bereiche.
830/10 = 83
Das heißt der erste Bereich geht bis 83, der zweite bis 166, der dritte 249 usw..

Das war’s zu den Ranking-Funktionen.

Ich hoffe das wird dem ein oder anderen eine große Hilfe sein.

Nicht Joints rauchen, sondern Joins setzen

Hier eine kurze Einführung über Joins in SQL.

Was sind Joins?
Mit Joins hat man die Möglichkeit “Mehr-Tabellen-Abfragen” zu machen. Wenn ich z.B. sehen möchte, Welcher Kunde, welches Produkt bestellt hat, werde ich diese Information wahrscheinlich (bei guten Datenbankdesign) nicht in einer Tabelle finden. Jetzt gibt es fünf verschiedene Joins um Tabellen mit einander zu verknüpfen.

INNER JOIN

Was passiert beim Inner Join? Beim Inner Join werden nur Datensätze ausgegeben die einen Partner in der Verknüpfungstabelle haben. Das fällt mir schwer das in die richtigen Worte zufassen, deshalb hier eine Grafik die das Prinzip erläutern sollte.

Image may be NSFW.
Clik here to view.
Beziehung

Wie hier unschwer zu sehen ist, besteht eine 1:n Beziehung zwischen der Customers-Tabelle und der Orders-Tabelle. Nun will ich mit einer Abfrage den Companyname haben und die dazu gehörige Freight(Frachtkosten). Syntax würde so ausschauen:

Select Companyname, Freight from Customers o
innerjoin Orders o on c.customerid = o.customerid


Da ich jetzt den Inner Join verwendet habe, bekomme ich NUR Einträge bei denen Kunden auch Bestellung gemacht haben. Kunden die keine Bestellung gemacht haben, haben auch keine Customerid in der Orders-Tabelle. Bestellungen die keinen Kunde haben tauchen ebenfalls nicht auf, falls das möglich wäre. Die Customerid ist in diesem Fall die Prüfspalte.

FULL OUTER JOIN

Dem Outer Join ist eigentlich egal ob der einen “Partner” in der anderen Tabelle findet oder nicht, er listet sowieso jeden Datensatz auf. Das bedeutet wenn ich für meine Abfrage auch die Kunden brauch die nie eine Bestellung gemacht haben, könnte ich den Outer Join verwenden.

Jeder Datensatz der rechten und der linken Tabelle kommt in die Ergebnismenge. Findet sich über das ON-Kriterium ein passender Partner werden beide zusammengefügt, andernfalls wird die jeweils fehlende Seite mit NULL aufgefüllt.

Beispiel zum FULL OUTER JOIN:

Select Companyname, Freight from Customers c
fullouterjoin Orders o on C.Customerid = O.Customerid
fullouterjoin [Order Details] od on od.Orderid = o.Orderid

Mit diesem Statement hab ich gleich zwei Tabellen gejoint und in diesem Fall würde ich alle Einträge bekommen, egal ob eine Kunde eine Bestellung hat oder nicht, was kein Vorteil ist, denn umso mehr Datensätze verglichen werden müssen umso länger dauert die Abfrage.


LEFT OUTER JOIN/RIGHT OUTER JOIN

Bei einem Left Join wird ein Datensatz der aus der linken Tabelle kommt in jedem Fall in das Ergebnis geschrieben. Wenn ein Datensatz der rechten Tabelle dem ON-Kriterium entspricht, so wird er entsprechend in den Spalten eingetragen, ansonsten bleiben die Spalten leer, also NULL. Der RIGHT JOIN arbeitet entgegengesetzt. Ein Left oder Right Join ist nichts anderes als ein Outer Join auf die Linke bzw. Rechte Tabelle.

 

CROSS JOIN

CROSS JOINs, die keine WHERE-Klausel aufweisen, erzeugen das kartesische Produkt aus den Join beteiligten Tabellen. So entspricht die Größe des Resultsets eines Kartheisches Produkt der Anzahl der Zeilen in der ersten Tabelle multipliziert mit der Anzahl der Zeilen in der zweiten Tabelle.

Beispiel zum CROSS JOIN:

Select * from Customers
crossjoin Orders


Wie man vielleicht schon bemerkt hat, kann man sich beim Cross Join die On-Anweisung sparen da sowieso alles mit allem multipliziert wird. Allerdings ist die Benutzung des Cross Joins sehr gering, da er doch eine etwas spezielle “Persönlichkeit” hat.


Contained Database – die Eigenständige Datenbank

Erst letzte Woche habe ich in meinem Blog nach Contained Databases gesucht .. und nichts gefunden. Wirklich peinlich! Gerade dieses kleine neue Gimmick des SQL Server 2012 ist für  mich eines des praktischsten und häufigst verwendeten neuen Features ab SQL Server 2012.

Zweck der Contained Database

Datenbanken können nicht ohne Systemdatenbanken leben. So werden beispielsweise Logins in der Master Datenbank gespeichert, temporäre Tabellen in der TempDB, Aufträge in der msdb. Dagegen ist überhaupt nichts einzuwenden. Aber sbald man ein Backup einer Datenbank auf einen anderen Server restored fehlen logischerweise wieder diese Dinge. Nicht – zumindest nicht ganz – bei einer Contained Database. Hier werden bspw. Logins in der Contained Database gespeichert und temporäre Tabellen ebenfalls dort erstellt. (zugegebenermaßen: Vor- und Nachteil).

Wie gehts?

Konfiguration des Servers

Bevor Contained Databases erstellt werden können muss auf dem Server die Option Einschlusstyp auf True gesetzt sein:

Image may be NSFW.
Clik here to view.
image

Das geht natürlich auch per Script:

EXEC sys.sp_configure N'contained database authentication', N'1'
GO
RECONFIGURE WITH OVERRIDE
GO

 

Anlegen einer Contained Database

Das Anlegen ist kurz und schmerzlos:

Image may be NSFW.
Clik here to view.
image

oder per Script natürlich:

USE [master]
GO
ALTER DATABASE [ContDB] SET CONTAINMENT = PARTIAL WITH NO_WAIT
GO

Interessanter ist schon die Tatsache, dass der Einschlusstyp entweder:

  1. Keine
  2. Teilweise

Nein es ist kein Fehler! Eine vollständige Kapselung einer Datenbank existiert auch nicht unter SQL Server 2014. In der Onlinedokumentation liest man zu folgendes:

Eine teilweise enthaltene Datenbank ist eine enthaltene Datenbank, die die Verwendung nicht enthaltener Funktionen zulässt.

Alles klar, oder? Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley

Letztendlich bedeutet dies, dass es immer noch Dinge gibt, die nicht in der Contained Database vorgehalten werden, sondern immer noch ausserhalb aufgehoben werden. (Jobs, etc.)

Sehen wir uns mal ein paar Beispiele an.

Keine verwaisten User mehr

Legen wir mal einen User an, der nicht wi üblich einem Login (master Datenbank) zugeordnet wird.

Image may be NSFW.
Clik here to view.
image

 

USE [ContDB]
GO
CREATE USER [Seppl] WITH PASSWORD=N'123@zauberei'
GO

Natürlich funktioniert das auch mit einem Windows Login

USE ContDB
CREATE USER [FBI\ss1]

Das Login – auf herkömmliche Art und Weise wird nun natürlich scheitern, da in der master kein Login enthalten ist.

Image may be NSFW.
Clik here to view.
image

Damit der Login gegenüber der Datenbank funktioniert muss diese explizit angegeben werden:

Image may be NSFW.
Clik here to view.
image

Das Schöne an der Geschichte ist, dass nicht nur Datenbanken inkl Logins nun leicht von Server zu Server gebracht werden können. (Das Backup selbst reicht ja schon aus), sondern auch Sicht der Administration. Die Benutzer ohne Login haben effektiv keine Zugriffsmöglichkeit auf anderen Datenbanken und noch besser sehen diese nicht einmal:

Image may be NSFW.
Clik here to view.
image

Allerdings sollte nicht unerwähnt blieben, dass per T-SQL immer noch ein wenig mehr gesehen werden kann.  Zumindest die master und die tempdb sind sichtbar und auch der Zugriff auf Systemsichten ist ebenso möglich. Allerdings ist das nur halb so schlimm, wie man nun denken würde, denn es werden nur Ergebisse zu sehen sein, die den jeweiligen Benutzer der Contained Database betreffen

Image may be NSFW.
Clik here to view.
image
Image may be NSFW.
Clik here to view.
image

Datenbankübergreifende Zugriffe?

Ja, das ist möglich. Notwendig dazu ist, dass die Contained Database auf vertrauenswürdig gesetzt ist:

ALTER DATABASE ContDB SET TRUSTWORTHY ON

Anschliessend muss die aktuelle Verbindung des Benutzer zurückgesetzt werden, damit die Änderung aktiv werden kann.

Image may be NSFW.
Clik here to view.
image

Entweder man schaltet den guest Account aktiv (nicht unbedingt das, was man gerne macht) oder man legt den Benutzer in der anderen Datenbank ebenfalls an.  Das funktioniert allerdings auch nur bei Windows Usern, da SQL Server intern immer SIDs verwendet. Im Falle eines SQL Users werden eigene SID generiert, im Falle von Windows Konten werden die Windows SID verwendet. Wenn man also einen weiteren SQL Benutzer in der Datenbank anlegt, wird dieser bei SQL Konten, defintiv eine andere SID besitzen.

Temporäre Tabellen

Ein weiterer Klassiker der Contained Database sind temporäre Tabellen. Verwendet man temporäre Tabellen, dann landen diese in der tempdb und nehmen dort auch die Sortierung der tempdb an. Weist jedoch aktuelle Datenbank eine andere Sortierung auf, kommt es zu einem Konflikt:

Image may be NSFW.
Clik here to view.
image

Ändert man die Datenbank auf eine Contained Database, funktioniert das plötzlich wieder:

Image may be NSFW.
Clik here to view.
image

Die temporäre Tabellen liegen aber nun nicht wirklich in der Contained Database, wie man immer wieder gerne mal liest, sondern nach wie vor in der tempdb, bekommt allerdings die Sortierung der Ursprungsdatenbank verpasst.

Das ist gut so, da die tempDB nach wie vor die Leistung von Auslagerungsvorgangängen übernehmen sollte.

Migration von Datenbanken und Benutzern in Contained Databases

Gelinde gesagt, ist die Migration einer Datenbank in eine Contained Database nichts anderes als das Aktvieren der Contained Database (siehe Einschlusstyp = True s.o.)

Für die Migration eines Benutzers steht explizit eine Prozedur zur Verfügung

sp_migrate_user_to_contained [ @username = ] N'user' ,     [ @rename = ] { N'copy_login_name' | N'keep_name' } ,     [ @disablelogin = ] { N'disable_login' | N'do_not_disable_login' } 

Also beispielsweise:

sp_migrate_user_to_contained 
@username = N'Hias',@rename = N'keep_name',@disablelogin = N'do_not_disable_login' ;

Wobei keep_name den Benutzername des Benutzers in der Datenbank übernimmt, dagegen übernimmt copy_login_name den Loginnamen des Benutzers für die Contained Database.

Microsoft stellt dazu sogar ein Codesnippet zur Verfügung mit dem man alle Benutzer auf einen Schlag migrieren könnte.

http://technet.microsoft.com/de-de/library/ff929275.aspx

DECLARE @username sysname ;
DECLARE user_cursor CURSOR     FOR         SELECT dp.name         FROM sys.database_principals AS dp         JOIN sys.server_principals AS sp         ON dp.sid = sp.sid         WHERE dp.authentication_type = 1 AND sp.is_disabled = 0;
OPEN user_cursor
FETCH NEXT FROM user_cursor INTO @username     WHILE @@FETCH_STATUS = 0     BEGIN         EXECUTE sp_migrate_user_to_contained         @username = @username,         @rename = N'keep_name',         @disablelogin = N'disable_login';     FETCH NEXT FROM user_cursor INTO @username     END
CLOSE user_cursor ;
DEALLOCATE user_cursor ;
Einschränkungen und Tipps
  • Vermeiden Sie Logins mit gleichen Namen wie der Contained DB Benutzer. Falls doch, dann ändern sie beim Login die Standarddatenbank so, dass nicht gleich auf die ContDB zugegriffen wird. Sonst gibts Fehler bei der Anmeldung.
  • keine Kerberos Authentifizierung möglich
  • beim Anfügen einer Datenbank den Zugriff auf Restricted User stellen, um nicht unegwollte zugriffe auf dem SQL Server zu haben.
  • keine Replikation
  • kein CDT und CDC
  • keine temporären Prozeduren

PS: Die Contained Database nicht auf AUTO_CLOSE stellen. Ausnahme wäre 1.April Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley
Sobald die Datenbank geschlossen ist, gibts auch keine Anmeldung mehr.

 

Coole SacheImage may be NSFW.
Clik here to view.
Zwinkerndes Smiley
.. und warten mal auf Einschlusstyp FULL

SQL Server 2014 – RTM –1. April Release Datum

SQL Server 2014 ist fertig! Am 1. April wird SQL Server zum Download zur Verfügung gestellt werden.   Quentin Clark, corporate vice president der  Data Platform Group verkündete heute im offiziellen SQL Server Blog  den Erscheinungstermin. (-> http://blogs.technet.com/b/dataplatforminsider/archive/2014/03/18/sql-server-2014-releases-april-1.aspx)

Schon einige male wurde der 1.April als Erscheinungstermin übergangen (siehe  bspw, Sharepoint Designer) um ja nicht als April Scherz  zu gelten. Dies dürfte auf den SQL Server 2014 nicht zutreffen. Wenn ein Server der letzten Jahre großes Lob verdient hat, dann dieser: inMemory Tabellen, Native kompilierte Prozeduren, Performancegewinn um über das 10 fache bei gleicher Hardware, updatebarer Columnstore Index bei extremer Kompressionsrate, erweiterte AlwaysOn Availabilitygroups und und und..  Das ist kein R2, das ist kein besseres SP. Das ist eine neue Generation!

Wer also keine Sekunden verpassen möchte, der kann sie hier registrieren, um per Email benachrichtigt zu werden.

Möchtest du mehr zu SQL wissen, dann schau mal hier: http://www.ppedv.de/SQL

Ándale, Ándale der schnellste SQL Server in Mexico

SQL Server 2014 – In-Memory

Der neue SQL Server 2014 steht in den Startlöchern und wird am 1. April released. Mit ihm fallen uns viele neue Features anheim. Wie z.B. In-Memory Tabellen.

Was ist In-Memory?

Mit In-Memory besteht die Möglichkeit „Memory Optimized tables“ zu erstellen. Diese beschleunigen das Lesen und Schreiben der Tabelle, in dem sie sich nach dem Start des SQL Server sofort im Arbeitsspeicher befinden und nicht erst von der Festplatte gelesen werden müssen. Das bringt schon einmal einen enormen Fortschritt bei der Optimierung der Datenbank, umso weniger Lese- und Schreibvorgänge von der Festplatte umso besser. Nun stellt sich die Frage: Ist es denn so tragisch wenn man einen Lesevorgang mehr hat?
Naja das kommt nun auf die Lebenszeit der Seiten im Arbeitsspeicher an.

Wenn nun eine Tabelle mit einen Select-Statement aufgerufen wird, werden alle Seiten der Tabelle von der Festplatte in den Arbeitsspeicher geladen. Nun fallen aber die Seiten der Abfrage nicht sofort wieder aus dem Arbeitsspeicher, es sei denn der Arbeitsspeicher ist dementsprechend voll und eine andere Abfrage braucht die Speicher.

Wenn nun die In-Memory Variante benutzt wird, fallen die „Memory-Optimized tables“ Seiten nie aus dem Speicher da sie sich dauerhaft im Speicher halten. Letztendlich bedeutet das, die I\O bei In-Memory gegen Null geht.

Dieses Feature erinnert stark an die frühere Variante Tabellen im Speicher zuhalten. Die Rede ist von „DBCC PINTABLE“. Mit diesem Statement wurden Tabellen als fixiert markiert und somit waren sie dauerhaft im Arbeitsspeicher. Allerdings wurde diese Funktionalität sehr früh wieder entfernt da unerwünschte Nebeneffekte auftraten. Dazu zählte die Beschädigung des Pufferpools. In-Memory soll komplett neu entworfen worden sein und keine negativen Gemeinsamkeit mit DBCC PINTABLE aufweisen.

 

Image may be NSFW.
Clik here to view.
Inmemory

 

Datenbank vorbereiten

Bevor man In-Memory Tabellen nutzen will muss man seine Datenbank dementsprechend erstellen oder anpassen.

createdatabase HKTest
on
primary (Name = [HKTest data],
filename = 'C:\data\HKTest.mdf'),
filegroup [IM_fg] contains memory_optimized_data
(name = [HKTest_mod_dir],
filename = 'C:\data\HKTest_fg')
log on (name = [HKTest_log], filename = 'C:\data\HKTest_log.ldf')
collate Latin1_general_100_bin2

Eine Filestream-Dateigruppe muss erstellt werden und die Collation auf Latin1_General_100_bin2 gesetzt werden.

Bestehende Datenbank anpassen:

alterdatabase HKTest
add filegroup IM_fg contains memory_optimized_data;
go
 
alterdatabase HKTest
addfile (Name='hk_mod', filename= 'C:\data\HKTest_fg')
to filgroup hk_mod;
go
 
alterdatabase [HKTest] collate Latin1_General_100_BIN2
go

 

SQL Server Neustart, wo sind meine Daten?

Gehen wir davon aus man benutzt In-Memory Tabellen und der SQL Server entscheidet sich aus irgendwelchen Gründen den Dienst zu quittieren. Was passiert mit den Daten in den Memory-Optimized Tabellen? Schließlich liegen diese vollständig im Arbeitsspeicher und RAM ist flüchtig.

Glücklicherweise werden die Daten einer Memory-Optimized Tabelle in einer extra definierten Filestream-Dateigruppe gespeichert, sodass nach dem SQL Server Neustart die Daten aus der Dateigruppe ohne unser Zutun wiederhergestellt werden. Außerdem werden Operationen mit „Memory-optimized Tables“ ins gleiche Transaktionslog geschrieben wie „disk-based“ Tabellen.
Die Protokollierung der Memory-Optimized fällt äußerst gering aus, im Gegensatz zu disk-based Tabellen. Bei einer Ermittlung der Größe des Transaktionsprotokolls kam bei Memory-Optimzed Tabellen ein Größe von 42MB zustande, bei einem Einfüge Vorgang von einer Million Zeilen. Die nicht Speicheroptimierte Tabelle schnitt nicht so gut ab. Bei gleicher Tabellenstruktur und derselben Anzahl der Datensätze kam die Protokolldatei auf 4, 1GB. Genauso sieht es mit der Ausführungszeit des Insert-Statements aus. Hier kann ebenfalls wieder die Speicheroptimierte Tabelle punkten, mit einer Ausführungszeit von ca. zwei Sekunden sticht sie die disk-based Tabelle die mit ca. vier Minuten unterwegs ist aus. Der Grund des enormen Unterschieds besteht darin das die disk-based Tabelle die neuen Datensätze in die jeweilige Datendatei schreiben muss, was natürlich IO verursacht. Da die Speicheroptimierte Tabelle vollständig im Speicher liegt, entsteht somit keine IO.

Wenn eine Tabelle mit der Option „Schema_Only“ erstellt wird ist sie nicht dauerhaft und wird auch nicht mitgeloggt. Das bedeutet diese Art der Tabelle befindet sich im Speicher und benötigt somit keine IO. Die Schema_Only Variante setzt beim Punkt Ausführungszeit noch einen drauf und ist noch schneller als die „normale“ memory-optimized Tabelle Nachteil: nach einem Serverneustart oder einem Failover sind die Daten verloren, doch die Tabellen werden neu erstellt. Diese Art von Tabellen eignen sich ideal um eventuell Abfrageergebnisse zwischen zu speichern. Diese Neuerung bietet ein gute Alternative für Temporäre Tabellen und Tabellenvariablen.

Vorteile gegenüber der Temporären Tabellen/Tabellenvariable:

Bei einer Temporären Tabelle entsteht IO sobald Daten in die Tabelle geschrieben werden.
Eine Tabellenvariable hat ebenfalls ein Problem, und zwar können die Zeilen in der Variable nicht geschätzt werden. Beides trifft auf eine Memory-Optimized Table nicht zu.

CREATETABLE dbo.Ord
(OrdNo INTnotnullprimarykeynonclustered hash with (bucket_count=1000000),
OrdDate datetime notnull,
CustCode nvarchar(5) notnull)
with (memory_optimized=on, Durability = Schema_and_Data)
go


Mit dieser Syntax wird eine Memory-Optimized Tabelle erstellt.

CREATETABLE dbo.Ord
(OrdNo INTnotnullprimarykeynonclustered hash with (bucket_count=1000000),
OrdDate datetime notnull,
CustCode nvarchar(5) notnull)
with (memory_optimized=on, Durability = Schema_Only)
go


Gleiche Tabelle mit Schema_Only gekennzeichnet.

Wichtig zu wissen ist das mindestens ein Index auf der Tabelle vorhanden sein muss, und zwar ein hash Index wie im oberen Beispiel oder ein Range Index. Zu beachten ist lediglich die Größe des bucket_counts, man sollte nämlich ca. das 2-fache der eindeutigen Wert nehmen. Was ist der bucket_count? Der Hash Index besteht aus einem Array von Zeigern.
Jedes Element des Arrays ist ein sogenannter bucket_count. Wie lässt sich nun die richtige Größe des bucket_counts bestimmen? Idealerweise sollte man sich hier an der Anzahl der eindeutigen Indexzeile orientieren.

createprocedure dbo.OrderInsert
with native_compilation, schemabinding, executeas owner
as
begin atomic with
(transactionisolationlevel = snapshot,
language = N'English')
declare @i int = 0
while @i <= 1000000
begin
declare @OrdDate datetime = getdate();
      insert into dbo.Ord (OrdNo, CustCode, OrdDate) values (@i, 'ALFKI', getdate());
set @i += 1
end
end

 

Hier wird eine natively compilied procedure erstellt, in der eine Million Datensätze in die Memory-optimized Tabelle eingefügt werden. Bei einer Million Einträgen habe ich deshalb den bucket_count auf 1000000 gesetzt.

Allerdings wird der angegebene bucket_count aufgerundet und zwar Bit-weise.

Bucket_count von 10000 ist nicht gleich 10000
Bucket_count von 10000 ist gleich 16384 ergibt 2^14

Problem bei zu viel Bucket_count: Umso höher der bucket_count umso mehr Speicher verbraucht die Speicheroptimierte Tabelle.

Beispiel in den Test:
Bei meinen Tests ist mir einmal der Fehler unterlaufen das ich mich beim bucket_count um eine 0 vertan habe, daraus resultierte das mein SQL Server Dienst den gesamten Speicher der Virtuellen Maschine vertilgte.

Problem bei zu wenig Bucket_count: Bei zu geringer Größe des bucket_counts ist die Leistungssteigerung dahin.

Beispiel bei zu wenig Bucket_count:
Der Einfüge Vorgang dauerte bei einem Bucket_count von 1024 und einer Million Datensätze, achtmal solange.

Wenn man den bucket_count nicht bestimmen kann, sollte man den Range Index benutzen, doch dieser wird erst in der CTP2 des SQL Server 2014 vorgestellt. Mal sehen was dieser mit sich bringt.

Natively Compiled Procedures

Diese neuartige Stored Procedure funktioniert nur in Verbindung mit einer Memory-optimized Tabelle. Sie hat einen entscheidenden Vorteil, sie wird kompiliert und nicht interpretiert. In-Memory Tabellen sind schnell, wer aber noch schneller unterwegs sein weil benutzt sie in Verbindung mit natively Compilied Procedures. Allerdings gibt es eine sehr lange Liste zu Statements die nicht in einer natively Compilied Procedure benutzt werden dürfen. Hier der Link: http://technet.microsoft.com/en-us/library/dn246937%28v=sql.120%29.aspx

Mit einer Natively Complied Procedure lässt sich zusätzlich das Lesen beschleunigen. Bei einem Lesevorgang von 10.000.000.000 Datensätzen legt die In-memory Tabelle eine Zeit von 28 Sekunden vor. Die Schema_Only Tabelle braucht dieselbe Zeit als die Schema_and_data da sie sich beide im Speicher befinden. Die gleiche Anzahl der Einträge mit einer disk-based Tabelle abzufragen wäre Irrsinn. Deshalb habe ich Anzahl der Abgefragten Datensätze ein bisschen nach unten geschraubt und zwar auf 1 Million. Bereits hier dauert die Abfrage 58 Sekunden.

Fazit

Mit In-Memory hat Microsoft wieder einmal ein Fortschritt in Richtung Geschwindigkeit gemacht. Bei Einfüge Vorgängen eine Leistungssteigerung um den Faktor 30 und bei den Schema_Only Tabellen sogar um den Faktor 60. Allerdings ist Vorsicht geraten bei In-memory Tabellen, bei meinen Tests musste ich ca. zehnmal meinen SQL Server Dienst beenden, da durch den Speicherverbrauch der SQL Server kein RAM mehr zur Verfügung hatte. Früher war Arbeitsspeicher noch teuer, heute nicht mehr, der Preis des Arbeitsspeichers ist rapide gesunken. Beim heutigen Stand kann man eine Server mit 32 Kernen und 1 TB RAM schon für unter 50000€ erstehen. Allerdings sollte nicht der Irrglaube entstehen das mehr Arbeitsspeicher alle Probleme löst. Mehr Arbeitsspeicher reduziert zwar die Wartezeit für Lesezugriffe, wirkt sich aber nicht auf die Wartezeit aus die benötigt wird um Schreibvorgänge auf die Festplatte zu tätigen. Außerdem muss sich die Datenbank nach dem Neustart eine Weile wiederherstellen um die Daten aus der Filestream-Dateigruppe wieder in den Arbeitsspeicher zu laden.

Möchtest du mehr zu SQL wissen, dann schau mal hier: http://www.ppedv.de/SQL

TSQL - Der bessere Replace

Ab und zu sieht man den Wald vor lauter Bäumen nicht. Dieses Mal stand ich vor der Aufgabe aus einer Spalte bestimmte Werte zu ersetzen. Die Liste der zu ersetzenden Werte war doch etwas länger. Mit einem simplen Replace komme ich nicht besonders weit, da sich ja nur ein Wert ersetzen lässt.  Grübel grübel.. Doch es ist ganz simpel.

Das Prinzip:

Wir weisen einer Variablen einen per Replace bereinigten Wert zu.  Anschliessend wird auf das Ergebnis wiederum ein weiterer Replace angewendet (mit einem anderen zu ersetzenden Wert) und dann wiederum der Variable zugewiesen. Das machen wir solange bis wir alle zu ersetzenden Werte durch haben.  Das geht pfeilschnell.

Das ganze packen wir noch in eine Funktion und schon haben wir einen besseren Replace:

CREATE FUNCTION  dbo.BetterReplace (@str NVARCHAR(4000))
RETURNS nVARCHAR(4000) AS
BEGIN
    SET @str = REPLACE (@str,'>',' ')
    SET @str = REPLACE (@str,';',' ')
    SET @str = REPLACE (@str,'&',' ')
    SET @str = REPLACE (@str,':',' ')
    SET @str = REPLACE (@str,'<',' ')
    SET @str = REPLACE (@str,'/',' ')
    SET @str = REPLACE (@str,'\',' ')
    SET @str = REPLACE (@str,',',' ')
    SET @str = REPLACE (@str,'*',' ')
    SET @str = REPLACE (@str,'^',' ')
    SET @str = REPLACE (@str,'ü','ue')
    SET @str = REPLACE (@str,'ä','ae')
    SET @str = REPLACE (@str,'ö','oe')
    SET @str = REPLACE (@str,'"',' ')

  RETURN @str    
END

Zum Beispiel:

declare @str as varchar(100)
set @str='<Tel>+49\8677-98890'

select dbo.BetterReplace (@str)

Ergebnis:

Tel +49 8677-98890

Einfach und wirkungsvoll!

TSQL–Funktionen sind super – oder doch nicht?

In diesem Blogeintrag werde ich die Performancehintergründe von Funktionen erleuchten und herausfinden, ob Funktionen wirklich zu den schwarzen Schafen gehören.

Funktionen können das Leben enorm erleichtern, da sie uns immer wiederkehrende Berechnungen oder Abläufe ersparen. Das mag soweit in anderen Programmiersprachen stimmen, in TSQL müssen wir mal etwas genauer hinschauen.

Funktionen im Allgemeinen

Ich persönlich war ein voller Fan von Funktionen, die Betonung liegt auf war. Allerdings sollte man Funktionen nach diesem Satz nicht vollständig verteufeln. Es gibt durchaus auch gute Funktionen.

Zuerst das Schlechte:
Es gibt drei Möglichkeiten, mit Funktionen zu arbeiten.

Funktionen in der Select-Liste

In der Select-Liste können nicht nur Spalten angegeben werden, sondern auch Funktionen, die vermutlich Spalten als Übergabeparameter entgegennehmen und dementsprechend manipulieren.
Die Funktionen müssen für jede verarbeitende Zeile aufgerufen werden, was natürlich zu höherer Last führt. Außerdem wird bei Funktionen die parallele Abfrageausführung verhindert.

Funktionen in der Where-Bedienung

Leider auch hier keine Performance-Vorteile. Wenn Funktionen als Prädikat verwendet werden, vereitelt das qualitative Kardinalsschätzungen. Außerdem kann es dazu führen, dass beispielsweise nicht auf einen Index zugegriffen werden kann.

createfunction f_test(@id int)
returnsint
as
begin
return(@id + 10)
end


Hier meine Testfunktion.
Erster Test mit der Funktion. In der Where-Bedingung wird die Funktion verwendet. Auf der Spalte “ID” ist ein non clustered Index.

select ID from test
where dbo.f_test(id) > 80000


Schauen wir uns mal die dazugehörigen Stats und den Plan an.

Image may be NSFW.
Clik here to view.
Plan mit funktion

Wie hier zu erkennen ist, wird ein Index-Scan verwendet, wobei es ihm möglich sein müsste einen Index Seek durchzuführen. Allerdings kann der Index mit Daten, die von einer Funktion verarbeitet werden, nichts anfangen und somit nicht durch den B-tree gehen.

Hier noch die Auswertung der Statistics:

Image may be NSFW.
Clik here to view.
Stats mit funktion

Um den Vergleich zu sehen: jetzt das Beispiel ohne Funktion im Prädikat.

select ID from test
where id > 79990


Jetzt hier der Plan:

Image may be NSFW.
Clik here to view.
Plan ohne Funktion

Wie zu erwarten: der effiziente Index-Seek, zudem parametrisiert der SQL Server den Plan.
Da Zahlen mehr sagen als Bilder, hier die Auswertung der Stats der zweiten Select-Anweisung.

Image may be NSFW.
Clik here to view.
Stats ohne Funktion

Gleich zu Beginn ist zu sehen, dass die Anzahl der Seiten, die verwendet wurden, ein gutes Viertel ist im Gegensatz zu den Stats des ersten Selects. Das liegt überwiegend am Index-Seek. Außerdem ist bei jeder Ausführung des zweiten Selects die CPU-Zeit auf 0ms. Nicht so beim ersten Beispiel, hier liegt die CPU-Zeit immer bei ca. 350ms.
Da die Funktion für jede Zeile berechnen muss, kommt hier ein bisschen Zeit zusammen. Nun kann man aber nicht wirklich von einer komplexen Rechnung und viel Business-Logik in der Funktion reden, d.h. sobald die Funktion kryptischere Ausmaße annimmt, kann das Ganze schnell böse enden.

Das Licht am Ende des Tunnels

Nun muss es irgendwas geben, was für Funktionen spricht, sonst wäre des Feature zwar nett aber langsam.
Leider gibt es nichts, was für Funktionen spricht (performancetechnisch). Es gibt allerdings eine Art Funktion, die keine Nachteile hat, aber auch keine Vorteile, und deswegen bedenkenlos benutzt werden kann. In Bayern gibt es das Sprichwort: Ned gschimpft, is globt gnua Image may be NSFW.
Clik here to view.
Smiley mit herausgestreckter Zunge
(Nicht geschimpft, ist gelobt genug).

Ich spreche hier von einer Inline-Tabellenwertfunktion. Kurz zur Erklärung: Eine Inline-Tabellenwertfunktion besteht lediglich aus einer Select-Anweisung, die in der Return-Klausel hinterlegt ist, mehr nicht. Es gibt keine Variablen-Deklaration, Fallunterscheidung oder Schleifen.

Kurze Demonstration:
Ich werde zwei Funktionen erstellen, die eine als Inline-Variante, die andere nicht.

Inline-Funktion

createfunction Kunden_Land(@Land varchar(30))
returnstable
as
return(select companyname from Customers
where Country like @Land)



Nicht Inline

createfunction Kunden_Land_nicht_Inline(@Land varchar(30))
returns @resulttable (Companyname varchar(50))
as
begin
    insert into @result
select companyname from Customers
where Country like @Land
return
end


Die beiden Aufrufe

select * from Kunden_Land('%')
select * from Kunden_Land_nicht_inline('%')

 

und hier die Pläne

Image may be NSFW.
Clik here to view.
Plan inline

Beim “Inline-Plan” schätzt der SQL Server die Zeilen richtig ein und kann somit einen sehr optimalen Plan verwenden.

Image may be NSFW.
Clik here to view.
Plan nicht Inline

Hier haben wir ein Negativbeispiel. Der SQL Server schätzt die Anzahl von Zeilen auf EINS, allerdings kommen 91 Zeilen raus. Das liegt nicht mal an der Funktion, der Übeltäter ist ein anderer, nämlich die Tabellenvariable. Mit der kann der SQL Server nichts anfangen und schätzt die Zeilen bei jeder Tabellenvariable auf eins, selbst wenn sie nicht in einer Funktion benutzt werden.

Fazit

Leider fallen Funktionen unter den Bereich “hazardous”. Lediglich die Inline-Funktion kann ohne Bedenken verwendet werden. Sie dient allerdings dann nur sehr eingeschränkt als “dynamische Sicht”. Zumal wird es bei dem ein oder anderen Anwendungsfall nicht möglich sein, komplett auf Funktionen zu verzichten.

 

Wollen Sie mehr über “No Go’s” in TSQL wissen, buchen Sie unseren Kurs: SQL Tuning

SQL Server 2012: CUME_DIST() – kumulierte Verteilung eines Wertes

Neben den Erweiterungen der OVER-Klausel wurden auch einige statistische Erweiterungen implementiert. Wollen Sie zum Beispiel wissen, wie häufig ein Wert relativ zum Rest eines Bereiches vorkommt, so kann dies CUME_DIST im Handumdrehen lösen. Das kann nützlich sein, wenn Sie in einer Tabelle Abteilungen und Umsätze sehen. Wollen Sie nun wissen, welcher Umsatz pro Abteilung der höchste war und wie hoch die anderen Umsätze rel. zur aktuellen Position in der Abteilung sind, dann wäre das ein Fall für den CUST_DIME().

 

CREATETABLE Tab1 (id intidentity,Col1 char(2), COL2 int) GO
INSERT INTO Tab1 VALUES('IT',5), ('IT',3),  ('IT',2), ('HR',10),('HR',8),('HR',3),('HR',1),('MA',5),('MA',4),('MA',4)GOSELECT Col1, COL2,
       CUME_DIST() OVER(PARTITION by COL1 ORDERBY COL2) AS "CUME_DIST()"   FROM Tab1

 

 

und hier das Ergebnis:

Die Spalte CUME_DIST() zeigt, wieviel Prozent der Abteilungen (HR, IT etc.) gleich oder kleiner sind als der aktuelle Wert in Spalte COL2.

Beispielsweise ist in HR in COL2 der höchste Wert. Daher ist CUME_DIST = 1, da alle anderen Werte (100%=1) eben kleiner oder gleich sind als HR. Betrachtet man die 8 in HR, so sind drei Viertel der Werte (8,3,1) kleiner oder gleich als die 8 (3/4 = 0,75)

Image may be NSFW.
Clik here to view.
image

SQL 2008 Administration TrainingImage may be NSFW.
Clik here to view.

SQL Server – Problem beim Einrichten der Spiegelung

Das Spiegeln von Datenbanken ist definitiv mein “Hochverfügbarkeits – Lieblingsfeature”. Sofern die Datenbanken bereits auf dem Zielserver im NoRecovery-Modus wiederhergestellt wurden, dauert das Einrichten keine 10 Sekunden. Nur eines scheitert gerne.. den Failover zu starten. Was kann denn in diesen 10 Sekunden passiert sein.. Der Assistent zum Einrichten der Spiegelung erledigt folgende Dinge:

- Anlegen der Spiegelungsendpunkte (Ein Listener auf normalerweise TCP Port 5022) auf allen beteiligten Servern (Prinzipal, Spiegelpartner, Zeuge)

- Und dann gab es da noch eine bescheidene Frage nach den Dienstkonten, die ein CONNECT Recht benötigen würden.

Meist wird das Einrichten mit folgender Fehlermeldung – Error 1418– quittiert:

Die Server-Netzwerkadresse TCP://Server:5022 ist nicht erreichbar oder nicht vorhanden. Überprüfen Sie den Namen der Netzwerkadresse, und dass die Ports für die lokalen und Remoteendpunkte betriebsbereit sind.

Um das Einrichten des Assistenten zu kontrollieren, kann man nun folgende Dinge tun:

  1. Kontrolle der Spiegelendpunkte via netstat

    Das erreicht man am besten über den Kommandokonsolenbefehl netstat
    zB. netstat –an oder netstat –ano für zusätzlich Information wie Besitzer des Ports bzw netstat –anb für die Prozess-ID
    Es sollte in der Liste auf jeden Fall ein 0.0.0.0:5022 [Abhören] auftauchen.

    Image may be NSFW.
    Clik here to view.
    image

  2. Kontrolle der Endpunkte in SQL Server

    Die Endpunkte müssen gestartet sein. Dies kann man sehr leicht durch folgendes Skript lösen:

    SELECTstate, state_desc from sys.database_mirroring_endpoints

    In den Spalten sollte ein Started zu finden sein.

    Ein Endpunkt läßt sich auch per Skript einrichten (ist auf jedem Server auszuführen)

     

    CREATE ENDPOINT Endpoint_MirroringSTATE=STARTEDAS TCP (LISTENER_PORT=5022)FOR DATABASE_MIRRORING (ROLE=ALL); –ALL auf Spiegel; PARTNER auf Prinzipal; WITNESS auf ZeugenGO

     

  3. Zugriffsrechte der Dienstkonten auf die jeweiligen Endpunkte !! ( Error 1418 )

    Dies ist genau der Punkt, an dem die Spiegelung im SQL Server Management Studio später versagen wird. Die Dienstkonten benötigen ein CONNECT Recht auf die jeweiligen Endpunkte. Das muss jedem Endpunkt, also allen beteiligten Servern, eingerichtet werden.

    Mit folgendem Skript lässt sich auch dieses Problem lösen:

    --Dienstkonten für die Spiegelung müssen ein Login besitzen, falls nicht vorhanden..CREATE LOGIN [Domäne\User] FROM WINDOWS 
    
    --Zugriffsrechte auf Spiegelungsendpunkt geben
    GRANTCONNECTON ENDPOINT::Endpoint_Mirroring TO Domäne\User


    Das Spiegeln muss natürlich anschließend gestartet werden. Entweder per SSMS oder auch per Skript:

    --Auf Spiegel anschließend auf PrinzipalALTERDATABASE DatenbankSET PARTNER ='TCP://PARTNERHOST1.COM:5022';GO
    
    --Zeuge in Betrieb nehmenALTERDATABASE DatenbankSET WITNESS ='TCP://WITNESSHOST4.COM:5022';
    GO
  4. Firewall (Error 1418)

    Sollte es dann immer noch scheitern, nochmals gründlich die Firewall prüfen! Image may be NSFW.
    Clik here to view.
    Zwinkerndes Smiley

     

  5. Sonstige Fehlerquellen

    - Das Wiederherstellungsmodell muss auf FULL (Vollständig) gesetzt sein

    - Das Vollständige Backup sollte inkl. Transaktionsprotokollsicherung auf dem Spiegelpartner im Modus NoRecovery wiederhergestellt werden.

    - Die Dienstkonten sind im günstigsten Fall auf allen Maschinen die gleichen Windows-Konten.

    Falls eine Meldung erscheinen sollte, dass Spiegelung zuvor entfernt werden sollte… bitte sehr! Und für den Fall der Fälle.. der manuelle Failover.

     

    --Entfernen der SpiegelungALTERDATABASE DatenbankSET PARTNER OFF
    
    --Manueller Failover
    ALTERDATABASE DatenbankSET PARTNER FAIlLOVER


    Anschließend eine Überprüfung des Status der Spiegelung

    SELECT db.name, m.mirroring_role_descFROM sys.database_mirroring m JOIN sys.databases dbON db.database_id = m.database_idWHERE db.name = Datenbank;
    GO

SELECT from PROCEDURE – Prozeduren in Abfragen verwenden

Prozeduren lassen sich nicht in SELECT Statements verwenden. Sie müssen ausgeführt werden. Im Prinzip eine sehr logische Sache, da Prozeduren einerseits mehrere Ergebnistabellen zurückgeben könnten bzw. - wenn nur INSERT, UPDATE oder DELETE Statements ausgeführt werden würden - auch gar nichts zurückgeben müssten.

Ein paar Ausnahmen gibt's allerdings schon ;-)

Der Standardweg wäre, das Ergebnis der Prozedur in eine temporäre Tabelle zu schreiben und mit dieser dann weiter zu arbeiten.

Der Weg Nr. 2 ist der Umweg mittels OPENROWSET: eine AdHoc-Abfrage auf einen SQL Server, in unserem Fall auf den eigenen. Die AdHoc-Abfrage ist natürlich mit allen Einschränkungen zu genießen wie z.B.:

  • Geschwindigkeit
  • Es wird das erste Resultset zurückgegeben, falls es mehrere gäbe

Hier nun die simplen Beispiele

 

--Variante 1 - Temporäre Tabelle

 

create table #t1 (product varchar(50), total int)

insert into #t1

exec CustorderHist 'ALFKI'

 

select * from #t1

 

--Variante 2 - OPENROWSET

 

 

Select * from    OPENROWSET('SQLNCLI',

                'Server=.;Trusted_Connection=yes;', --. Für lokale Std Instanz

                'exec northwind.dbo.custOrderHist ''ALFKI''') ProcTab

 INNER JOIN Products P on P.ProductName = ProcTab.Productname

    where

        ProcTab.Total> 10

 

Cool, gell?

 


Den zweitbesten per TSQL finden

Das passt ja fast zur Fussball-Weltmeisterschaft. Ein Teilnehmer einer ppedv Schulung schreibt mir:

“Ich habe da eine „knifflige“ Aufgabe in der Firma, wo ich mit meinem bescheidenen SQL-Wissen nach ein paar Stunden nicht mehr weiter komme. Ich kann's natürlich über Umwege lösen, glaube aber dass man das mit einem sql query oder sql query + stored procedure oder function auch zusammenbringt.”

Kurz zusammengefasst: eine SQL Abfrage im Microsoft SQL Server soll den zweiten Wert liefern. Die Frage war mir neu und mein SQL-Wissen auch ein wenig eingerostet - teilweise war es auf dem Stand von SQL 2000. Seit 2005 ist aber RowNum hinzugekommen und seit 2012 auch ein Offset-Kommando, um in Kombination mit z.B. TOP /Group By einfach einen Datensatz zu überspringen. So zunächst mein erster Gedanke.

Die Ausgangstabelle

   1:  CREATETABLE [dbo].[KickiTest](
   2:      [ID] [int] IDENTITY(1,1) NOTNULL,
   3:      [Ergebnis] [bit] NULL,
   4:      [Datum] [datetime] NULL,
   5:      [Seriennummer] [int] NULL
   6:  ) ON [PRIMARY]

Die Daten

Image may be NSFW.
Clik here to view.
image

Abfrage: Für alle Geräte, deren Ergebnis positiv ist, der zweite Datensatz. Wenn dieser nicht vorhanden ist, wird der erste abgefragt. 

Die Lösung verwendet die generierte Zeilennummer in Kombination mit einem Zähler

   1:  WITH tmp  AS (SELECT ID, Ergebnis, Datum, Seriennummer,
   2:         anzahl= Count(seriennummer) OVER(PARTITION BY seriennummer) ,
   3:          ROW_NUMBER() OVER (PARTITION BY seriennummer 
ORDERBY seriennummer,datum) AS RowNum
   4:  FROM   KickiTest where ergebnis=1) 
   5:   
   6:   
   7:  SELECT ID, Ergebnis, Datum, Seriennummer,rownum,anzahl
   8:  from tmp
   9:  where (anzahl = 1) or (anzahl>1 and rownum=2) 

 

Statistisch semantische Suche

Ja richtig gehört: Statistisch semantische Suche. Ein schönes Wort. In Wikipedia liest man dazu “.. Bedeutung sprachlicher Zeichen..”  Und das triffts auch schon fast. Die statistisch semantische Suche extrahiert relevante Phrasen per Volltextsuche aus Spalten oder Filetables und gewichtet diese.

Image may be NSFW.
Clik here to view.
image
Image may be NSFW.
Clik here to view.
image

Sehen wir uns mal gewichtete Phrasen eines Dokuments an.

Man ganz gut erkennen, dass hier in einem Dokument folgende Begriffe gefunden wurden und je nach Score der eine wohl wichtiger (häufiger) zu sein scheint, als ein anderer. In diesem Beispiel geht es also wohl um Workflows, Stichwörter und Sharepoint.

Tatsächlich muss der Begriff nicht als einzelnes Wort vorkommen, sondern durchaus in Kombinationen. In diesen Text geht es beispielsweise auch um Unternehmensstichwörter.

Aber wie kommt die statistisch semantische Suche in den SQL Server?

Dazu brauchts ein ordentliches Rezept mit folgenden Zutaten: Eine Semantikdatenbank, evtl. das Microsoft Office Filter Paket, die Volltextsuche, ein wenig TSQL und einen Neustart der Volltextsuche.

Punkt 1: Semantikdatenbank einbinden

Auf der DVD des SQL Servers findet sich im Verzeichnis \1031_DEU_LP\x64\Setup die Installationsdatei SemanticLanguageDatabase.msi für die Datenbank. Das Setup entpackt lediglich die Datenbank in ein Verzeichnis. Um die Semantikdatenbank in die SQL Serverinstanz einzubinden, muss die Datenbank noch angefügt werden. Entweder per GUI im SSMS oder per Script. Anschließend die Datenbank für die semantische Suche registrieren.

Punkt 2: Office Filter Paket einbinden

Da SQL Server keine aktuellen Office Versionen out-of-the-box supportet,  kann man das schnell ändern indem man sich das Office Filter Paket 2010 downloaded: http://www.microsoft.com/de-de/download/details.aspx?id=17062. Nach der Installation muss das Filterpaket noch der Volltextsuche bekannt gemacht werden

--SemantikDb DB extrahieren (\1031_DEU_LP\x64\Setup) --und attachen.....msi-FileCREATEDATABASE semanticsdbON (FILENAME ='C:\Microsoft Semantic Language Database\semanticsdb.mdf') FOR ATTACH-- und registrierenEXEC sp_fulltext_semantic_register_language_statistics_db @dbname= N'semanticsdb';--Welche Sprachen werden unterstütztselect*from sys.fulltext_semantic_languages--Filterpackage installiert?? Exsitiert für Office 2007 und Office 2010 --Aktivieren der iFilter für VolltextsucheSp_fulltext_service 'Load_os_resources', 1--Anzeige der indizierbaren Dokumenteselect*from sys.fulltext_document_types

 

Nun Steht die Volltextsuche inkl semantischer Suche voll zur Verfügung. 

Image may be NSFW.
Clik here to view.
image

In den Volltexteigenschaften der Tabelle nur noch Häkchen setzten und den richtigen Typ wählen.

File_type für den Inhalt der Dokumente. In diesem Screenshot handelt es sich um eine Filetable.

 

Image may be NSFW.
Clik here to view.
image

Ergebnismengen zusammenfassen–SQL

Ab und zu besteht die Herausforderung im SQL Server Daten aus mehreren Abfragen in einem Ergebnis darzustellen. Hier behilflich ist uns der Union-Operator.

Wenn Sie nun Ihre Kunden, aus Performance-Gründen,  in mehrere Tabellen aufgeteilt haben und nun eine Abfrage über alle Kunden benötigen, hilft Ihnen hier das UNION. Wichtig ist hier das die Anzahl der Spalten des Select-Statements gleich sind, ebenso das die Datentypen der Spalten miteinander vergleichbar sind.

 

Image may be NSFW.
Clik here to view.
Union

Das Union würde nun alle Datensätze der zwei Tabellen ausgeben, allerdings OHNE Duplikate.
Ergo: Die zwei roten Teile und EINMAL der grüne Bereich.

Select Shipcountry from Orders
Union
Select Country from Customers


Wer die Duplikate mitnehmen will, ersetzt das UNION durch UNION ALL und volià.

Bei diesem Syntax gibt es allerdings einige Sachen zu beachten. Wenn man zum Beispiel einen Alias auf die Ausgabespalte setzen will, muss der Alias in der ersten Select-Anweisung gesetzt werden.

Select Shipcountry as Land from Orders
union
Select Country from Customers


Mit diesem Statement würde die Ausgabespalte wie folgt aussehen.

Image may be NSFW.
Clik here to view.
Ausgabe mit Alias

Ein weiterer Fallstrick ist das ORDER BY, welches nur ganz unten im Statement auftreten darf.

 

Select Shipcountry from Orders
union
Select Country from Customers
Orderby ShipCountry


Falls Sie nun die zusammengefasste Menge in eine Tabellen wegschreiben wollen würde der Syntax so aussehen:

Select Shipcountry into DemoTab from Orders
union
Select Country from Customers



Neben UNION und UNION ALL gibt es noch zwei weitere Operatoren die auf diesem Prinzip basieren und eine sehr tolle Funktionalität bieten.


INTERSECT & EXCEPT

Beim INTERSECT werden alle Datensätze angezeigt die in allen beteiligten Tabellen gleich sind, die Schnittmenge.

 Image may be NSFW.
Clik here to view.
Intersect

Mit INTERSECT wäre die Ergebnismenge der rot eingefärbte Bereich, allerdings wird zusätzlich noch ein DISTINCT angewendet, was heißt das jeder Datensatz nur einfach vorkommt.

Mit EXCEPT lassen sich Datensätze zurückliefern die in der ersten Tabelle vorhanden sind und in der zweiten Tabelle fehlen. Wichtig ist hier die Reihenfolge der Select-Statements, denn es werden die Datensätze der Tabelle angezeigt die im ersten Select stehen!

Image may be NSFW.
Clik here to view.
Except

Hier wäre wieder die roteingefärbte Menge das Ergebnis.

Die verschiedenen Operatoren können auch in Kombination verwendet werden, doch könnte es sein das Sie womöglich Klammern einsetzen müssen um zum richtigen Ergebnis zu kommen.

Falls es unter Ihnen jemanden geben sollte der solche Herausforderungen ohne EXCEPT oder INTERSECT löst, würde ich diesen jemand bitte dazu bewegen es sich nochmal zu überlegen.

Hier ein paar Beispiele im Vergleich.

-- Except
Select Shipcountry from Orders
Except
Select Country from customers
 
-- Exists
Selectdistinct Shipcountry
from orders o
wherenotexists (Select 1 from customers c
where c.Country = o.ShipCountry)
 
-- Left Join
Selectdistinct shipcountry
from orders o
leftjoin customers c
on c.Country = o.ShipCountry
where C.Country is null


Und nun die dazugehörigen Ausführungspläne:

Image may be NSFW.
Clik here to view.
Ausführungspläne except

Die Ausführungspläne sprechen für sich. Die Variante mit Except ist laut Ausführungsplan die günstigste. Mit Intersect sieht es nicht anders aus.

Viel Spaß damit Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley

Schnellere Inserts mit Index

Auch wenn sich die Überschrift nach einem verspäteten Aprilscherz anhören mag, muss ich Sie leider enttäuschen. In diesem Blogeintrag geht es darum, die Inserts auf eine Tabelle zu beschleunigen. Viel Spaß beim Lesen.

Wie viele von euch vielleicht noch wissen, ging es in meinem Blogeintrag vom Oktober '13 um den SQL-Datentyp uniqueidentifier und dessen Nutzung als Primary Key-Spalte und somit als clustered Index. Laut dieses Blogeintrages sollte der uniqueidentifier nur im äußersten Notfall, für eine offline Anwendung oder globale ID, verwendet werden: Primary Key als GUID

Mittlerweile habe ich ein Szenario gefunden, indem es sogar von Vorteil wäre den Primary Key als uniqueidentifier und somit als clustered Index zu hinterlegen.

Problematik

Der uniqueidentifier-Datentyp ist leider viel zu groß, immerhin verbraucht er ganze 16byte Speicher pro Eintrag. Hinzu kommt das die Spalte des clustered Index in jedem non clustered Index enthalten ist, was die Tabellen/Datenbanken nur unnötig aufbläht.

Genaueres finden Sie in dem oben aufgeführtem Link.


Vorteil der GUID

Bestimmte Gegebenheiten müssen gewährleistet sein, damit die Vorteile der Performance optimal genutzt werden können. Umso mehr geschrieben wird, umso besser.

Das ich so was mal im Zusammenhang mit Indizes schreibe, hätte ich nie gedacht Image may be NSFW.
Clik here to view.
Zwinkerndes Smiley


Der Satz zuvor deutet schon ungefähr an um was es geht. In diesem Zusammenhang beschleunigt der Index das schreiben. Es müssen viele Inserts von verschiedenen Verbindungen stattfinden um einen Performancegewinn zu spüren.

Wie ist das möglich?

Pages, Pages und nochmal Pages.

Image may be NSFW.
Clik here to view.
Int

Die Grafik zeigt eine Tabelle mit int Spalte, identity-Eigenschaft und clustered Index. auf der int-Spalte.
Da der clustered Index auf der int-Spalte gesetzt ist, sortiert der SQL Server die Einträge aufsteigend. Und genau darin besteht das Problem. Wenn nun mehrere Prozesse in die Tabelle schreiben wollen, können diese nur seriell geschrieben werden. D.h. ein Insert mit dem Wert  “1” sperrt die Page bis der Datensatz geschrieben ist. Erst dann kann der Datensatz “2” geschrieben werden usw..

Nun werden sich einige von Ihnen denken, wieso indiziert man so eine Tabelle mit stark frequentierten Inserts? Denn es gilt folgende Regel: Schreibanteil > Leseanteil = kein Index, doch selbst ein HEAP, eine Tabelle ohne Index, fügt die Datensätze seriell ein.

Nun die Variante mit der GUID

Image may be NSFW.
Clik here to view.
GUID


Hier ein Uniqueidentifier als Datentyp und einen clustered Index auf der Spalte. Eine Sortierung erfolgt hier ebenfalls (in meiner Grafik nicht). Die kleinste GUID steht auf der Page 100, ganz vorne.

Jetzt folgt der Insert. Nehmen wir an es werden drei Datensätze eingefügt. Gehen wir davon aus das jeder diese Datensätze auf einer anderen Page landet, was bei einer GUID nicht unwahrscheinlich ist. Wenn dies so eintreten sollte, können alle drei Datensätze parallel geschrieben werden, was den Durchsatz enorm erhöht. 


Ich hoffe ich konnte Ihnen den uniqueidentifier als clustered Index etwas schmackhafter reden/schreiben. Denken Sie jedoch an das Anwendungskriterium. Es müssen viele Datensätze geschrieben werden und zwar von verschiedenen Verbindungen.

Ich habe mir erlaubt ein paar Tests zu machen, die dieses Szenario bestätigen. Einen etwas erweiterten Beitrag und die Resultate der Tests finden Sie in der neuen Ausgabe der VS1.

Viewing all 110 articles
Browse latest View live