Insane C# Development

Freitag, November 14, 2008

SQL: Select default row if no row matches

Heute stand ein Bekannter von mir vor einem relativ simplen, aber eigentlich auch gemeinen Problem. Gegeben sei folgende Tabelle:

FORM QUAL FARBE GROESSEVON GROESSEBIS Preis
3 334 0 36 47 39.99
3 334 0 48 50 49.99
3 334 370 48 50 49.99
3 334 370 36 47 39.99
3 334 714 48 50 55
3 334 714 36 47 45
3 334 736 48 50 55
3 334 736 36 47 45

Es handelt sich um Schuhe. Form & Qual ist sozusagen die Artikelnummer. Wenn für einen Artikel in einer bestimmten Farbe kein Preis hinterlegt ist, muss der Preis der "Defaultfarbe" (0) genommen werden. Die Größen spielen dabei keine Rolle, es müssen nämlich alle Rows zurückgeliefert werden.

Also für FORM/QUAL 3/334 und Farbe 736 wollen wir:

FORM QUAL FARBE GROESSEVON GROESSEBIS Preis
3 334 736 48 50 55
3 334 736 36 47 45

und für Form/Qual 3/334 in Farbe "80":

FORM QUAL FARBE GROESSEVON GROESSEBIS Preis
3 334 0 36 47 39.99
3 334 0 48 50 49.99

Soweit klar? Dann schreibt mal ein einzelnes SELECT Statement dafür! Wenn man auf der Leitung steht kann man relativ lange darüber nachdenken ;-)

Der erste Ansatz (wir vereinfachen Spaltennamen und ignorieren "Form"):

SELECT TOP 1 * FROM Preise WHERE ARTNR = 334 AND Farbe IN (0, 736) ORDER BY FARBE DESC

Leider falsch, denn so bekommt man nur 1 Resultset, also nur ein Größenrange. TOP 2, 3, 4 ist nicht möglich da man nicht weiß wieviele Ranges es gibt, und Filterung auf Applikationsebene ist aus verschiedenen Gründen auch nicht möglich!

Nach einigem Probieren, und einiger Zeit in der wir uns vorkamen als wären wir die dümmsten Idioten auf der Welt, fanden wir Lösung A:

SELECT *
FROM Preise WHERE ArtNr = 334 AND Farbe = 736
UNION
SELECT *
FROM Preise
WHERE Farbe = 0 AND NOT EXISTS ( SELECT * FROM Preise WHERE ArtNr = 334 and FARBE = 736)

Was uns die Resultate entweder aus dem ersten Teil der UNION oder aus dem zweiten Teil bringt, aber drei SELECTS verursacht. Zugegeben, auf einer guten DB mit richtig gesetzten INDICES (hier auf ArtNr+Farbe) wäre das kein Problem, aber richtig schön ist das auch nicht.

Wir haben es aber zunächst dabei belassen, bis uns eine zweite Lösung einfiel, die sehr einfach ist, und sowohl ohne temporäre Variablen als auch ohne temporäre Tabellen auskommt. Hier also Lösung B:

SELECT * FROM Preise WHERE ARTNR = 334 AND Farbe = ( SELECT MAX(FARBE) FROM Preise WHERE ArtNR = 334 AND FARBE IN (0, 736));

Eleganter, einfacher zu verstehen, und auch um einiges ressourcenschonender. Auch hier gilt: Sauber gesetzte Indices beschleunigen auch diese Abfrage. :)

Sicherlich, wenn man die Datenbank mit Stored-Procedures abfrägt, oder das Statement sonst aufbohren kann, kann man sich vielleicht noch einen Seek sparen, vielleicht noch ein bißchen Performance rausholen, aber der Aufwand lohnt sich nicht, da das Statement auf diese Weise recht klein und fein ist.

Labels: