16Nov

Wer sich schon immer mal darüber geärgert hat, dass mehrere SQL-Anweisungen nötig waren, um in einer Tabelle, abhängig von den Bedingungen in einer anderen Tabelle, Daten einzufügen, zu ändern und/oder zu löschen, der wird über die neue MERGE-Anweisung in SQL Server 2008 hocherfreut sein - … vorausgesetzt, er kann sich für komplexe SQL-Anweisungen begeistern ;-) .

Das absolute Standard-Szenario für die Anwendung von MERGE ist die Aktualisierung von Daten in einem Data Warehouse auf Basis der Daten aus dem Produktivsystem: Im Data Warehouse sollen

  • Adressdaten, die im Produktivsystem vorhanden sind, aber nicht im Data Warehouse, eingefügt werden (INSERT)
  • Adressdaten, die im Produktivsystem und im Data Warehouse vorhanden sind, geändert werden (UPDATE)
  • Adressdaten, die im Data Warehouse, nicht aber im Produktivsystem vorhanden sind, gelöscht werden (DELETE)

Dafür waren bisher 3 Anweisungen der folgenden Art nötig (je eine für INSERT, UPDATE und DELETE):

Mit dem MERGE-Befehl nun ist das alles in einer Anweisung unterzubringen:

Das kommt nicht nur deutlich eleganter daher, sondern ist auch ressourcensparend, da nur eine anstelle mehrerer Lookup-Operationen durchgeführt wird.

Wichtig: Die MERGE-Anweisung muss immer mit einem Semikolon abgeschlossen werden.

Natürlich ist die MERGE-Anweisung noch deutlich flexibler als hier abgebildete Variante, z.B. kann die USING-Klausel auch eine Unterabfrage (dann mit einem Alias) enthalten oder die MATCHED-Klausel beliebig mit weiteren logischen Ausrücken kombiniert werden, des Weiteren sind beliebig viele MATCHED-Klauseln von jeder Art möglich. Ein etwas komplexeres Beispiel könnte z.B. so aussehen:

Ausführliche Syntaxinformationen finden sich unter http://msdn.microsoft.com/de-de/library/bb510625.aspx.

Technorati Tags: , ,

28Feb

Hin und wieder kommt es vor, dass man Dateien im BLOB-Format in einer Tabelle ablegt. Wenn man nun “schnell” auf diese Daten zugreifen will, steht man vor einem Problem, denn der Standard-SQL-Befehlssatz bietet keine Möglichkeit, Spalten mit Binärdaten zu füllen bzw. diese Daten wieder auszulesen und in eine Datei zu speichern.

Nun hat man nicht immer eine Entwicklungsumgebung zu Hand, um ein Programm zu schreiben, das per ADO oder ADO.NET diese Spalten befüllt. Auch der SQL-Server selbst bietet Möglichkeiten, um hier z.B. mittels Management-Studio tätig zu werden. Im Folgenden beschreibt ein Beispiel das generelle Vorgehen:

Als vorbereitende Maßnahme legt man eine Datenbank namens [pictures] an, die eine Tabelle mit dem Namen [images] enthält. Diese Tabelle enthält einfachheitshalber nur einen Primärschlüssel und das eigentliche Datenfeld. Wichtig hierbei ist, dass das Datenfeld den Datentyp [varbinary](max) hat.

CREATE DATABASE pictures
GO
USE pictures
GO  

CREATE TABLE [dbo].[images](
[imageid] [int] IDENTITY(1,1) NOT NULL,
[imageblob] [varbinary](max) NOT NULL
PRIMARY KEY CLUSTERED ([imageid]))
GO

Nun wird die Tabelle mittels INSERT befüllt. Mittels OPENROWSET in Kombination mit der Option BULK ist es möglich, Spalteninhalte als Binärstrom aus einer Datei (im Beispiel: bild1.jpg) zu lesen.

INSERT [dbo].[images]([imageblob])
SELECT BulkColumn
FROM OPENROWSET( BULK 'C:\bild1.jpg',
SINGLE_BLOB) as ExternalFile
GO

Führt man nun einen SELECT auf die Tabelle aus, sieht man, dass die Binärdaten gespeichert wurden.

image

Der umgekehrte Weg ist leider nicht ganz so einfach. Hierzu muss man das Programm bcp bemühen. Um bcp per SQL aufzurufen, muss der Server konfiguriert werden, dass Shell-Aufrufe möglich sind. Auch das ist (notwendige Berechtigungen vorausgesetzt) per SQL-Statement möglich:

EXECUTE sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO

Für den eigentlichen Export ist noch eine Konfigurationsdatei erforderlich. Diese beinhaltet die Formatbeschreibung. Wird diese nicht verwendet hat die resultierende Datei zwar die passende Größe, kann aber nicht gelesen werden, da bcp beim Export einige automatische Zeichenersetzungen vornimmt. Die Formatdatei kann auf Kommandozeilenebene mit bcp erstellt und dann angepasst oder direkt mittels Editor erstellt werden.

image

Die resultierende Datei sollte final wie dargestellt aussehen:

image

Nun kann mittels eines BCP-Befehls eine Datei erstellt werden. Wichtig hierbei ist, dass immer nur 1 Spalte und eine Zeile ausgegeben wird, da a) nur eine Datei geschrieben wird und b) dies auch in der eben erstellten Formatdatei so beschrieben ist.

exec master..xp_cmdshell ‘bcp “select [imageblob] from [pictures].[dbo].[images] where imageid = 1″ queryout C:\kopiebild1.jpg -T -S . -f c:\imageblob.fmt’

image

Die Datei liegt nun als kopiebild1.jpg wieder vor.

Technorati Tags: , , , , ,

18Jan

Für gezielte Datenbankexporte oder -views möchte man oft eine Zeile für einen Eintrag in der SQL Abfrage erhalten. Beim Verknüpfen von mehreren Tabellen führt dies bei 1:n oder n:m Beziehungen zu dem Nachteil, dass für die Basistabelle mehrere Zeilen entstehen. Zur Verdeutlichung sei folgendes Beispiel (in PostgreSQL) gegeben:

create table account (
ID BIGINT not null,
EMAIL CHARACTER VARYING(1024) not null unique,
primary key (ID)
);


create table history (
ID BIGINT not null,
ACCOUNT_FK BIGINT not null,
ENTRY CHARACTER VARYING(1024) not null unique,
primary key (ID)
);

Ziel soll es sein, ein Abfrage zu erstellen, die alle Accounts und die dazu zugehörigen Historyeinträge zurückliefert. Allerdings soll pro Account nur eine Zeile verwendet werden.

Folgende Abfrage führt nicht zum Ziel:

select account.email, history.entry
from account, history
where account.id = history.account_fk;

Eine Möglichkeit ist es, die Einträge in der History zu verknüpfen, z. B. über ein concat Funktion. Wenn diese als Aggregatfunktion zur Verfügung steht, kann diese in einem group by verwendet werden.

In PostgreSQL ist dies wie folgt möglich:

CREATE AGGREGATE textcat_all(
basetype = text,
sfunc = textcat,
stype = text,
initcond = ''
);

Mit folgender Abfrage kommt man dann zu dem Ziel nur noch eine Zeile pro Account zu erhalten:


select account.email, textcat_all(history.entry || ',')
from account, history
where account.id = history.account_fk;
group by account.email

(Siehe dazu auch hier http://archives.postgresql.org/pgsql-novice/2003-09/msg00177.php)

In mysql ist dies noch einfacher, denn hier existiert bereits eine solche Aggregationsfunktion: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat