Quantcast
Channel: ppedv Team Blog - SQL
Viewing all 110 articles
Browse latest View live

Ándale, Ándale, der schnellste SQL Server in Mexico

$
0
0

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, indem sie sich nach dem Start des SQL-Servers 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 einem 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, dass die I\O bei In-Memory gegen Null geht.

Dieses Feature erinnert stark an die frühere Variante Tabellen im Speicher zu halten. 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 Gemeinsamkeiten mit DBCC PINTABLE aufweisen.

 

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-Optimized-Tabellen eine Größe von 42MB zustande, bei einem Einfügevorgang 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, dass 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 zwischenzuspeichern. Diese Neuerung bietet eine gute Alternative für Temporäre Tabellen und Tabellenvariablen.

Vorteile gegenüber der Temporären Tabelle/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, dass 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 des eindeutigen Werts 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 compiled 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, dass ich mich beim bucket_count um eine 0 vertan habe, daraus resultierte, dass 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 will, benutzt sie in Verbindung mit natively Compiled Procedures. Allerdings gibt es eine sehr lange Liste zu Statements, die nicht in einer natively Compiled Procedure benutzt werden dürfen. Hier der Link: http://technet.microsoft.com/en-us/library/dn246937%28v=sql.120%29.aspx

Mit einer natively Compiled 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 wie 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 die 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 einen Fortschritt in Richtung Geschwindigkeit gemacht: bei Einfügevorgä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 einen Server mit 32 Kernen und 1 TB RAM schon für unter 50000€ erstehen. Allerdings sollte nicht der Irrglaube entstehen, dass 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

$
0
0

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?

$
0
0

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.

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:

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:

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.

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 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

Plan inline

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

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

$
0
0

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

SQL 2008 Administration Training

SQL Server – Problem beim Einrichten der Spiegelung

$
0
0

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

  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! 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

$
0
0

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

$
0
0

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

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

$
0
0

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.

imageimage

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

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


Ergebnismengen zusammenfassen–SQL

$
0
0

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.

 

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.

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.

 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!

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:

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 Zwinkerndes Smiley

Schnellere Inserts mit Index

$
0
0

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 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.

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

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.

SQL Server 2016 – CTP2

$
0
0

Nach langer Zeit wieder mal eine Artikel von mir.. der mich besonders erfreut. SQL Server 2016 ist nun endlich als Download in der CTP 2 Version verfügbar. Den Link zum Download finden Sie hier: https://www.microsoft.com/en-us/evalcenter/evaluate-sql-server-2016.

SQL 2016 Logo

Natürlich erwartet man von einer neuen Version viele schöne neue Dinge.. und ich muss sagen, enttäuscht worden bin ich nicht:

Hier mal eine kleine Liste mit Neuigkeiten des Datenbankmoduls, die für mich am spannendsten sind:

  • Live Query Plan / Statistik

    Ausführungspläne live beobachten. Pro Operator Sekunden und Status in %.

  • Query Store

    Historisierung der Abfragepläne, Performance Data, Erkennung teurer bzw. auch schlechter werdenden Pläne). Nicht mehr umständlich den flüchtigen Plancache per T-SQL analysieren müssen.

  • Availability Groups

    nun auch für Standard Edition – yeah!! limitiert auf 2 Knoten

  • Polybase

    eigtl aus Parallel Data Warehouse stammend. Unstrukturierte Daten direkt aus dem Datenbankmodul abfragen

  • Stretch Database

      “Warme” Daten bleiben auf dem System, kalte können automatisch in Azure Datenbanken abgelegt werden

  • JSon Support

    keine umständlichen Programmierung mehr mit CLR, sondern nativer Support

  • Row Level Security
    Mittels einer Securitypolicy werden bei Abfragen auf Tabellen nur noch die Zeilen ausgegeben, die per Richtlinie erlaubt sind. Die Richtlinie selbst besteht aus ein Funktion, die das Filterprädikat vorgibt und einer der einer Tabelle zugewiesenen Richtlinie. Die Daten werden anschliessend nur noch maskiert ausgegen. Also keine Sicht oder ähnliches Notwendig.

  • Always Encrypted
    Nicht zu verwechseln mit TDE, aber gleicher Gedanke. Daten sind nie im Plaintext einsehbar, sondern auch bei Abfragen und im Netzwerkprotokoll verschlüsselt.

  • InMemory Verbesserungen

    Columnstore Index auf inMemory Tables—Wau!!
    benutzerdefinierte Skalarwertfunktionen. 
    ALTER TABLE  Juhuu! Ändern des Bucket count, hinzufügen, Ändern, Löschen von Spalten oder contraints
    ALTER PROCEDURE für native kompilierte Prozeduren
    Fremdschlüsselbeziehungen auf inMemory Tabellen .. endlich kann man von DB Design sprechen!

  • Columnstore Index

    Non-Clustered IX nun auch updatebar
    Auch bei einem Clustered Columnstore IX zusätzlich B-Tree Indizes möglich
    Gefilterte Indizes
    IX Reorganize supportet
    Snapshot und Read Cmommited Snapshot Isolation Support

Die CTP2 macht bereits einen sehr gute Eindruck und enthält viele Neuerungen, die einerseits längst überfällig waren, wie Rowlevel Security oder Always Encrypted (der Administrator kann die Daten nicht mehr im plain text sehen) oder auch sinnvolle Ergänzungen, die einem das Leben mit SQL Server leichter machen. Weiter so!

SQL Server 2016 Schritt für Schritt–Installation und First Look

$
0
0

SQL Server 2016.. habe ich schon erwähnt, dass ich den ziemlich cool finde?

Wollen wir mal Schritt für Schritt den neuen SQL Server unter die Lupe nehmen.

Installation

SQL Server 2016 läßt sich wie gewohnt installieren. Allerdings fallen natürlich ein paar Dinge gleich ins Auge:

Zu den Instanzfeatures zählt nun PolyBase.

clip_image001

Allerdings muss dazu auch die Java Plattform installiert werden..

clip_image002

Die Idee, bereits bei der Installation, die best practice Gedanken umzusetzen, wurde bereits sehr gut in SQL den vorherigen Versionen umgesetzt. Trennung der Daten von den Logfiles und Tempdb eigenes Laufwerk. Nun wird ebenfalls gleich empfohlen, der tempdb mehrere Datein zuzuweisen..

clip_image003

Ansonsten bleibt des Setup identisch.

Database

Auf den ersten Blick fällt dann auch gleich auf, dass Eigenschaften einer Datenbank eín wenig zugelegt haben. So läßt sich der QueryStore in den Settings der DB aktivieren.

clip_image005

Interessanterweise ist Mirroring immer noch Bestandteil des SQL Server 2016. Ein Wegfall würde mir allerdings nicht mehr allzu schwer fallen, da AlwaysOn Availability Groups nun auch in der Standard Version enthalten sind. (Begrenzt auf 2 Knoten)

clip_image007

Innerhalb der Datenbank finden sich nun die neuen Features, wie External Ressources für External Tables, sprich Polybase, Query Store, der die Top Queries bzw schlecht performanden Queries anzeigen kann.

clip_image009

image

QueryStore

Anstatt mühseelig den Plancache per TSQL abzufragen bzw. die Pläne sogar zu archivieren und zu analysieren, steht nun der QueryStore zur Verfügung.

image

Mit verschiedenen vorgefertigten Berichten, kommt man so den teuersten, oder sich verschlechternden Abfragen auf die Schliche.

image

Temporal Tables


Versionierung der Daten via Temporal Tables: Ohne großen Aufwand , den verlauf der Daten speichern.

image

Live Query Statsistics

Wunderbar zu beobachten, an welcher Stelle die Abfrage gerade arbeitet. Pro Operator die Anzeige der Dauer in Sekunden und % Abgeschlossen.

image

Super.., oder?

Nächstes Artikel:Temporal Tables.

Schritt für Schritt: SQL Server 2016 – temporal tables

$
0
0

Nein, bitte nicht verwechseln: temporal tables haben nichts zu tun mit temporary tables table variables und dergleichen. Im Gegenteil, die Daten  einer temporal table bleiben bestehen – auch nach Neustart. Also was sollte denn dann der Zweck sein? Kurz und einfach gesagt, stellen temporal tables den historischen Verlauf von Datensätze dar. In SQL auch "System versioning" genannt.

Das Prinzip dahinter ist schnell erklärt: Tabellen, für die ein Verlauf der Datensätze organisiert werden soll, benötigen zwingend 2 Spalten vom Typ datetime2, die den Gültigkeitszeitraum eines Datensatzes markieren. Zu diesen Tabellen wird außerdem eine weitere Tabellen benötigt, die über dasselbe Tabellenschema verfügt und den historischen Verlauf der Datensätze speichert.

Weder die Tabelle mit den historischen Werten, noch die Funktion, welche eine Änderung der Daten abfängt und die "alten" Datensätze in die Verlaufstabelle schreibt müssen tatsächlich selbst geschrieben bzw. angelegt werden.

Wollen wir mal einen Blick darauf werfen..

System_Versioning aktivieren

Das Aktivieren des SYSTEM_VERSIONING geschieht als Erweiterung des CREATE TABLE Statement.

Create table contacts 
(
Cid intidentityprimarykey,
Lastname varchar(50),
Firstname varchar(50),
Birthday date,
Phone varchar(50),
email varchar(50),
ValidFrom datetime2 Generated always asrow start notnull,
ValidTo datetime2 Generated always asrowendnotnull,
Period for system_time(Startdatum, Enddatum)
)

with (system_Versioning = ON (History_table=dbo.Contactshistory))
  

Essentiell sind natürlich die Zeile, die den Gültigkeitsbereich definieren. ValidFrom gibt dabei den Zeitpunkt an, seit wann dieser Datensatz in der Form existiert und ValidTo bis wann er gültig ist. Im Falle der Originaltabelle wird das datum auf das Jahr 9999 gesetzt, was für die meisten einen gewissen beruhigenden Charakter darstellt. Die Spaltennamen dürfen sind frei wählbar. Die Option GENERATED ALWAYS kann auch mit dem Merkmal HIDDEN verwendet werden. Das ist dann erforderlich, wenn Sie die beiden Spalten für den Gültigkeitsbereich standardmäßig immer versteckt halten wollen. Zumindest thereotisch.. in der aktuellen CTP2 wurde das wohln noch nicht implementiert und quittiert daher mit einem Syntaxfehler.

ValidFrom datetime2 GENERATED ALWAYS ASROW START HIDDEN notnull,
ValidTo datetime2 GENERATED ALWAYS ASROWEND HIDDEN notnull,


PERIOD gibt die zu verwendenden datetime2 Spalten für den Gültigkeitsbereich an

Period for system_time(ValidFrom, ValidTo)

..und folgende Zeile definiert die Versionstabelle. Diese kann bereits vorher erstellt worden sein. Falls nicht wird Sie in dem moment erstellt. Gibt man gar keine Versionstabelle so weist der Name folgendes Muster aus: MSSQL_TemporalHistoryFor_<objectid>

with (system_Versioning = ON (History_table=dbo.Contactshistory))

Und so sieht das Ergebnis im SSMS aus:


Natürlich kann auch nachträglich eine Tabelle als SYSTEM_VERSIONING initialisiert werden.


Aktivierung des SYSTEM_VERSIONING bei bestehenden Tabellen

Die Initialisierung von temporal tables bei bestehenden Tabellen vollzieht sich ebenfalls rel. Einfach und ist mit obiger synteax auch recht gut naschvollziehbar:

Fall 1: Eine Tabelle hat bereits, warum auch immer, zwei Spalten die für den Gültigkeitsbereich stehen:

CREATE
TABLE Demo2

(

SP1 intidentityprimarykey,
SP2 int,
StartFrom datetime2notnull, EndTo datetime2notnull

);

--Aktivierung der PERIOD

ALTER
TABLE demo1
ADD PERIOD FOR SYSTEM_TIME(StartFrom,EndTo)

--Aktivierung des SYSTEM_VERSIONING

ALTER
TABLE demo1
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.demohistory, DATA_CONSISTENCY_CHECK = ON))
 

Fall 2: Die Spalten für das SYSTEM_VERSIONING sind noch nicht vorhanden..

--Alternativ, wenn die Start und End-Zeitstempelspalten noch nicht vorhanden sind

CREATE
TABLE Demo3
(
SP1 intidentityprimarykey, SP2 int
)
 

ALTER
TABLE demo3
ADD PERIOD FOR SYSTEM_TIME (StartFrom, EndTo),
StartFrom datetime2 GENERATED ALWAYS ASROW START NOTNULLDEFAULTGETUTCDATE(),
EndTo datetime2 GENERATED ALWAYS ASROWENDNOTNULLDEFAULTCONVERT(DATETIME2,'9999.12.31');
     

ALTER
TABLE demo3
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.demo3history, DATA_CONSISTENCY_CHECK = ON))
 

Arbeiten mit temporal tables

Nun wollen wir mal mit dem SYSTEM_VERSIONING arbeiten. Zuerst ein paar Datensätze zum Spielen

insert
into Contacts
(Lastname,Firstname,Birthday, Phone, email)

select'Kent', 'Clark','3.4.2010', '089-3303003', 'clarkk@krypton.universe'

insert
into Contacts
(Lastname,Firstname,Birthday, Phone, email)

select'Wayne', 'Bruce','3.4.2012', '08677-3303003', 'brucew@gotham.city'

Und nun die Änderungen, die zu einer Versionierung der Datensätze führt

update contacts set email ='wer@earth.de'where cid = 1
update contacts set Phone ='w3434'where cid = 1
update contacts set Lastname ='Wayne'where cid = 1

--etwas später

update contacts set email ='asas@earth.de'where cid = 1
update contacts set Phone ='w34sasaa34'where cid = 2
update contacts set Lastname ='Smith'where cid = 1

Das Ergebnis:

select*from contacts
s
elect*from ContactsHistory


In der Originaltabelle findet man logischer Weise den aktuell gültigen Datensatz wieder. Die Zeitstempelspalten geben den Bereich an, seit wann der Datensatz gütlig ist. In den Verlaufstabellen dagegen, befindet jeder geänderte Datensatz – ebenfalls mit den Zeitstempelspalten für seine Lebensdauer.

Versionen finden

Das Praktische wollen wir natürlich nicht außer Acht lassen: Die Suche nach einer bestimmten Version.

Entweder man frägt die temporal table direkt ab:

--Abfrage auf Versiossverlauf

select
*
from contactshistory

where

    Startdatum >='2015-06-28 09:17:04'

    and

    Enddatum <='2015-06-28 09:17:50'


Das ist natürlich dann empfehlenswert, wenn man den Verlauf eines Datensatzes sehen möchte.

Sofern nur die letzte gütlige Version zu einem Zeitpunkt gesucht wird, kann man das auch direkt von der Originaltabelle erfahren.

--Coole Abfrage: direkt auf Originatabelle

select
*
from contacts

    FOR SYSTEM_TIME ASOF'2015-06-27 15:49:11.1448833'
    where cid =1


Nachträgliches Ändern von SYSTEM_VERSIONING Tabellen

Haben wir was vergessen? Ach ja: kann man versionierte Tabellen auch nachträglich ändern? Klar! System_Versioning ausschalten, Änderungen auf beiden Tabellen ausfürhren, SYSTEM_VERSIONING wieder einschalten. Guggste..

--SYSTEM_VERSIONING deaktiveren

Alter
Table contacts set (system_versioning= off)

--Beliebige Spalten auf beiden Seiten hinzufügen

Alter
Table contacts add Fax varchar(50)

Alter
Table contactshistory add Fax varchar(50)

--und wieder einschalten

ALTER TABLE contacts

SET (system_versioning=on (History_table=dbo.Contactshistory))

Deaktivieren des SYSTEM_VERSIONING bzw Löschen der Verlaufstabelle


Und was, wenn ich die Versionierung nicht mehr haben will. Dann SYSTEM_VERSIONING ausschalten und Tabellen jeweils löschen.

alter
table Contacts Set (system_versioning=OFF)

GO

drop
table contacts

drop
table Contactshistory

Randnotizen

Abgesehen Davon, dass ich teporal tables extrem einfach in der Anwendung finde, bleiben noch ein paar Details zu erwähnen übrig.

  • Temporal tables sind per default zeilenkomprimiert
  • Die Originaltabllen müssen einen Primary key haben
  • Löscht man den Clustered IX der temporal Tabelle, dann kann man auch Clustered Columnstore IX verwenden –absolutly perfect! 12 Punkte!!
  • Geht nicht bei Filetables
  • Kein truncate table bei SYSTEM_VERSIONING = ON
  • Kein instead of Trigger
  • Support für AlwaysOn

Coole Sache..

Demnächst mehr mit Schritt für Schritt: SQL 2016

SQL Geography Datentyp Entfernung kalkulieren

$
0
0

Stolpere grad über ein Problem mit net und der Spatial Datentypen. Damit kann an Orte definieren und deren Entfernung berechnen. Im Microsoft SQL Server wird der Breitengrad und Längengrad, also Latitude und Longitude gespeichert. Gleiches erhält man bei Nutzung von Bing oder Google Maps als Koordinaten.

image

Nun zwei Varianten der Berechnung der Entfernung per SQL Statement. Hier wird die Distanz zwischen zwei Punkten über eine Kugel gerechnet. Die Funktion STLength liefert die direkte Verbindung und damit einen kleineren Wert.

SQL  Point ( Lat, Long, SRID )

   1:  DECLARE @g geography;
   2:  DECLARE @h geography;
   3:   
   4:  SET @g = geography::Point(48.206501 ,16.378343, 4326);
   5:  SET @h = geography::Point(48.16988, 12.830674, 4326);
   6:  SELECT @h.STDistance(@g);

Ergebnis 263789,75902209

Für Entity Framework oder auch SQLCommand muss man einen String umwandeln um dann per DbGeography.FromText die Koordinaten zu übergeben. Im Kern wird dazu dieses TSQL Statement genutzt.

   1:  DECLARE @g geography;
   2:  DECLARE @h geography;
   3:  SET @g = geography::STGeomFromText('POINT(48.206501 16.378343)', 4326);
   4:  SET @h = geography::STGeomFromText('POINT(48.16988 12.830674)', 4326);
   5:  SELECT @h.STDistance(@g);
Ergebnis 392552,427707221

Das erste Ergebnis ist korrekt und beim zweiten offensichtlich Längengrad an erster Stelle zu nehmen. Die Reihenfolge ist also Längengrad und Breitengrad als vertauscht.

Schritt für Schritt: SQL 2016 - Dynamic Data Masking

$
0
0

clip_image002

Es weihnachtet! Gerade bekam ich von einer Kollegin Plätzchen angeboten mit der Größe eines Diskus und ca 2,5 Milliarden Kalorien. Ich sehe schon das Hüftgold anrollen, oder wie die Franzosen es liebevoll nennen: „poignées d'amour“. Der Brite übrigens love handles. Was mich zu dem Schluss bringt, dass wir in good old germany scheinbar ein Problem mit unseren Körperzonen haben.

clip_image004

Solche Problemzonen gibt’s aber nicht nur zu den Feiertagen und auch nicht nur um die Hüfte. Im konkreten Fall spreche ich vom Hüftspeck Marke „geheime Daten“.

Bisher war es gelinde gesagt, etwas umständlich Leseberechtigten den Zugriff auf bestimmte Spalten zu nehmen oder die Daten zu verschlüsseln. Mit SQL Server 2016 (CTP 3.1) wird die Geschichte schon deutlich einfacher.

Wir wäre es denn, wenn wir ohne zusätzlichen Code, die Daten – je nach Berechtigung – verschlüsselt oder unverschlüsselt sehen könnten? Das hört sich für mich nach einem ziemlich coolen Securtiyfeature an. Das sogenannte Dynamic Data Masking – kurz DDM.

Maskierfunktionen

SQL Server 2016 stellt für DDM folgende Funktionen zur Verfügung:

  • · Default():
    Gibt bei Textdatentypen entweder XXXX aus oder weniger, falls das Feld eine geringer Größe aufweist
    Bei numerischen Daten wird eine 0 ausgegeben.
    Bei Datumsfeldern das entgegen der Doku der 1.1.1900
  • · Email():
    maskiert Emailadressen in folgender Form: mXXXX@XXXX.com. Lediglich der erste Buchstabe der Emailadresse wird korrekt ausgegeben, der Rest wird „ausgext“ und mit der Domäne .com versehen
  • · Random():
    Für numerische Datentypen eine Zufallsmaskierung, bei der Zufallszahlen innerhalb des Angegeben Start- und Endwertes zurückgegeben werden
  • · Custom():
    Hier können sie ein Prä- und Suffix angeben und in welcher Form der Teil dazwischen dargstellt werden soll. Wie etwa partial(2,“XXX“,3) die ersten 2 und die letzten 3 Zeichen korrekt ausgibt. Der Teil dazwischen mit dem gewünschten string maskiert wird.

Wollten wir nicht die Problemzonen entfernen? Nun denn, wie geht’s?

Anlegen der Demotabelle

 

Create database DataMasking;
GO

use DataMasking;
GO

CREATE TABLE Angestellte
(AngID int IDENTITY PRIMARY KEY,
Vorname varchar(100) MASKED WITH (FUNCTION = 'partial(1,"XXXXXXX",0)') NULL,
Nachname varchar(100) NOT NULL,
tel varchar(12) MASKED WITH (FUNCTION = 'default()') NULL,
Email varchar(100) MASKED WITH (FUNCTION = 'email()') NULL,
Gebdatum date MASKED WITH (FUNCTION='default()') NULL,
Kennwort varchar(100) MASKED WITH (FUNCTION='partial(5,"XXXXXXX",0)') NULL
);
GO

Arbeiten mit Dynamic Data Mask

Ich sehe die Speckröllchen schon schwinden. Sie auch? In der Tabelle wurden bereits die Maskierungen angegeben. Natürlich kann jederzeit eine maskierte/unmaskierte Spalte hinzugefügt werden.

ALTER TABLE Angestellte
ADD HireDate date MASKED WITH(FUNCTION='default()') NULL;

Oder natürlich auch eine bestehende geändert werden:

ALTER TABLE Angestellte
ADD Gehalt money MASKED WITH(FUNCTION='random(1,12)') NULL;

