Freitag, 27. Januar 2012

DataViewer auf Basis von TABLESAMPLE im ExPEditor

Beim Dokumentieren von Datenbanken mit dem ExPEditor habe ich gemerkt, dass ich ab und zu die Daten anschauen will, die in der Tabellen gespeichert sind. Nachdem einige Anwender von ExPEditor mir ähnliche Rückmeldung gegeben haben und ich zigmal zwischen zwei Tools gewechselt habe, war die Entscheidung gefallen:
Ein einfacher DataViewer ist ins ExPEditor einzubauen!
Die Version Build 16 implementiert ein neues Feature Table sample, das als ein weiterer Reiter umgesetzt ist. Der Reiter wird sichtbar, wenn eine Tabelle oder eine View im Object Explorer markiert ist. Table sample besteht aus zwei Bereichen Conditions und DataGrid:
  1. Im Bereich Conditions kann man Bedingungen für eine Abfrage der Stichproben festlegen und den Bereich zusammenklappen, wenn die Bedingungen nicht mehr geändert werden sollen.
    Man kann zwischen Klauseln TOP, TOP RANDOM, TABLESAMPLE, TABLESAMPLE RANDOM wechseln und entweder maximale Anzahl oder prozentualen Anteil der Stichproben auswählen.
    • TOP - zeigt begrenzte Anzahl von Datenzeilen an.
    • TOP RANDOM - bei jeder Ausführung der Abfrage wird begrenzte Anzahl von zufälligen Datenzeilen angezeigt.
    • TABLESAMPLE - zeigt Datenzeilen der zufällig ausgewählten Datenseiten an. Kann nur für eine Tabelle verwendet werden
    • TABLESAMPLE RANDOM - funktioniert ähnlich wie TABLESAMPLE nur mit dem Unterschied, dass die Zeilen in einer zufälligen Reihenfolge angezeigt werden. Wird für Views deaktiviert.
  2. DataGrid dient zur Darstellung von Stichproben und gibt die Möglichkeit, Datenzeilen nach einer Spalte zu sortieren. Dafür klickt man einfach auf dem Spaltenkopf.
Nachdem die Abfragebedingungen festgelegt sind, wird eine Abfrage mit dem Knopf Refresh ausgeführt. Nach einer Ausführung einer Abfrage wird eine Anzahl von angezeigten Zeilen und eine Gesamtanzahl von Zeilen der Tabelle (oder einer indizierten Sicht) unten im Statusbar angezeigt. Wenn Daten einer nicht indizierten Sicht im Reiter Table sample angezeigt werden, wird im Statusbar nur eine Anzahl der zurückgelieferten Zeilen angezeigt.
Viel Spaß beim Datenbankdokumentieren ;)

Links:

Mittwoch, 18. Januar 2012

Uniqueidentifier, GUID - The value could not be converted because of a potential loss of data

Wenn beim Import von Datensätzen mit GUIDs so ein Konvertierungsfehler auftritt, wird es höchstwahrscheinlich an den fehlenden geschweiften Klammern liegen, die normalerweise eine GUID umgeben sollen. Hier ist z.B. eine GUID, die in einem SSIS Paket ohne Weiteres importiert werden kann:
{AC4939C4-E35D-4DEE-B9D0-D6B888854F9C}
Wenn die geschweiften Klammern fehlen, sollte man versuchen den Export-Prozess zu überprüfen und so anzupassen, dass die GUIDs von benötigen geschweiften Klammern umgerahmt werden. Ist die Möglichkeit den Export anzupassen nicht gegeben, kann man immer noch den Import mit dem Task Derived Column (Transformation für abgeleitete Spalten) erweitern, um das Konvertierungsproblem zu lösen.
Hier ist ein Beispiel vom Transformationsausdruk, der im Feld Expression der Transformation für abgeleitete Spalten angegeben wird:
(DT_GUID)("{" + [GUID-Column-Name] + "}")
Eins muss man noch beachten, dass eine GUID vor der Konkatenation als ein String aus der Quelle ausgelesen werden muss. Dafür konfiguriert man die Eigenschaft DataType der GUID-Spalte im Connection Manager Editor als [DT_STR], sonst schlägt die Transformation fehl.
Links:

Freitag, 9. Dezember 2011

ExPEditor - Editor for SQL Server Extended Properties

Mit dem Artikel möchte ich auch was zum Thema „Datenbankdokumentation“ beitragen, und zwar ein Tool ExPEditor vorstellen.


Zu einer Datenbankdokumentation gehören auf jeden Fall ein ER-Diagramm und eine technische und fachliche Beschreibung von Datenbankobjekten (Tabellen, Spalten, usw.). Die beiden Arten von der DB-Dokumentation kann man generieren lassen und es sind bereits ein oder anderes der kostenpflichtigen Tools, die es ermöglichen, bekannt.
SQL Server z.B. bietet Extended Properties zum Speichern der „fachlichen“ Beschreibung von Datenbankobjekten an. Es ist möglich, mehrere Extended Properties zu einem Datenbankobjekt hinzuzufügen. Die Extended Properties kann man direkt im SQL Server Management Studio editieren, es sind dafür leider mehrere Mouse-Klicks notwendig, bis man eine Eigenschaft editieren kann.

ExPEditor

Um die Arbeit mit Extended Properties zu vereinfachen und bequemer zu machen, habe ich die Entwicklung von dem ExPEditor gestartet und möchte heute eine Alpha-Version vorstellen.
Es sind folgende Features geplant
  • Navigation durch alle Datenbanken einer SQL Server Instanz
  • Bearbeitung von Extended Properties
  • Generieren von Beschreibung des Datenbankschemas im Word-Format
  • Generieren von DDL-Skripten zum Hinzufügen und Aktualiseren von Extended Properties
  • Anzeige von Definitionen der Datenbankobjekten

Implementierungsstand

Der ExPEditor ermöglicht ein bequemeren Zugriff auf die Extended Properties als das SQL Server Management Studio es anbietet
In der Alpha-Version kann man vorhandene Extended Properties und das Property Description, das automatisch durch ExPEditor zu jedem Objekt hinzugefügt wird und den Hauptteil der fachlichen Dokumentation darstellt, bearbeiten. In einer der nächsten Versionen wird es möglich sein, mehrere freibenannten EP’s zu einem Datenbankobjekt hinzuzufügen.
Das Generieren der Datenbankbeschreibung im Word-Format ist implementiert. Es gibt die Möglichkeit den Inhalt des Dokumentes nur für ausgewählte Datenbankobjekttypen generieren zu lassen.
Es kann ein DDL Skript generiert werden um alle Extended Properties einer Datenbank zu einer anderen Instanz zu übertragen.
Außerdem zeigt das Tool die DDL-Definition/T-SQL von Views, SP’s, Funktionen und Trigger an, was eine Datenbankentwicklung beschleunigen könnte und einem neuen Kollegen einen schnelleren Einstieg in die Entwicklung ermöglicht.

User Interface

Abbildung 1: Hauptfenster mit einem Objekt Explorer und einer tabellarischen Sicht, in der die Extended Properties des ausgewählen Objektes editiert werden können
 Abbildung 2: Ein zusätzliches Dialog zum Editieren von Extended Properties, ermöglicht bequemere Erfassen von mehrzeiligen Beschreibung
Abbildung 3: Dialog zum Auswahl von Datenbankobjekttypen, für die die Datenbankdokumentation im Word-Format generiert wird. Man kann im Dialog den Pfad zum Speichern des generierten Dokuments auswählen. Wenn die Checkbox "Save and Open" markiert ist, wird das generierte Dokument im Word automatisch geöffnet.
Abbildung 4: Definition einer gespeicherten Prozedur

Technischen Voraussetzungen

Für Verwendung des ExPEditor's müssen Microsoft Word ab 2007 und .Net Framework 4.0 auf dem Rechner installiert sein.

Anmerkung

Der ExPEditor wird kostenfrei zur Verfügung gestellt und von mir weiterentwickelt. Ich freue mich auf jede Rückmeldung, die man an die E-Mail im About-Dialog des ExPEditor's senden kann.

Download

Dienstag, 21. Juni 2011

CountDistinct (T-SQL)

Möchte man Anzahl von eindeutigen Werten ermitteln, kann man das Argument DISTINCT der Funktion COUNT dazu verwenden.
Hier ist ein Beispiel
use tempdb
go

create table t1
(
 col1 int null
)
go

insert into t1 values
(1),(2),(3),(4),(5),
(1),(2),(3),(4),
(1),(2),(3),
(1),(2),
(1),
(null)
go

select
 count(distinct col1) as CountDistinct,
 count(all col1) as CountAll,
 count(*) as CountAsterisk
from t1
go

drop table t1
go
Hier ist das Resultset
CountDistinct CountAll    CountAsterisk
------------- ----------- -------------
5             15          16

Sonntag, 5. Juni 2011

Versionsnummer einer Datenbank

Bevor man First-Level-Support anruft, sollte man zumindest die Versionsnummer des Beschwerdeobjekts kennen, um die Hilfe schnell und gezielt zu bekommen.
Bei Assemblies z.B. ist es üblich, Versionsnummer in Assembliesinfo zu verpacken, Oberflächen besitzen eigene Dialogboxen oder Seiten About, um die Versionsinfo dem Anwender zur Verfügung zu stellen.
Was ist mit Datenbanken, muss man eine Datenbank mit einer Versionsnummer versehen? Wird eine Datenbank nur von einer Anwendung verwendet, kann man in einigen Fällen die Datenbank und die Anwendung unter einer Versionsnummer zusammen ausliefern, dann wird das About-Anwendungsfenster ausreichend sein. Bei Enterprise-Lösungen wird eine Datenbank von „zich“ Web- und Windows-Services, Enterprise Service Bus, Web- und Desktop- Anwendungen verwendet, dazu kommen noch ETL-Prozesse, BI-Lösungen, die die Datenbank als Ziel oder Datenquelle anbinden. Mit anderen Wörtern wird eine Datenbank in einer Enterprise Lösung als einzelne Komponente betrachtet, deren Aktualisierung nicht zwingend eine Aktualisierung von anderen Komponenten der Gesamtlösung erfordert. Es ist auch umgekehrt gültig, beim Bugfixing einer Middleware-Anwendung wird die Datenbank nicht neu bereitgestellt, daher bleibt sie unter einer anderen Version bestehen.
Also ja, man sollte eine Datenbank mit einer Versionsnummer versehen. Die Frage ist „Wie?“.
Man könnte eine Tabelle Version erstellen, in der die ganze Versionsinfo abgespeichert wird. Es gibt aber für SQL Server Datenbanken (ab SQL Server 2005) auch eine andere Möglichkeit, die ich persönlich viel eleganter finde als eine Version-Tabelle. Die Möglichkeit ist, erweiterte Eigenschaften einer Datenbank und Datenbankobjekten einzusetzen. Im Beispiel 1 zeige ich, wie Datenbankeigenschaften um die Version und Bereitstellungsinfo erweitert werden können. Die erweiterten Eigenschaften einer Datenbank sind in SQL Server Management Studio über das Fenster Database Properties und die Seite Extended Properties zu erreichen (s. Abbildung 1).
Möchte man die erweiterten Eigenschaften von einer Anwendung aus abfragen, steht die Funktion fn_listextendedproperty zu Verfügung, ein Einsatz der Funktion ist auch in dem Beispiel 1 zu finden.
Beispiel 1: Erstellen von erweiterten Eigenschaften Version, Last deployment, Deployed by der aktuellen Datenbank
-- Stored Procedure
-- dbo.merge_extended_db_property
-- erstellt oder aktualisiert erweiterte Eigenschaften
-- der aktuellen Datenbank
-- @name - Name der Eigenschaft
-- @value - Wert der Eigenschaft
create proc dbo.merge_extended_db_property
 @name sysname,
 @value sql_variant
as
begin
 if not exists
 (
  select name
  from
   fn_listextendedproperty(@name,
     default, default, default,
     default, default, default)
 )
 begin
  exec sys.sp_addextendedproperty @name, @value
 end
 
 exec sys.sp_updateextendedproperty @name, @value
end
go
 
-- Erweiterte Eigenschaft 'Version'
-- Version der bereitgestellten Datenbank 
declare @name sysname = N'Version'
declare @value varchar(16) = '1.3.25'
exec dbo.merge_extended_db_property @name,@value
go

-- Erweiterte Eigenschaft 'Last deployment'
-- Das Datum der Bereitstellung der Datenbank
declare @name sysname = N'Last deployment'
declare @value varchar(128) = convert(varchar(128),getdate(),121)
exec dbo.merge_extended_db_property @name,@value
go

-- Erweiterte Eigenschaft 'Deployed by'
-- Anmeldename des Benutzers,
-- der die Datenbank bereitgestellt hat
declare @name sysname = N'Deployed by'
declare @value varchar(128) = system_user
exec dbo.merge_extended_db_property @name,@value
go
-- Löscht die Prozedur dbo.merge_extended_db_property
drop proc dbo.merge_extended_db_property
go
Abbildung 1: Anzeigen erweiterter Eigenschaften in SSMS


