Glorf.it

Glorf IT

Bedenkliches aus dem IT-Alltag

24. Juni 2007 um 14:56

SQL-Server-2005: OVER() mit Aggregatfunktionen

Im Artikel "SQL Sever 2005: Using OVER() with Aggregate Functions" wird auf SQLTeam.com ein nettes Feature beschrieben, das mir bisher entging:

Man kann (ohne Angabe von Group-By) eine Aggregatfunktion nur auf gruppen berechnen lassen. "Früher" war dazu eine Subquery notwendig oder etwas performanter ein Join mit einer Derived-Table. Jetzt kann das sehr elegant in einem Schupps erledigen.

So steht es in einem Bespiel meiner TSQL-Schulung (Datenbank "Northwind", die mag ich immer noch recht gerne):

select P.ProductName,
P.UnitPrice,
P.CategoryID,
(select avg(Pc.UnitPrice)
from "Products" as Pc
where Pc.CategoryID=P.CategoryID)
as "durchschn. Preis der Produktklasse"
from "Products" as P

So könnte es zukünftig aussehen:

select P.ProductName,
P.UnitPrice,
P.CategoryID,
avg(P.UnitPrice) over (Partition by P.CategoryID)
as "durchschn. Preis der Produktklasse"
from "Products" as P

Allerdings bin ich mit dem Zugriffsplan der zweiten Variante noch nicht wirklich zufrieden. Durch die Subquery hat man einen Nested-Loop-Join, durch das Partition-By einen Sort. Was "besser" ist, kann man so pauschal nicht sagen, möglicherweise wäre ein Clustered-Index auf die Spalten in dem Partion-By ideal.

gefunden im SQL Server FAQ Blog

19. Juni 2007 um 19:23

SQL-Server: Paging von Ergebnismengen

Heute wollte ich einen Trick vorstellen, wie man es schafft eine Ergebnismenge seitenweise zu lesen. Das ist sinnvoll, wenn man in der Anwendung immer nur eine "Seite" voller Datensätze anzeigt. Es wäre in dem Fall Quatsch Tausende von Sätzen zu lesen und am Client dann doch nur eine oder zwei Seiten anzuzeigen.

Leider wird das derzeit schlaueste Verfahren im Artikel "Server Side Paging using SQL Server 2005" auf SQLTeam.com so schön beschrieben, dass ich mir meine Version an dieser Stelle schenke. Allerdings möchte ich ein paar persönliche Bemerkungen ergänzen:

  • Ich würde generell zu einem Index auf dem Sortierkriterium raten, am besten sollte man auf dem Clustered-Index "pagen". Für meinen untigen Trick ist das ein Muss.
  • Es ist wichtig, dass man die Menge der Datensätze mittels der Where-Klause auf die wirklich benötigten Sätze einschränkt, sonst beschäftigt man den SQL-Server unnötig. Aber ich gehe davon aus, dass Ihr das schon macht.
  • Wenn man sehr, sehr viele Datensätze im Ergebnis hat über das man "pagen" will, dann würde ich dazu raten, die Query so abzuspecken, dass nur der Primärschlüssel in der Common-Table-Expression ist. Dann kann die "Zählung" auf dem Sortierkriterium (und der Where-Klausel) mittels "Index Covering" durchgeführt werden. Das Ergebnis wird dann wieder mit der originalen Tabelle verjoint.

Frohes "pagen"…

16. Juni 2007 um 13:31

SQL-Server-2005: Logon-Trigger

Wie ich heute festgestellt habe, wurde mit SP2 zum SQL-Server-2005 Logon-Trigger eingeführt. Sie werden gefeuert bevor die Benutzersitzung erstellt wird. Hier kann man dann Dinge protokollieren. Man kann die Anmeldung aber auch unter fachlichen Gesichtspunkten untersuchen und ggf. ablehnen… 😛

Damit sind solche handgestrickten Lösungen, die auf dem ungeliebten Polling aufbauen, unnötig geworden.

Eigentlich wollte ich eine kleine Beschreibung dazu verfassen, aber weil das schon so schön im " SQL Server FAQ Blog", den ich übrigens auch erst heute entdeckte, beschrieben ist, kann ich einfach darauf verweisen… 😉

Gugst Du hier: "Trigger für Anmelde (Logon) Ereignisse"

Hinweis, falls einer meiner Kollegen auf den "running Gag" kommt: Man kann damit nicht generelle Verbindungsoptionen setzen.

16. Juni 2007 um 13:15

SQL-Server-2005: Kompatibilität mit "Common-Criteria"