ALTER TABLE Angestellte
ALTER COLUMN Gehalt MASKED WITH(FUNCTION=’default()') NULL;

..und wenn man möchte bekommt man sie auch wieder los

ALTER TABLE Angestellte
ALTER COLUMN DROP MASKED;

Ergebnisse. Wir wollen Ergebnisse sehen! OK .. guggst du..:

Fügen wir zunächst mal ein paar Daten ein:

INSERT Angestellte (Vorname, Nachname, Tel, Email, GebDatum, Kennwort )
VALUES
('Robert', 'Lindner', '08676-9876', 'robert@mi6.com','5.1.2012','145@qwe'),
('Hans', 'Blatter', '089-984576', 'hans@blatter.de', '6.6.2014','sdfsf324543'),
('Sepp', 'Beckenbauer', '08677-455646', 'derKaiser@fbc.de','7.7.2000','flkjfd@lkdfdl');

Kurze Abfrage:

clip_image006

Haben sie etwas Anderes erwartet? Wir haben das Recht die Daten zu lesen. Für Benutzer mit reinen Leserechten sieht das schon anders aus:

Legen wir einen Benutzer MrX an und statten ihn mit Leserechte aus:

CREATE USER MrX WITHOUT LOGIN;
GRANT SELECT ON Angestellte TO MrX;
EXECUTE AS USER = 'MrX';

SELECT * FROM Angestellte;
REVERT;

clip_image008

Hier kommt der Aha-Effekt. Keine Codeänderung, nur von den Rechten des Benutzers abhängig! Super Sache. Speckröllchen ohne Diät weg. Da schmeckt mir die Schweinshaxe wieder..

Wer Recht hat, hat Recht

Natürlich können wir Benutzern das leserechte auf maskierte Daten geben.. und wieder nehmen:

--Rechte geben..

GRANT UNMASK TO MrX;
EXECUTE AS USER = 'MrX';
SELECT * FROM Angestellte;
REVERT;

--und Rechte wieder nehmen

REVOKE UNMASK TO Mrx;

Und welches recht benötigt man, um GRANT UNMASK bzw REVOKE UMASK vergeben zu können?

Die notwendige Rechte dazu sind: ALTER ANY MASK und ALTERTABLE.

Plan B

Ich nehme an, dass den meisten TSQL Schreiberlingen auch die Problemzone der Funktionen bekannt sind. Nachdem eine Funktion zuständig ist, um die Daten zu maskieren, sollte doch bei Abfragen eine Suche per Index zu einem Scan führen. Das passiert allerdings tatsächlich nicht. Die Daten indiziert werden und gesucht werden. Lediglich die Ausgabe wird verschlüsselt.

Bei Tests auf ca 20 Mio Zeilen ergaben sich keine nennenswerte Unterschiede. Für testzwecke habe ich eine Datumsspalte einmal maskiert und einmal unmaskiert in der Tabelle. Auf beide Spalten wurde die gleichen Indizes angelegt. Non Clustered mit Included Columns, so dass ein Covered Index entstehen müßte.

Das Ergebnis:

In beiden Fällen, ob Benutzer mit Leserecht oder auch ohne bekommen wir einen IX Seek.

clip_image010

Die Statistikmessungen kommen zum selben Ergebnis:

clip_image012

Auch im Falle einer größeren Menge (1 MIo Ergebniszeilen) ändert daran nichts. Auch keine höhere CPU Last.

Cool… noch L

Was geht ab, Mann

Oder was geht eben nicht ab.. Mit folgenden Limits müssen wir aktuell in der CTP 3.1 leben..

Kein Support von DDM für

  • · Encrypted columns (Always Encrypted)
  • · FILESTREAM
  • · COLUMN_SET

Ebenfalls sinnlos ist für Mrx die Daten zu kopieren. Mittels eines

SELECT * into Angestellte2 FROM Angestellte;

bekommt man lediglich maskierte Daten in die Zieltabellen. Die Maskierfunktionen werden nicht übernommen.

Ein Update wird MrX zweifelsohne gelingen, aber die Daten wird er wegen der Maskierfunktion nicht lesen können.

Der „Bug“ aus früheren CTPs, dass ein Konvertieren in einen von DDM nicht unterstützten Datentyp, die Daten im Klartext anzeigt, war in der CTP 3.1 bisher nicht mehr nachzuvollziehen. Vor allem, da jetzt alle wichtigen Datentypen supported sind.

Ohje - Weiter Problemzonen

Nun schien doch alles in Ordnung zu sein? Doch genau dort wo wir hurra auf 10 Kilo weniger geschrieben haben, enau dort liegt auch der Hund begraben. (Wieso begräbt eigentlich immer den Hund?)

Schauen wir uns das mal an…:

EXECUTE AS USER = 'MrX';
select * from angestellte where Gehalt between 30001 and 50001
select * from angestellte where gehalt = 50000
REVERT

select * from angestellte where Gehalt between 30001 and 50001
REVOKE UNMASK TO Mrx

clip_image014

Achnee.., oder? Doch! Wer fragt bekommt Antwort. Logischerweise muss es möglich sein die Daten abfragen und korrekt Ergebnisse zu bekommen. Tja das Resultat heißt hier im billigsten Fall: Mit Ausdauer und Fleiß komm ich auch an die Gehälter…

 

Fazit: Tolles Feature, das mit Betracht eingesetzt werden sollte.  So sind erratbare bzw schätzbare Felder rel. leicht herauszubekommen. Dagegen ist das Unkenntlichmachen von Daten wie Email , Kreditkartennummern extrem easy geworden. Also mal schauen, was da noch so kommt in der Finalversion..

 

Viel Spaß damit!


Lambda Expressions und Linq

$
0
0

In einem letzten Artikel (Lambda Expressions) habe ich mich mit dem Zusammenhang zwischen Delegates und Lambda Expressions beschäftigt.
Das dort erlernte Wissen möchte ich heute mit einem Praxisbeispiel bei der Verwendung von Linq anwenden.

Zuerst mal: Was ist Linq?

Stark vereinfacht ausgedrückt ist Linq eine Klassenbibliothek, die uns Erweiterungs Methoden für das Interface IEnumerable und somit für alle Klassen, welche das Interface, implementieren (List<T>, Array, ObservableCollection<T>, usw…) bereitstellt. Damit werden Abfragen auf Collections in C# sehr einfach.
Linq steht für Language integrated query.

Es gibt vier verschiedene Linq Bibliotheken:
- Linq to Object (Arrays und Collections)
- Linq to DataSet (Abfragen auf DataTables in DataSets)
- Linq to XML (Abfragen auf XML Dokumente)
- Linq to SQL (Abfragen auf SQL-Server)

Der große Mehrwert von Linq liegt darn, dass man nicht zwischen den Bibliotheken unterscheiden muss und die Syntax bei allen vieren identisch ist.

Auch MSDN liefert viele Informationen zu Linq.

Was hat Linq jetzt mit Lambda Expressions zu tun?

Betrachten wir zuerst mal die Syntax einer der wichtigsten Methoden von Linq:

publicstaticIEnumerable<TSource> Where<TSource>(thisIEnumerable<TSource> source,Func<TSource, bool> predicate
)

Mit dem Wissen meiner bereits oben genannten Blogeinträge (Lambda Expressions und Erweiterungs Methoden) können wir diese Methode jetzt sehr leicht verstehen.

Erster Parameter:thisIEnumerable<TSource> source
Wir sehen anhand des Schlüsselwort “this”, dass es sich um eine Erweiterungsmethode handelt, welche sich auf den Typ IEnumerable<T> bezieht.

Zweiter Parameter: Func<TSource, bool> predicate
Der zweite Parameter verrät uns, dass es sich um ein delegate handelt. Passen würde jede Methode, welche ein Objekt vom Typ TSourceübernimmt und einen booleschen Wert zurückliefert.
TSource ist natürlich genau der Typ, von dem auch die abzufragende Collection ist.

string[] namen = newstring[] { "Sepp", "Klaus", "Karina", "Magdalena", "Judith" };IEnumerable<string> result = namen.Where(name => name.StartsWith("K"));

Wie man gut erkennen kann, kann hier für den Delegate-Parameter eine Lambda Expression übergeben werden.

Beispiele für weitere Linq-Abfragen: 101 Linq Samples

Power BI Reports in SQL Server Reporting Services On-Premises

$
0
0

Im Oktober auf dem PASS Summit 2016 hat Microsoft die Technical Preview von Power BI Reports in SQL Server Reporting Services (SSRS) On-Premises vorgestellt und veröffentlicht. Somit schafft Microsoft einen großen Mehrwert für viele Kunden, welche nicht in die Cloud gehen “können”. Eine neue Vorlage, mit dem Stand Januar 2017- “Vorschau der Power BI Report-Funktionalität in SQL Server Reporting Services” ist nun verfügbar. In diesem Beitrag stelle ich Ihnen diesen Stand aus dem Azure-Marketplace vor. Ein weiterer Artikel wird folgenden, indem auf Basis vom SQL Server 2016 SP1 das gleiche Preview vorgestellt wird.

Die folgende Umgebung ist nur für die Entwicklung und das Testen lizenziert, nicht für die Produktion. Es handelt sich um eine vorkonfigurierte Umgebung, die alle SQL Server-Komponenten (Berichtsdienste, Datenbankmodule, Analysis Services), Client-Tools (Power BI Desktop, Mobile Report Publisher, Berichts-Generator, SQL Server-Datentools) und Demoinhalte enthält.

Filter nach “Power BI”:

image

Auswahl und bestätigen des Technical Preview:

image

Basis Konfiguration:

image

Anmerkung: Es wird eine Tabular oder Multidimensional SSAS Instance unterstützt.

Konfiguration Storage:

image

Anmerkung: Habe die vorgeschlagene VM nicht geändert, jedoch bei Storage unter “Performance” von Premium auf Standard umgestellt. Da sonst auch bei ausgeschalteter VM, kosten für den Premium-Storage anfallen.

Zusammenfassung bestätigen:

image

Nutzungsregeln bestätigen:

image

 

Nach der Bereitstellung und dem Connect, können Sie über das Icon “SSRS Preview” sofort starten:

image 

image

 

Drei Power-BI Reports stehen zum testen zur Verfügung:

image

image

image

image

Weitere Blogartikel zu den technischen Details werden folgen.

Wer mehr erfahren möchte, ist in diesem SQL Server Reporting Services Kurs genau richtig.

Viel Erfolg und Spaß beim Testen!

SQL – Datum, Format() und Convert()

$
0
0


Womit kann man jeden Programmierer zum Weinen bringen? Richtig: wenn es ums Datum geht. In SQL ist das aber gar nicht so dramatisch und sicher nichts, wovor man Angst haben muss. Wir sehen uns in diesem Beitrag an, wie man in SQL mit Datumsfunktionen arbeiten, das Datum im gewünschten Format ausgeben und welche Datentypen man dafür verwenden kann.

 

Die gebräuchlichsten Datumsfunktionen in SQL




Mit diesen Funktionen kommt man schon ziemlich weit. Wer sich damit noch nicht zufrieden geben will: Eine Komplettübersicht über alle Datumsfunktionen findet Ihr in der Microsoft-Dokumentation.


SYSDATETIME() und GETDATE() – aktuelles Datum und Uhrzeit

Mit SYSDATETIME()und GETDATE() könnt Ihr das aktuelle Datum abfragen; dabei ist GETDATE() auf mehrere Millisekunden und SYSDATETIME() auf Nanosekunden genau. Ein weiterer nicht unerheblicher Unterschied ist der verbrauchte Speicherplatz; SYSDATETIME() verbraucht dabei 6-8 Bytes (entsprechend weniger, wenn die Millisekunden oder gar die Zeit gar nicht benötigt wird), GETDATE() braucht 8 Bytes.

Wer jetzt meint, Speicherplatz ist billig und auf das bisschen kommt es wohl auch nicht an: die Menge machts. Bei potenziell Millionen von Einträgen kann die konsequente Verwendung von optimalen Datentypen sehr wohl einen Unterschied machen. Aber das ist ein anderes Thema.

  

DAY(), MONTH(), YEAR() und DATEPART() – Datumsanteile abfragen

Mit DAY(),MONTH() oder YEAR()kann man sich den entsprechenden Datumsanteil als Integer ausgeben lassen; ebenso mit DATEPART(). Bei letzterem muss allerdings noch spezifiziert werden, welchen Datumsanteil man denn gerne hätte, und abhängig vom Datentyp des verwendeten Datums lassen sich bis zu Nanosekunden ausgeben.




DATEPART(day,date),DATEPART(month,date) oderDATEPART(year,date) führen zum exakt gleichen Ergebnis wie DAY(date),MONTH(date) oder YEAR(date).

 

DATENAME() – Wochentag oder Monatsname

Mit DATENAME()kann man sich den Namen eines Wochentages oder den Monatsnamen ausgeben lassen. Für andere Ausgaben macht DATENAME() nicht viel Sinn, da wir für alle anderen wieder Zahlen herausbekommen.



DATEADD() – Daten addieren

Mit DATEADD()kann man eine Zeitspanne zum angegebenen Datum hinzuaddieren. Wird eine negative Zahl eingegeben, wird diese Zeitspanne vom angegebenen Datum abgezogen.




DATEDIFF() – Differenz zwischen zwei Daten

Mit DATEDIFF()kann man eine Datumsdifferenz zwischen zwei Daten ermitteln. Abhängig von der Abfrage bekommt man einen Integer-Wert zurück, der für die Differenz in der angegebenen Größenordnung (Jahre, Tage, …) steht. Liegt das Enddatum vor dem Startdatum, ist der Ausgabewert negativ.





Datepart Parameter – was wollen wir abfragen?

 

Damit DATEPART(),DATEADD() und DATEDIFF()funktionieren, müssen wir genau angeben, welchen Datumsteil (datepart) wir abfragen wollen.

Die folgende Tabelle zeigt die wichtigsten Intervalle, die für die Datumsabfragen verwendet werden können.



Leider gibt es dazu ein paar Abweichungen, wenn wir das Datum individueller formatieren wollen; dazu kommen wir noch im Abschnitt „Format“.

 

Die vollständige Liste der datepart- Angaben findet Ihr in derMicrosoft-Dokumentation.


FORMAT() – wie soll etwas ausgegeben werden?

 

Das Problem mit dem Datum, das die bereits erwähnten Verzweiflungsausbrüche bei Programmierern auslöst, sind die vielen unterschiedlichen Formate, in denen das Datum in unterschiedlichen Regionen der Welt dargestellt wird.

Da gibt es YYYY-DD-MM, YYYY-MM-DD, YYYY/MM/DD, DD.MM.YYYY und noch eine ganze Reihe anderer Varianten. Meistens wird die Formatierung des Datums, das aus der Datenbank kommt, clientseitig oder in einem Zwischenschritt, beispielsweise mit C# erledigt. Wenn es aber unbedingt serverseitig passieren soll, haben wir mehrere Möglichkeiten.

 

Die SQL Server Funktion FORMAT() erlaubt uns, sehr individuelle Formatierungen durchzuführen. FORMAT() funktioniert unter anderem mit den Datentypen int,float, money– und eben auch mit Datumsformaten (date, time, datetime, datetime2). Der Datentyp des zurückgegebenen Wertes bei FORMAT() ist nvarchar (oder NULL).


Für einen vollständigen Überblick über alle akzeptierten Datentypen könnt Ihr einen Blick in die Microsoft-Dokumentation werfen.

Die einfachste Variante sieht so aus:



FORMAT() und Datum

Wir geben einen Wert ein, der in ein bestimmtes Format gebracht werden soll, gefolgt vom gewünschten Format. Das geht auch mit Datum…




…allerdings müssen wir das Datum irgendwo auslesen, entweder aus einer Datumsfunktion oder aus einer Tabellenspalte, die ein Datum enthält; die Angabe eines einzelnen konkreten Datums würde hier nicht funktionieren, da dies als Text (varchar) interpretiert wird.

In der Praxis arbeiten wir aber normalerweise ohnehin mit Daten, die aus einer Tabelle oder einer Datumsfunktion kommen, und nicht mit einzelnen, händisch eingegebenen Daten.





FORMAT() und Variablen

Für die Anwendung  von FORMAT()ist auch die Verwendung von Variablen hervorragend geeignet:




Über eine Variable könnte theoretisch auch wieder ein konkretes Datum angegeben werden, da ja hier auch ein Datentyp definiert wird (nicht empfehlenswert):



FORMAT() und der culture-Parameter

Optional kann noch ein weiterer Parameter (culture) eingegeben werden, mit dem sich eine regionsbezogene Formatierung erreichen lässt:




Das kleine 'd' im format-Parameter bedeutet, dass das Datum in Zahlen ausgegeben wird; bei großem 'D' würde der Monat und (je nach regionaler Konvention) auch der Wochentag ausgeschrieben werden, z.B.:



Eine vollständige Übersicht über unterstützte culture-Codes gibt es in der Microsoft-Dokumentation. (Diese Übersicht ist derzeit nur auf Englisch verfügbar, aber die Tabelle ist selbsterklärend.)


Da in vielen Regionen die gleichen Formate für die Datumsanzeige verwendet werden, macht es keinen Sinn, sämtliche verfügbaren culture-Codes anzugeben. Im Gegenteil, das wäre eine massive unnötige Belastung für die Abfrage.




Wer es noch genauer wissen möchte: Die Microsoft-Dokumentation zum Thema FORMAT() findet Ihr hier.



CONVERT() und CAST() – umwandeln von Datentypen

 

Mit CONVERT() und CAST() ist es möglich, einen Datentyp in einen anderen zu verwandeln.

Wir sehen uns hier allerdings nur die Fälle an, die für unser Thema „Datum“ relevant sind.

Mit beiden Anweisungen lässt sich ein Datentyp in einen anderen konvertieren; allerdings ist die Syntax bei CONVERT() und CAST() ein wenig anders und CONVERT() bietet noch die zusätzliche Option von sogenannten Styles.

 

CAST() und Datum

Wollen wir mittels CAST() ein Datum als Text (also etwa als varchar oder nvarchar) darstellen (oder umgekehrt), sieht das so aus:


Es ist möglich, mittels CAST() einen Datumswert als Text darzustellen oder umgekehrt. Mit CAST() allein haben wir allerdings keinen Einfluss darauf, wie unser Datum nun dargestellt wird. Geben wir eine Länge (length) an, beispielsweise varchar(20), müssen wir außerdem noch darauf achten, dass sich das, was wir darstellen wollen, in dieser Anzahl an Zeichen überhaupt ausgeht.

Der umgekehrte Weg, Text in ein Datum umzuwandeln, wäre nicht empfehlenswert (obwohl es theoretisch möglich ist), denn hier haben wir das Problem, dass wir nicht sicher sein können, welcher Wert als Tag oder Monat interpretiert wird. Glücklicherweise kommt dieser Fall in der Praxis so gut wie nicht vor, da wir ja normalerweise Werte aus einer Tabelle auslesen und nicht einzelne Daten händisch eingeben.

 

CONVERT() und Datum

Auch CONVERT() konvertiert einen Datentyp in einen anderen, wie der Name schon sagt, allerdings haben wir hier noch die Möglichkeit, einen sogenannten Style anzugeben, der zugleich auch für ein bestimmtes Format sorgt.




CONVERT(), Datum und der Style-Parameter

Den Style geben wir über eine Zahl ein. Liegt diese Zahl über 100, wird das Jahrhundert mit angegeben (yyyy), sonst nur die Jahreszahl (yy). Damit das nicht zu einfach wird, gibt es natürlich auch hier ein paar Ausnahmen.

In der folgenden Tabelle seht Ihr einen Überblick über einige gebräuchliche Standards und die dazugehörigen Style-Angaben.



Einen vollständigen Überblick über alle Standards findet Ihr wieder in der Microsoft-Dokumentation.


Mit all diesem Wissen ausgerüstet sollte es Euch jetzt leichter fallen, Eure Datumsangaben in das von Euch gewünschte Format zu bringen.

Viel Spaß beim Ausprobieren, und vielleicht sehen wir uns ja in einem unserer Kurse zum Thema SQL!

Was ist eigentlich... MAXDOP?

$
0
0

Die gute oder die schlechte Nachricht zuerst? Ok, die gute: Das Grundprinzip von MAXDOP ist recht schnell erklärt. Die schlechte? Ach ja: Es gibt keine Einheitsformel, keine immer richtige Lösung. Die „richtige Lösung“ müssen wir durch Testen von Fall zu Fall finden. Aber erstmal langsam von Anfang an.

 

Das Grundprinzip von MAXDOP


MAXDOP steht für Maximal Degree of Parallelism und bedeutet nichts anderes, als dass mehrere CPUs parallel, also mehr oder weniger gleichzeitig, an einer Abfrage arbeiten dürfen. Mehr oder weniger ist ein wichtiger Punkt – die Aufgabenverteilung erfolgt nämlich nicht gleichmäßig.

Warum sollten überhaupt mehrere CPUs eine Abfrage bearbeiten? Ist das gut oder schlecht? Zur Veranschaulichung verwenden wir die Jelly Bean-Analogie.

Die Jelly Bean-Analogie

Stell dir vor, du müsstest eine Handvoll Jelly Beans zählen.


1,2,3,…9… schaffst du locker. Aber wie sieht’s damit aus:


Ein ganzer Haufen von Jelly Beans… Klar, schaffst du auch – aber es wird eine ganze Weile dauern. Geht doch viel schneller, wenn wir die Arbeit aufteilen – hol dir ein paar Freunde, und jeder bekommt ein paar Jelly Beans, die er/sie zählen soll.


Wahrscheinlich geht das schneller – aber jetzt haben wir einen zusätzlichen Arbeitsvorgang: Jeder muss sagen, wie viele Jelly Beans er/sie hat und wir müssen die einzelnen Summen zu einer Gesamtsumme addieren. Dieser zusätzliche Arbeitsschritt kostet wieder ein bisschen Zeit.

Außerdem haben nicht alle gleich viele Jelly Beans. (Wenn wir das schon vorab wüssten, bräuchten wir sie ja nicht mehr zu zählen.) Das heißt auch, dass einer schneller mit dem Zählen fertig ist und einer langsamer – aber um zu einer Endsumme zu kommen, müssen wir auf den Langsamsten warten.

 

Jelly Beans und SQL-Abfragen

…und was genau hat das jetzt mit MAXDOP zu tun?

Abhängig davon, wieviel Information bei einer Abfrage verarbeitet werden muss, kann es Sinn machen, die Arbeit auf mehrere CPUs aufzuteilen. Dabei heißt „mehr“ leider nicht immer auch gleich „besser“.

Wenn uns 1000 Freunde helfen, die Jelly Beans zu zählen, dann dauert es auch recht lange, bis wir alle Einzelsummen eingesammelt haben, um sie addieren zu können.


Mit den CPUs und den Abfragen ist es ähnlich: Die einzelnen Ergebnisse wieder miteinander zu verknüpfen braucht Zeit – und auch CPUs müssen auf den Langsamsten warten (also den, der die größte Datenmenge bewältigen muss).

 

 

MAXDOP im SSMS

Ok, wo also finde ich dieses mysteriöse MAXDOP-Setting?


Im Microsoft SQL Server Management Studio (SSMS) klicken wir im Object Explorer mit Rechtsklick auf unsere Datenbank. Dann öffnet sich ein Fenster mit Optionen:


Mit Klick auf den letzten Menüpunkt, Properties:


Wir wählen Advanced aus und scrollen zu „Parallelism“ hinunter:


Unter dem Reiter „Parallelism“ finden wir den Eintrag „Max Degree of Parallelism“… oder MAXDOP. Treffer!

 

MAXDOP Settings

Das Default-Setting für MAXDOP ist 0. Das ist etwas missverständlich, denn es heißt NICHT, wie man vielleicht vermuten würde, dass Null, also kein Parallelismus erlaubt ist, sondern es heißt vielmehr, dass alle verfügbaren CPUs verwendet werden dürfen!

Will man keinen Parallelismus zulassen, müsste man hier 1 einstellen, d.h. es darf nur ein CPU verwendet werden. In manchen Fällen kann das Sinn machen.

Für eine sinnvolle Beschränkung gibt man hier die gewünschte Anzahl an CPUs an, die man für eine Abfrage zur Verfügung stellen will, also beispielsweise 2 oder 4.

Wird Hyper-Threading verwendet, stehen uns hier doppelt so viele Scheduler zur Verfügung, wie physikalisch CPUs eingebaut sind. Als Richtwert könnte man es hier mit der Hälfte der verfügbaren Scheduler versuchen. Also beispielsweise 8 Scheduler -> MAXDOP: 4.

Wird NUMA verwendet, wird es noch komplexer. Hier wird empfohlen, nicht mehr als die Anzahl von Cores in einem NUMA-Node zu verwenden. Das ist aber schon wieder ein anderes Thema – die Microsoft-Dokumentation zum Thema findet sich hier.


MAXDOP in eine Query integrieren

Wir können eine MAXDOP-Anweisung auch direkt an eine Query anhängen – Vorsicht: das gilt nur für die jeweilige Query und überschreibt das Server-MAXDOP-Setting.

Das würde dann beispielsweise so aussehen:

USE AdventureWorks2017

GO

SELECT*

FROM Sales.SalesOrderDetail

ORDERBY ProductID

OPTION (MAXDOP 2)

GO


Cost Threshold for Parallelism

Indirekt können wir MAXDOP auch über die Cost Threshold for Parallelism beeinflussen. Dieses Setting finden wir unter Advanced> Parallelismüber dem Max Degree of Parallelism. Jede Abfrage hat bestimmte „Kosten“. Da es für diesen Kostenwert keine Einheit gibt, wird er oft scherzhaft als SQL-Dollar bezeichnet. (Tatsächlich soll es sich dabei um die Zeit handeln, die diese Abfrage auf einem bestimmten Computer eines bestimmten Microsoft Mitarbeiters in den 90ern gedauert hätte.)

Per Default steht das Setting für Cost Threshold for Parallelism auf 5; die meisten Queries haben aber weitaus höhere „Kosten“. Ist 5 eingestellt, dürfen die meisten Abfragen gleich Parallelismus verwenden; setzt man diesen Schwellwert höher, beispielsweise auf 50, dürfen nur kostspieligere Abfragen auch parallel arbeiten.

Auch das erfordert Tests und einen Blick in die Execution Plans– wieviel kosten unsere Abfragen so im Schnitt? Danach können wir uns bei der Einstellung der Cost Threshold richten.

 

 

Wie können wir testen?

Ok, jetzt wissen wir, wo wir das MAXDOP-Setting verändern können. Wie können wir aber testen, was es uns bringt?

Zum einen brauchen wir dazu eine Datenbank mit einer realistischen Befüllung mit Daten – klar: Wenn wir nur mit wenigen Testdaten Abfrage-Zeiten auslesen wollen, wird das Ergebnis massiv verfälscht.

Zum anderen müssen wir nur ein bisschen durch das SSMS navigieren.

 

Statistik einschalten

Die Statistik einzuschalten ist schon einmal eine gute Idee:

setstatisticsio,timeon

Damit erhalten wir, wenn wir eine Query ausführen, zusätzliche Informationen im „Messages-Tab“ des Ergebnisfensters:


Wir können hier ablesen, wie lange es gedauert hat, bis eine Abfrage ausgeführt wurde (elapsed time), sowie die „CPU time“. Ist die CPU-Time höher als die Geschwindigkeit der gesamten Abfrage, kam offenbar Parallelismus zum Einsatz: Die CPU-Time sagt uns, wie lange mehrere CPUs in Summe an der Abfrage gearbeitet haben.

Beim Testen mit unterschiedlichen MAXDOP-Einstellungen geben uns diese Werte wertvolle Hinweise darauf, mit welchen Einstellungen wir im jeweiligen Fall am besten unterwegs sind.

 

Execution Plan verwenden

Der Execution Plan gibt uns eine Übersicht darüber, wie denn unsere Abfrage so abläuft. Auch hier finden wir Informationen über MAXDOP. Wir können einen Estimated oder Actual Execution Plan verwenden; wie der Name schon sagt, bekommen wir bei ersterem eine informierte Schätzung, bei letzterem eine Analyse der ausgeführten Abfrage. Aber Achtung: die Erstellung des Execution Plans kostet auch etwas Zeit – die Abfragezeit wird dadurch also leicht verfälscht!

Tastenkombination

Einschalten können wir die Pläne über Tastenkombination, Menütabs oder Toolbar; mit STRG + L bekommen wir den Estimated, mit STRG + M den Actual Execution Plan.

Menüleiste

In der Menüleiste wählen wir „Query“ und können uns dort zwischen Estimated oder Actual Execution Plan entscheiden:


Toolbar

In der Toolbar gibt es zwei Icons, die wir für Estimated oder Actual Execution Plan anklicken können:



Execution Plan lesen

Ein Execution Plan für eine (nicht besonders gute) Abfrage könnte etwa so aussehen:


Dabei sagen uns die Doppelpfeile im gelben Feld, dass hier Parallelismus verwendet wurde, und unter „Parallelism“ sehen wir, dass das Zusammenführen der Informationen (Gather Streams) in diesem Fall 19% unserer Rechenzeit verbraucht hat! Vielleicht war in diesem Fall Parallelismus doch nicht ganz so schlau.

Wenn wir im Execution Plan mit der Maus über das Select gehen, sehen wir unter „Estimated Subtree Cost“ die geschätzten Kosten der Abfrage:


… und gleich darüber können wir auch die aktuelle MAXDOP-Einstellung sehen.

 

Properties

Wenn wir im Execution Plan unsere Abfrage auswählen und dann auf F4 drücken, öffnet sich das Properties-Fenster. Hier können wir unter anderem sehen, wie viele Zeilen von jedem CPU gelesen werden mussten.


In diesem Fall liegt ein Unterschied von fast 10.000 Zeilen zwischen dem „Fleißigsten“ und dem „Faulsten“. Entsprechend gravierender können die Unterschiede bei Abfragen aus größeren Datenmengen werden (und entsprechend länger muss auf den Langsamsten gewartet werden).

 

Von Fall zu Fall entscheiden

Wie eingangs schon erwähnt, gibt es keine Einheitslösung. Wo der „sweet spot“ liegt, müssen wir jeweils durch Testen herausfinden. Im Execution Plan können wir sehen, ob Parallelismus verwendet wurde; in der Statistik können wir uns ansehen, wieviel CPU-Zeit die Query verbraucht hat und wie lange die gesamte Query gedauert hat.

Wie viele CPUs verwendet werden dürfen, können wir leicht einstellen; die Herausforderung ist vielmehr, zu entscheiden, wie viele verwendet werden sollten.

Auch eine Überlegung wert: das Verhältnis zwischen CPU-Time und Query-Time. Nicht in jedem Fall ist das Ziel, die Zeit der Abfrage um noch ein paar Millisekunden schneller zu machen – wenn dadurch die CPU-Zeit wesentlich in die Höhe schießt, bedeutet das, dass zwar meine Abfrage recht schnell ist, dass aber möglicherweise nicht mehr genug Ressourcen für andere Prozesse zur Verfügung stehen. Ich hätte dann also eine Abfrage optimiert, blockiere aber alle anderen währenddessen.

 

Weiterführende Infos, MAXDOP für Fortgeschrittene sozusagen, gibt es z.B. in diesem Blogartikel von Matteo Lorini.

Viel Spaß beim Tes… nein, das traue ich mich nicht zu sagen… viel Spaß beim Ausprobieren!

Und vielleicht sehen wir uns ja in einem unserer Kurse zumThema SQL!
























































Was ist eigentlich... ein Clustered Index?

$
0
0

Ihr beschäftigt euch mit SQL und wollt wissen, was ein Index ist und wofür wir das brauchen? Dann seid ihr hier richtig. In diesem Artikel schauen wir uns an, was ein clustered index (gruppierter Index) ist, wie wir einen erstellen und wie er funktioniert.

 

Suchen wie im Telefonbuch

Dieses Beispiel ist für alle geeignet, die alt genug sind, um noch zu wissen, wie ein gutes altes Telefonbuch verwendet wurde (alle anderen: stellt euch ein Lexikon vor… oder irgendein Buch… eines mit richtigen Seiten aus Papier 😉 ).

Angenommen, wir suchen nach der Telefonnummer von Bruce Wayne im Telefonbuch von Gotham City. Angenommen, dieses Telefonbuch hat 1000 Seiten. Kein Problem für uns, denn wir wissen ja, ein Telefonbuch ist alphabetisch erst nach Nachnamen, dann nach Vornamen geordnet. Wir blättern also zu „W“, streifen kurz mal über „Wayne, Alex“ und „Wayne, Amanda“ und schon haben wir „Wayne, Bruce“ gefunden.

 

Wie speichert die Datenbank?

Jetzt stellt euch aber mal vor, die Einträge im Telefonbuch wären nicht alphabetisch geordnet, sondern in der Reihenfolge, in der die Leute dort zugezogen sind. Bei einer so alteingesessenen Familie wäre „Wayne, Bruce“ wohl relativ weit am Anfang zu finden, aber wo genau auf diesen 1000 Seiten? Entsprechend länger dauert es, bis wir etwas gefunden haben.

Aber genau so speichert die Datenbank die Datensätze ab; die Reihenfolge ist mehr oder weniger zufällig, gespeichert wird dort, wo gerade Platz ist.



Dabei sprechen wir von einem „heap“ – die Einträge werden sozusagen alle auf einem „Haufen“ gespeichert.

Die Datenbank müsste also alle Einträge nach „Wayne, Bruce“ absuchen, denn selbst, wenn sie den gewünschten Eintrag zufällig gleich als zweiten findet, kann sie ja nicht sicher sein, dass es nicht irgendwo noch einen anderen Einwohner mit dem gleichen Namen gibt.

Erstellen wir dazu einmal eine Beispieltabelle:


In diese Tabelle fügen wir die oben genannten Personen mit absolut authentischen Telefonnummern ein. Mit einem SELECT-Statement geben wir jetzt den Inhalt der Tabelle aus und lassen uns zugleich den Actual Execution Plan anzeigen:


Die eigentliche Ausgabe ist erwartungsgemäß unspektakulär:


Viel spannender ist für uns ein Blick in den Execution Plan:


Hier sehen wir, dass, obwohl wir nur eine einzige Person abgefragt haben, ein sogenannter Table Scan stattgefunden hat – das bedeutet, dass die gesamte Tabelle gescannt, also abgesucht wurde!

Bei unseren 12 Einträgen hat das natürlich keine erkennbaren Auswirkungen auf die Performance; bei ein paar Millionen Einträgen sieht das aber gleich ganz anders aus.

Wie funktioniert ein Clustered Index?

Beim Telefonbuch können wir uns darauf verlassen, dass, wenn wir „Wayne, Bruce“ unter „W“ gefunden haben, nicht noch andere Personen dieses Namens irgendwo anders versteckt sind. Wir müssen also wesentlich weniger absuchen (nur die Einträge unter „W“) und kommen somit schneller zu einem Ergebnis.

Diese Funktion erfüllt bei der Datenbank der Clustered Index. Er sorgt dafür, dass die Einträge auch in einer (vom Index bestimmten) Reihenfolge abgespeichert werden. Wir suchen also sozusagen nur unter „W“.

 

Datenspeicherung in der Datenbank

Die Daten werden auf sogenannten Seiten (pages) gespeichert. Auf einer Seite werden rund 8KB (8060 bytes) gespeichert. Ein Index sorgt dafür, dass die Daten in einer bestimmten Reihenfolge abgespeichert werden, und nicht als heap. Wir bekommen also einen Verweis auf die Seiten, auf denen unsere gewünschten Daten liegen, und müssen nicht mehr alle absuchen.

 

Navigation mit Index

Wären jetzt unsere Daten alphabetisch sortiert (was ein Index für uns übernehmen kann), könnte die Datenbank also nach „Wayne, Bruce“ suchen, wie wir im Telefonbuch: vorblättern bis „W“, A-V müssen gar nicht erst durchsucht werden.

 

Das kann man sich etwa so vorstellen:


Somit können wir zu der Seite mit den gesuchten Informationen navigieren, ohne alle Seiten lesen zu müssen. Ausgehend vom root node gehen wir über die intermediate nodes zu den eingentlichen data pages:


Die SQL-Anweisung, um einen Clustered Index zu erstellen, ist keine Hexerei:


Wenn wir jetzt noch einmal mittels SELECT nach Bruce Wayne suchen, sieht unser Execution Plan gleich anders aus:


Jetzt wurde kein Table Scan mehr ausgeführt, sondern ein Clustered Index SEEK, eine indizierte Suche.

 

Primary Key und Clustered Index

Im Relationalen Datenbankmodell haben Tabellen normalerweise einen Primary Key (Hauptschlüssel). Dieser erstellt eigentlich klammheimlich im Hintergrund einen solchen Clustered Index für uns – auf der Spalte des Hauptschlüssels. In vielen Fällen ist das auch sinnvoll; will man die Datenbank optimieren, muss man allerdings von Fall zu Fall abwägen.

Eine Tabelle kann mehrere Indizes haben, davon aber nur einen Clustered Index (mit Nonclustered Indexes, also nichtgruppierten Indizes setzen wir uns in einem anderen Artikel auseinander).

Daher sollte schon bei der Planung überlegt werden, ob es im jeweiligen Fall sinnvoll ist, dass der Clustered Index über dem Hauptschlüssel liegt, oder ob man ihn lieber über eine andere Spalte setzen möchte.

Viel Spaß beim Ausprobieren, und vielleicht sehen wir uns ja in einem unserer Kurse zum Thema SQL!




Viewing all 110 articles
Browse latest View live