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:
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.
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.
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.
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:
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:
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.
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.
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.
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.. 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.
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.
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.
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.
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.
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.
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.
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.
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.
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)
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:
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:
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:
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:
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. 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:
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
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.
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
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
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
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.
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.
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.
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:
USE [master] GO ALTER DATABASE [ContDB] SET CONTAINMENT = PARTIAL WITH NO_WAIT GO
Interessanter ist schon die Tatsache, dass der Einschlusstyp entweder:
Keine
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.
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.
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:
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
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:
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
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.
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. Sobald die Datenbank geschlossen ist, gibts auch keine Anmeldung mehr.
Coole SacheImage may be NSFW. Clik here to view... und warten mal auf Einschlusstyp FULL
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.
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.
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.
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'
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.
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.
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.
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.(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.
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
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)
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:
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.
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
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
Firewall (Error 1418)
Sollte es dann immer noch scheitern, nochmals gründlich die Firewall prüfen! Image may be NSFW. Clik here to view.
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
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 * fromOPENROWSET('SQLNCLI',
'Server=.;Trusted_Connection=yes;', --. Für lokale Std Instanz
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.
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.
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.
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.
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.
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!
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.
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.
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. 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.
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.