Eher zufällig habe ich heute die "Common-Criteria-Kompatibilität" entdeckt. Das ist eine Möglichkeit die Sicherheit des SQL-Servers durch ein paar zusätzlich Details zu erhöhen. Sie erscheinen mir generell sinnvoll:

  • "Residual Information Protection" (mit der makabren Abkürzung "RIP"): Muss der SQL-Server Hauptspeicher abgeben, dann wird er mit Dummy-Daten überschrieben und erst dann freigegeben.
  • Der SQL-Server führt Anmeldestatistiken. Damit kann man "ungewöhnliche" Anmeldemuster entdecken und analysieren.
  • Ein DENY auf eine Tabelle, kann nicht durch ein GRANT auf Spalten dieser Tabelle umgangen werden.

Diese "erweiterte" Option ist laut Doku nur in den Editionen Enterprise, Evaluation und Developer des SQL-Servers-2005 mit Service Pack 2 verfügbar. Leider ist das wohl mit heißer Nadel eingeführt worden, anders kann ich diesen Hinweis nicht erklären:

Zusätzlich zur Aktivierung der Option Common Criteria-Kompatibilität aktiviert müssen Sie ein Skript downloaden und ausführen, mit dem die Konfiguration von SQL Server 2005 SP2 für die Kompatibilität mit der Common Criteria-Auswertungssicherungsstufe 4 (EAL4+) ausgeführt wird. Sie können dieses Skript von der Website Microsoft SQL Server Common Criteria (in Englisch) downloaden.

Weitere Informationen dazu:

13. Juni 2007 um 21:10

Ersatz für TDS

Bei DBA24Hrs wird "SqlNitro" beschrieben. Das ersetzt einfach das TDS-Protokoll des SQL-Servers durch eine eigene Implementierung. Durch die bessere Auslastung/Füllung von TCP/IP-Paketen soll eine bessere Performance erreicht werden.
Nun sind die Netzwerke meine schwache Stelle, aber meiner Ansicht nach sollte es reichen, wenn man darauf achtet, dass man die Paket-Größe des SQL-Servers der Paketgröße von TCP/IP anpasst, oder?
Das sollte mit der Server-Einstellung der "network packet size" oder der Verbindungseigenaschaft "packet size" prima gehen. Microsoft rät allerdings davon ab.

gefunden bei TheDailyGrind
6. Juni 2007 um 20:47

SQL Server: neue Version der "Books Online"

Mein Kollegen Marus amchte mich heute darauf aufmerksam, dass es eine neue Version der "SQL Server 2005 Books Online" gibt. Ganz untypisch für Microsoft wurde die Version 9.00.3062 zwar "May 2007" genannt, aber erst am 4.6.2007 bereit gestellt… 😉

Achtung: bisher ist nur die englische Version neu, die deutsche hat immer noch den Februar-Stand.

5. Juni 2007 um 20:38

Der Klang von kaputten Festplatten

Wer immer schon mal wissen wollte, wie kaputte Festplatten klingen, der wird bei Hitachi Global Storage Technologies fündig. Bei den Geräuschen läuft es mir eiskalt den Rücken runter…

Die Formulierung ist ja wohl auch stark: "may indicate"

There are various noises that may indicate a failing hard drive. If you are experiencing any of the noises, please contact the technical support center at: […]

Danke an meinen Kollegen Hans.
30. Mai 2007 um 18:12

Easy SQL Scripter

Das Werkzeug "Easy SQL Scripter" scheint mir recht nützlich zu sein. Das "SQL Server Management Studio" (SSMS) bietet ja auch schon einige Script-Generierungen an, aber immer nur für einzelne Datenbank-Objekte. Mit dem Plugin wird das SSMS so erweitert, dass man für eine ganze Liste von Objekten die Create-Befehle erzeugt bekommt. Als Bonbon werden auch Insert-Statements für die Daten erzeugt.

Schade ist nur, dass es immer mit dem ganzen Paket "Easy Deploy" installiert werden muss. Deswegen konnte ich mich nicht dazu durchringen, das "mal eben" zu testen, sondern lege es auf meinen Stack…

gefunden bei "The Daily Grind"
29. Mai 2007 um 20:19

ReportViewer Control

Mittlerweile hat es sich herumgesprochen, dass Microsoft mit dem Visual Studio 2005 auch ein ReportViewer-Control bereitstellt, dass man einfach so in seinen Anwendungen einsetzen kann. Damit kann drei Dinge, eines davon wird von Microsoft aber nicht groß beschrieben…

  • Man kann Berichte ansehen, die in einem Reporting-Service aufbereitet wurden.
    Diese Nutzung beschreibt Microsoft super ausführlich. Immerhin wird der Reporting-Service inzwischen sogar mit der Express-Edition ausgeliefert.
  • Man kann in seinem Projekt einen Bericht erstellen und ihn in seine Anwendung reinkompilieren. Das wird von Microsoft leidlich beschrieben. Man kann damit einen Bericht am erstellen und aufbereiten (mit allen schicken Features der Reporting-Services ohne ihn installieren zu müssen). Leider kann man damit nur genau einen Bericht pro Control ansehen.
  • Außerdem kann man den Control dazu nutzen viele verschiedene Berichte anzuzeigen. Im Prinzip kann man sogar beliebige Berichte anzeigen, wenn man bereit ist, den Aufwand zu erbringen. Damit kann man aus den Reporting-Services das schickste nutzen, die Anzeige und das Rendering der Berichte (inkl. Export nach PDF, Excel usw.), ohne eine Server-Anwendung installieren und konfigurieren zu müssen. Warum das von Microsoft nicht an die große Glocke gehangen wird, ist klar, oder? Sie erwähnen zwar mal die Möglichkeit, aber eine Anleitung fand ich nicht…

Zum letzteren fand ich lediglich einen brauchbaren Artikel, der in Visual-Basic kurz skizziert, wie man das anstellen muss: "Take Control of Your Reports with ReportViewer, Part 2". Er ist kostenpfichtig, die Code-Fragmente sind frei zugänglich. Leider wird darin alles in Visual-Basic gemacht. Es hat eine Weile gedauert bis ich das Prinzip verstanden habe. Ich habe folgende Erkenntnisse gewonnen:

Das ReportViewer-Control übernimmt nur die Anzeige des Berichts: also das Rendering und die Aufbereitung der Daten. Alles andere muss die eigene Anwendung erledigen:

  • Zusammenstellen des SQL-Statements, mit dem die Daten gelesen werden. Die Query kann man eigentlich auch aus dem Bericht rauslesen, wenn man sich nicht scheut, sie aus dem XML-File zu entnehmen.
  • Die Übergabe der im Bericht enthaltenen Parameter. In dem Beispiel wird gezeigt, wie man dem Bericht-Objekt die Parameter entlocken kann.
  • Bereitstellen einer Datasource, die dynamisch erstellt werden kann. Nur der Name muss so sein, wie im Bericht angegeben.

Im ersten Schritt kann man so eine Hülle für das ReportViewer-Control schreiben, die dem Entwickler anhand von bestimmten Namens-Konventionen das Leben leicht macht, z.B. DataSource1 oder Param01.
Andererseits ist die Struktur der Berichte (XML-Datei) sehr einsichtig, deswegen kann man sie auch im ersten Schritt auswerten und die Query, deren Parameter und den Namen der Data-Source ermitteln. Im zweiten Schritt muss man das Control lediglich initialisieren und schon hat man ein Universal-ReportViewer-Control.

24. Mai 2007 um 18:18

SQL-Server: Was war denn so los?

Dieser Tage fragte mich ein Kollege, wie man statistische Informationen darüber bekommen kann, also in absoluten Zahlen, was am SQL-Server seit dem letzten Start passiert ist. In den Performance-Countern werden per Definition immer nur die aktuellen Zustande bzw. aktuelle Werte pro Sekunde.

Ich persönlich finde die Informationen der Prozedur "sp_monitor" sehr aussagekräftig:

  • cpu_busy: Die Anzahl von Sekunden, während derer von der CPU des Servercomputers für SQL Server Vorgänge ausgeführt wurden.
  • io_busy: Die Anzahl von Sekunden, während derer von SQL Server Eingabe- und Ausgabevorgänge ausgeführt wurden.
  • idle: Die Anzahl von Sekunden, während derer SQL Server sich im Leerlauf befand.
  • packet_errors: Die Anzahl von Fehlern, die von SQL Server beim Lesen und Schreiben von Paketen festgestellt wurden.
  • total_read: Die Anzahl von Lesevorgängen durch SQL Server.
  • total_write: Die Anzahl von Schreibvorgängen durch SQL Server.
  • connections: Die Anzahl von Anmeldungen oder versuchten Anmeldungen an SQL Server.

usw.

23. Mai 2007 um 19:58

SQL Server: Trailings blanks

When SQL Server compares two strings, it makes a comparison from left to right and treats the shorter expression as though it was padded with spaces to equal the length of the longer string.
For most people the result is unexpected: padded blanks are ignored!

Lets see some samples:

'Hello ' = 'Hello' --> true

The same applies for unicode:

N'Hello ' = N'Hello' --> true

LIKE behaves a little different. Firstly comparing unicode results different:

'Hello ' LIKE 'Hello' --> true
N'Hello ' LIKE N'Hello' –> false

And second blanks in the pattern are allways significant:

'Hello' LIKE 'Hello ' --> false
N'Hello' LIKE N'Hello ' –> false

What about LEN? It behaves consistent to the test for equality: padding blanks are ignored.

LEN('Hello') --> 5
LEN('Hello ') –> 5
LEN(N'Hello') –> 5
LEN(N'Hello ') –> 5

DATALENGTH('Hello') --> 5
DATALENGTH('Hello ') –> 6
DATALENGTH(N'Hello') –> 10
DATALENGTH(N'Hello ') –> 12

So we have some possible solutions, if we neeed to check for equality with blank awareness:

1. Use allways unicode and LIKE
2. add some dummy character at the end:
'Hello'+ '#'= 'Hello '+ '#'
N'Hello'+N'#'=N'Hello '+N'#'

3. convert to varbinary:
cast( 'Hello ' as varbinary(10)) = cast( 'Hello' as varbinary(10))
cast(N'Hello ' as varbinary(20)) = cast(N'Hello' as varbinary(20))

4. compare datalength:
'Hello'= 'Hello ' AND DATALENGTH( 'Hello')=DATALENGTH( 'Hello ')
N'Hello'=N'Hello ' AND DATALENGTH(N'Hello')=DATALENGTH(N'Hello ')

P.S.
Please be aware that the described behavior applies only to varchar and nvarchar, not char/nchar!
CHAR/NCHAR are always filled with blanks.
If you use ANSI_PADDING OFF trailing blanks are trimmed when inserted in a table.

-- ANSI
declare @vchar1 varchar(10),
@vchar2 varchar(10);

set @vchar1 = 'Hello ';
set @vchar2 = 'Hello';

select
'"'+@vchar1+'"' as "@vchar1",
'"'+@vchar2+'"' as "@vchar1",
case when @vchar1=@vchar2
then 'true'
else 'false' end as "@vchar1=@vchar2",
case when @vchar1 like @vchar2
then 'true'
else 'false' end as "@vchar1 like @vchar2",
case when @vchar2 like @vchar1
then 'true'
else 'false' end as "@vchar2 like @vchar1", –> false
case when @vchar1+'#'=@vchar2+'#'
then 'true'
else 'false' end as "@vchar1+'#'=@vchar2+'#'", –> false
case when cast(@vchar1 as varbinary(10)) = cast(@vchar2 as varbinary(10))
then 'true'
else 'false' end as "cast(@vchar1 as varbinary(10)) = cast(@vchar2 as varbinary(10))",
case when @vchar1=@vchar2 AND DATALENGTH(@vchar1)=DATALENGTH(@vchar2)
then 'true'
else 'false' end as "@vchar1=@vchar2 AND DATALENGTH(@vchar1)=DATALENGTH(@vchar2)";
go

– Unicode
declare @nvchar1 nvarchar(10),
@nvchar2 nvarchar(10);

set @nvchar1 = 'Hello ';
set @nvchar2 = 'Hello';

select
'"'+@nvchar1+'"' as "@nvchar1",
'"'+@nvchar2+'"' as "@nvchar1",
case when @nvchar1=@nvchar2
then 'true'
else 'false' end as "@nvchar1=@nvchar2",
case when @nvchar1 like @nvchar2
then 'true'
else 'false' end as "@nvchar1 like @nvchar2",
case when @nvchar2 like @nvchar1
then 'true'
else 'false' end as "@nvchar2 like @nvchar1",
case when @nvchar1+'#'=@nvchar2+'#'
then 'true'
else 'false' end as "@nvchar1+'#'=@nvchar2+'#'",
case when cast(@nvchar1 as varbinary(20)) = cast(@nvchar2 as varbinary(20))
then 'true'
else 'false' end as "cast(@nvchar1 as varbinary(20)) = cast(@nvchar2 as varbinary(20))",
case when @nvchar1=@nvchar2 AND DATALENGTH(@nvchar1)=DATALENGTH(@nvchar2)
then 'true'
else 'false' end as "@nvchar1=@nvchar2 AND DATALENGTH(@nvchar1)=DATALENGTH(@nvchar2)";

22. Mai 2007 um 23:27

und ein weiterer Team-Blog der MS-SQL-Entwickler

Heute habe ich den "SQL Programmability & API Development Team Blog" für mich entdeckt. Er enthält jede Menge detaillierter Informationen über potentielle Performance-Probleme, über Zugriffspläne, Cache-Nutzung, usw. am SQL-Server-2005.

Es wird lange dauern bis ich die Artikel durchgearbeitet habe! Alle erste Klasse! 😉