Verwandte Links

Sonntag, 29. Mai 2011

Tabellen vor versehentlichem Löschen schützen

Ich kann mich an einige Fälle erinnern, als Produktionsdatenbanken aus einem Versehen oder fehlender Übersicht der Infrastruktur verschrottet wurden. Es fängt mit dem Löschen einer Tabelle an und endet mit der Bereitstellung einer nicht getesteten Version der DB auf dem Produktionssystem. Dazu gibt es noch Benutzertabellen in der SQL Server Systemdatenbanken, die eigentlich nicht dahin gehören.

Könnte man die Fälle vermeiden oder das Risiko zumindest reduzieren?

Als erste Maßnahme soll man die Sicherheit der SQL Server Umgebung konzipieren und den Kreis der Mitarbeiter reduzieren, die zu der Serverrolle sysadmin und der Datenbankrolle dbo (databaseowner) zugeordnet sind.

Leider lässt es sich nicht in allen Projekten und nicht bei allen Kunden vermeiden, dass die sa- oder dbo-Berechtigungen für viele zugänglich sind. Außerdem können die Fehler auch einem erfahrenen Entwickler oder Administrator passieren.

Ich habe in einem Buch den Vorschlag gelesen, View‘s mit der Option SCHEMABINDING zu erstellen um das Löschen der zugrunde liegenden Tabellen zu unterbinden. Die aus einer View referenzierten Tabellen können nur nach einem Löschen der View gelöscht werden. Das Beispiel 1 zeigt, wie es umgesetzt werden könnte.

Beispiel 1: Schutz einer Tabelle vor versehentlichem Löschen mit einer schemagebundenen View
use [Insidesql.org]
go
create table dbo.MyTable
(
    MyTableID int identity,
    Col1 nvarchar(55)
)
go
create view dbo.vwMyTable
    with schemabinding
as
select
    MyTableID,
    Col1
from
    dbo.MyTable
go
-- Den folgenden Versuch, die Tabelle dbo.MyTable zu löschen,
-- beenden mit der Fehlermeldung:
-- Msg 3729, Level 16, State 1, Line 1
-- Cannot DROP TABLE 'dbo.MyTable' because it is being referenced by object 'vwMyTable'.
drop table dbo.MyTable
go
-- Um die Tabelle löschen zu können, muss zuerst die View dbo.vwMyTable gelöscht werden.
drop view dbo.vwMyTable
drop table dbo.MyTable
go
Tatsächlich kann man View's mit SCHEMABINDING als Schutz vor einem Löschen der Tabellen nutzen, aber ich persönlich bevorzuge, einen DDL-Trigger einzusetzen. Beispiel 2 zeigt, wie man mit einem DDL-Trigger das Erstellen, Ändern und Löschen aller Tabellen der Datenbank unterbinden kann. Es gibt sogar weitere Möglichkeiten mit einem DDL-Trigger Functions, Stored Procedures und andere Datenbankobjekte vor einem Löschen zu schützen. Ein Überblick über die DDL-Ereignisgruppen, die einen DDL-Trigger ausführen, bekommt man in dem MSDN Artikel DDL-Ereignisgruppen.

Beispiel 2: DDL-Trigger als Überwachung der Datenbankänderungen
use [Insidesql.org]
go
create table dbo.MyTable
(
    MyTableID int identity,
    Col1 nvarchar(55)
)
go
if exists
(
    select *
    from sys.triggers
    where name = N'TR_TABLE_DENY_CAD'
        and parent_class_desc = N'DATABASE'
)
drop trigger TR_TABLE_DENY_CAD on database
go

create trigger TR_TABLE_DENY_CAD on database 
    for CREATE_TABLE, ALTER_TABLE, DROP_TABLE 
as 
begin
    print 'You must ask your DBA to create, alter or drop tables!' 
    rollback transaction
end
go

-- Den folgenden Versuch, die Tabelle dbo.MyTable zu löschen
-- beenden mit der Fehlermeldung:
-- You must ask your DBA to create, alter or drop tables!
-- Msg 3609, Level 16, State 2, Line 1
-- The transaction ended in the trigger. The batch has been aborted.
drop table dbo.MyTable
go
Vorteile der DDL-Trigger-Lösung
  • In einem DDL-Trigger können mehrere Typen von DDL-Ereignissen unterbunden werden
  • Es können mehrere DDL-Trigger für unterschiedliche DDL-Ereignisse erstellt werden
  • Ein DDL-Trigger überwacht alle Objekte der DDL-Ereignisgruppe, damit ist keine Anpassung des Triggers notwendig, wen z.B. eine Tabelle umbenannt oder gelöscht wird
  • Es ist kein Löschen des DDL-Triggers notwendig, wenn die DDL-Ereignisse zugelassen werden müssen, für die der Trigger erstellt ist. Es reicht den DDL-Trigger zu deaktivieren
  • DDL-Trigger's sind von anderen Datenbankobjekten getrennt gespeichert und damit leicht zugänglich
Aktivieren und Deaktivieren vom DDL-Trigger
Wird ein DDL-Trigger zum Schützen von Datenbankobjekten vor versehentlichem Löschen eingesetzt, sollte man den Trigger vor der Bereitstellung der Datenbank per Hand über das SSMS (siehe Abbildung 1) in der Zieldatenbank deaktivieren. Um den Trigger nach der Bereitstellung automatisch zu aktivieren, kann man am Ende des Bereitstellungsskripts den folgenden Befehl einbauen:
enable trigger TR_TABLE_DENY_CAD on database
go
Abbildung 1: Deaktivieren von DDL-Trigger

Zusammenfassung
Man kann das Risiko einer versehentlichen Änderung einer Datenbank reduzieren, indem:
  • die Datenbankberechtigungen sorgfältig verteilt werden
  • Standarddatenbank von SQL Server-Anmeldungen gepflegt wird
  • use-Anweisung in Bereitstellungsskripten benutzt wird
  • schemagebundene View’s oder DDL-Trigger’s implementiert werden.

Donnerstag, 26. Mai 2011

PRIMARY KEY-Einschränkungen mit einem NONCLUSTERED INDEX finden

In der Regel verfügt eine Tabelle über PRIMARY KEY-Einschränkung, für die ein gruppierter Index standardmäßig erstellt wird. Man kann auch eine PK-Einschränkung mit einem nicht gruppierten Index erstellen. Als ein Beispiel dafür siehe Abfrage 3.
Sind die Richtlinien für den Entwurf gruppierter Indizes angehalten, ist der Index eine gute Grundlage für die Erstellung von weiteren Indizes und so für eine schnellere Datenbank.
Entwickelt oder optimiert man eine große Datenbank, kann es passieren, dass gruppierte Indizes für eine ungünstige Spalte oder Spaltenkombination erstellt werden. Besonders teuer für Abfragen können die Tabellen mit einer PRIMARY KEY-Einschränkung sein, für die ein nicht gruppierter Index oder ein gruppierter Index für die Spalten, die nicht in die PK eingeschlossen sind, erstellt wurde. Deswegen sollte man die Tabellen genauer anschauen.
Um einen Überblick über die PRIMARY KEY-Einschränkungen mit einem nicht gruppierten Index  in einer großen Datenbank zu bekommen, habe ich die folgenden zwei Abfragen geschrieben.
Abfrage 1. Bezeichnet Tabellennamen mit 1, die einen gruppierten Index besitzen
select
    name as TableName,
    objectproperty(t.object_id,'TableHasClustIndex')
        as HasClustIndex
from
    sys.tables t
go
Abfrage 2. Bezeichnet PK-Einschränkungen mit 1, für die ein gruppierter Index erstellt wurde
select
    quotename(schema_name(k.schema_id)) + '.' +
    quotename(object_name(k.parent_object_id)) as TableName,
    k.name as PrimaryKey,
    objectproperty(k.[object_id],'CnstIsClustKey') as IsClustKey
from
    sys.key_constraints k
where
    k.[type] = 'PK'
order by
    k.schema_id ,k.parent_object_id
go
Abfrage 3. Erstellt die PK-Einschränkung [PK_MyTable] für die Tabelle [MyTable] mit einem nicht gruppierten Index
create table dbo.MyTable
(
    MyTableID int identity,
    Column1 nvarchar(55),
    constraint PK_MyTable primary key nonclustered
    (
        MyTableID
    )
)
go

oder

create table dbo.MyTable
(
 MyTableID int identity constraint PK_MyTable primary key nonclustered,
 Column1 nvarchar(55)
)
go
Verwandte Links: