SQL: Duplikate vermeiden

Saugkraft

Saugkraft

Aktives Mitglied
Thread Starter
Dabei seit
20.02.2005
Beiträge
8.998
Reaktionspunkte
3.189
Hallo SQL Experten,

ich sitze grad an einem relativ kniffligen Problem und ich weiß nicht, ob es dazu eine reine SQL Lösung gibt. Ich habe eine Abfrage bei der datensatz_id, status1, status2 und datum abgefragt werden sollen.

datensatz_id und status1 kommen aus Tabelle 1.
datensatz_id ist Fremdschlüssel in Tabelle 2. Aus Tabelle 2 lese ich status2 und datum aus.

status1 kann true sein, status2 kann true sein oder alle beide.

In Tabelle 2 sind jeder datensatz_id aus Tabelle 1 mehrere Datumswerte zugeordnet.

Ich will abfragen, an welchen Tagen (Datum) status1, status2 oder alle beide true sind. Dabei soll nach Datumswerten sortiert werden.

Soweit so gut. Problem: Ich will jede datensatz_id nur einmal aufgeführt bekommen.

Mit
Code:
SELECT tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum INNER JOIN tabelle1 ON tabelle1.datensatz_id = tabelle2.datensatz_id WHERE tabelle1.status1 = true OR tabelle2.status2 = true GROUP BY tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum ORDER BY MIN(tabelle2.datum)
spuckt er mir die Datensätze aus, führt aber ggf. die datensatz_id doppelt auf. Nämlich dort wo ein status true ist und mehrere Datumseinträge zu einer datensatz_id existieren.

Zum Beispiel so:
Code:
datensatz_id status1 status2 datum
       2           true     false   1.1.2008
       2           true     false   2.1.2008
       3           true     true    5.1.2008
       3           true     true    7.1.2008
       2           false    true    8.1.2008
       1           true     true    9.1.2008

Was ich brauche ist das jeweils erste Datum bei dem eine datensatz_id auftaucht. Bei 2 z.B. der 1.1.2008. Danach ist die datensatz_id "verbraucht" und darf nicht wieder auftauchen. Das Ergebnis sollte also so aussehen:
Code:
datensatz_id status1 status2 datum
       2           true     false   1.1.2008
       3           true     true    5.1.2008
       1           true     true    9.1.2008

Warum das so sein muss? Da hängen natürlich noch ein paar andere Daten aus anderen Tabellen dran. Wenn ich daraus eine Webseite generiere, kommen die Daten geclustert zum Einsatz und zu jeder id werden die Datumswerte nochmal gesondert abgefragt (plus ein paar Zusatzinfos):

Beginn: 1.1.2008
id: 2
Termine: 1.1.2008, 2.1.2008, 8.1.2008

Entscheidend für die Sortierung ist aber der erste Termin. Wenn die id mehrfach (irgendwo weiter hinten in der Liste nochmal) auftaucht, haut er mir dazu natürlich nochmal die 4 Termine raus. Das will ich natürlich nicht.

Ich kann an der Datenbank Struktur nichts ändern. Dazu hängt zuviel hinten dran. Die Frage ist also: Gibt es dafür eine reine SQL Lösung oder muss ich in ASP, PHP, etc. was programmieren um bereits abgearbeitete ids zu überspringen?

Ich hoffe, ihr könnt mir helfen.
 
Hm... Das einzige, was mir jetzt einfällt, währe das über mehrere einzelne Querys zu machen.

Für jede datensatz_id einen eigenen, der sich dann mit LIMIT und ORDER jeweils den ersten Satz holt.

Wird aber bei vielen Sätzen recht unperfomant...

Ansonsten, schau Dir mal den GROUP-Befehl an. Den hab ich jetzt nicht so gut drauf, aber mein Instinkt sagt mir, dass, wenn es möglich ist, es damit möglich ist :D

(Männlicher Instinkt, keine Weibliche Intuition :D)
 
  • Gefällt mir
Reaktionen: Saugkraft
Hm... Das einzige, was mir jetzt einfällt, währe das über mehrere einzelne Querys zu machen.

Für jede datensatz_id einen eigenen, der sich dann mit LIMIT und ORDER jeweils den ersten Satz holt.

Wird aber bei vielen Sätzen recht unperfomant...

Ansonsten, schau Dir mal den GROUP-Befehl an. Den hab ich jetzt nicht so gut drauf, aber mein Instinkt sagt mir, dass, wenn es möglich ist, es damit möglich ist :D

(Männlicher Instinkt, keine Weibliche Intuition :D)

Mit GROUP BY und FIRST. Schau dir mal group by und die sog. Aggregatfunktionen an.
 
  • Gefällt mir
Reaktionen: Saugkraft
das sollte per SQL ohne Probleme möglich sein, poste doch mal bitte die genau Struktur der Tabellen mit Primärschlüsseln etc... das macht es einfacher.
 
Hast du es schon mal mit DISTINCT und einem LEFT JOIN Probiert? *

SELECT DISTINCT tabelle1.datensatz_id…
FROM (tabelle1 LEFT JOIN tabelle2
ON tabelle1.datensatz_id = tabelle2.datensatz_id)
WHERE…. (Datumsanfrage muss hier rein u.a.)



*ggf. nur DB2 Syntax ...
aber müsste es für alle Datenbanken ja analoges zu geben
 
  • Gefällt mir
Reaktionen: Saugkraft
auf die Schnelle fiele mir ein:

GROUP BY datensatz_id

und

ORDER BY tabell2.datum

das sollte ggf helfen die Zeilen auf eine ( die älteste) zu reduzieren. Da Du ja im Prinzip nur die Datensätze ausschließt bei denen status1 und status2 false sind, bekommst Du auch mit INNER JOINS keine besseren Daten.

Ggf. kann man die Zeilen auch erstmal alle so in eine temporäre Tabelle schreiben und dort mit einem 2ten Statement nur die jeweils ältesten Einträge je ID herauslesen. Sind zwei Schritte aber ggf. etwas übersichtlicher!
 
internetlady schrieb:
Hast du es schon mal mit DISTINCT und einem LEFT JOIN Probiert?

an DISTINCT hab ich auch gedacht, bezieht sich denn DISTINCT nur auf die eine Spalte oder ist DISTINCT je Resultset/Tupel gemeint? Im letzteren Fall dürfte das ja kaum helfen.
 
an DISTINCT hab ich auch gedacht, bezieht sich denn DISTINCT nur auf die eine Spalte oder ist DISTINCT je Resultset/Tupel gemeint? Im letzteren Fall dürfte das ja kaum helfen.

Sowohl, als auch, wenn ich mich nicht irre.

SELECT DISTINCT spalte FROM tabelle

vs.

SELECT DISTINCT(spalte) FROM tabelle
 
  • Gefällt mir
Reaktionen: Saugkraft
FIRST wird vom MS SQL Server 2000 nicht unterstützt. :hum: Wird Zeit, dass ich mal auf 2008 update.

DISTINCT datensatz_id klappt auch nicht. Weder mit LEFT JOIN noch ohne. Ich bin mir nicht sicher, aber ich vermute, dass sich DISCTINCT auf das Resultset bezieht.

Das Problem ist ja, dass ich selbst wenn ich die datensatz_id gruppiere und per MIN DISTINCT die Datumswerte eingrenze, zwei oder mehr Daten erhalte, bei denen die Kriterien erfüllt sind. Dadurch unterscheiden sich die Ergebnissätze und für jedes unterschiedliche Datum wird mir die datensatz_id mit ausgespuckt.

frikih: Die Struktur sieht so aus..
Tabelle 1: datensatz_id (Primärschlüssel, ID), status1
Tabelle 2: tbl2_id, datensatz_id (Fremdschlüssel von Tabelle 1), status2, datum

Sind zwar noch mehr Felder drin aber die kommen nicht zum Einsatz und bilden auch keine Verknüpfung zwischen den Tabellen.

Das mit der temporäten Tabelle hab ich mir auch schon überlegt, müsste allerdings erstmal rausfinden ob das mit dem SQL 2000 geht. Vielleicht kann ich ja auch eine Abfrage auf eine Abfrage legen.

Alternativ böte sich noch ASP.NET an, da kann man Abfragen auf das Ergebnis anwenden weil die Daten nicht als Recordset sondern in einer Tabellenstruktur vorliegen. Der Nachteil ist, dass die Datensatznavigation da nicht per Zeiger funktioniert und für mein kleines Hirn offensichtlich zu kompliziert ist.
 
[QUOTE="Saugkraft]Das mit der temporäten Tabelle hab ich mir auch schon überlegt, müsste allerdings erstmal rausfinden ob das mit dem SQL 2000 geht. Vielleicht kann ich ja auch eine Abfrage auf eine Abfrage legen.[/QUOTE]

das geht in jedem Fall! Nutze das selbst hier. Auf die Schnelle aus dem Kopf ohne Gewähr: temp. Tabellen beginnen mit einer # glaub ich!
EDIT: stimmte :) #tabelle ist lokale temporäre Tabelle ( nur für Deinen einen Connect) ##tabelle ist eine globale temporäre Tabelle. Mehr gibt es im Query Analyzer unter Hilfe zu Transact SQL wenn Du nach temporär suchst!

hast Du das mit GROUP BY probiert? Immerhin ist das das Mittel der Wahl da auf eine Zeile herunterzubrechen.
 
  • Gefällt mir
Reaktionen: Saugkraft
Hallo,

also hier sollte dir definitv ein GROUP BY helfen. Etwa in der Art:

SELECT
datensatz_id,
MIN(datum)
FROM
... (Join)
WHERE
status1 = true or status2 = true
GROUP BY
datensatz_id

Diese Abfrage liefert erstmal alle Datensätze in denen Status1 oder Status2 auf TRUE stehen.
Der GROUP BY sorgt dann dafür, dass du jede "datensatz_id" nur einmal bekommst und über den MIN bekommst du das kleinste Datum (also das frühste).

Gruß
Dirk
 
Jep. GROUP BY hab ich probiert. Steht ja auch oben drin:

SELECT tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum
INNER JOIN tabelle1 ON tabelle1.datensatz_id = tabelle2.datensatz_id
WHERE tabelle1.status1 = true OR tabelle2.status2 = true
GROUP BY tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum
ORDER BY MIN(tabelle2.datum)

Dann schaue ich mal nach den temporären Tabellen.

Wenn ich die Abfrage als Sicht abspeichere und darauf eine Abfrage mit MIN DISTINCT durchführe, klappts auch. Das finde ich aber wiederum nicht so elegant. Wer weiß, ob ich in einem Anfall von "Was ist denn UnterAbfrageSichtAufDingensBumens) für ne Abfrage, die braucht doch kein Mensch mehr" die Abfrage lösche.

Ich experimentiere auch grad mit einer Unterabfrage im Stil:
Code:
SELECT DISTINCT tabelle1.datensatz_id, status1, status2, 
min(datum) FROM tabelle1, tabelle2  
WHERE EXISTS 
(SELECT datum from tabelle2 WHERE tabelle1.datensatz_id = tabelle2.datensatz_id) 
AND (status1 = true) OR (status2 = true) 
GROUP BY tabelle1.datensatz_id, status1, status2 
ORDER BY datum

So richtig will das aber auch noch nicht.
 
saugkraft schrieb:
Wenn ich die Abfrage als Sicht abspeichere und darauf eine Abfrage mit MIN DISTINCT durchführe, klappts auch.

soweit ich weiß werden Views auch automatisch indiziert, das könnte also doch eher ein Grund dafür sein! Ich nutze Views sehr gern. Wichtig ist nur das man im Kopf behält: Geänderte Tabellen erfordern aktualisieren der Views !
 
Jep. GROUP BY hab ich probiert. Steht ja auch oben drin:

SELECT tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum
INNER JOIN tabelle1 ON tabelle1.datensatz_id = tabelle2.datensatz_id
WHERE tabelle1.status1 = true OR tabelle2.status2 = true
GROUP BY tabelle1.datensatz_id, tabelle1.status1, tabelle2.status2, tabelle2.datum
ORDER BY MIN(tabelle2.datum)

Du darfst hier aber weder nach status1, status2 oder insbesondere Datum gruppieren, denn dann bekomsmt du ja wieder für jedes Datum einen Datensatz. Du darfst ausschließlich nach datensatz_id gruppieren, damit du nur je eine Zeile pro ID bekommst. Über das MIN(datum) erhälst du dann das jüngste Datum.

Gruß
Dirk
 
  • Gefällt mir
Reaktionen: Saugkraft
just.do.it schrieb:
Du darfst hier aber weder nach status1, status2 oder insbesondere Datum gruppieren

Aua ja! Das hatte ich gar nicht gelesen. Vorher ist das Group by nat nutzlos gewesen.
 
Du darfst hier aber weder nach status1, status2 oder insbesondere Datum gruppieren, denn dann bekomsmt du ja wieder für jedes Datum einen Datensatz.
:faint:

Oh Mann. Wie blind kann ein einzelner Mensch sein? Na logo. Die Statusfelder hatte ich zur Kontrolle mit drin, um zu sehen welche Kombination vorliegt.

Hab's gerade probiert, klappt natürlich. Jetzt muss ich es nur noch mal in der Anwendung testen um zu sehen, ob ich damit alle Infos habe die ich brauche, aber ich denke das sieht gut aus.

Tausend Dank! Das hat mein Hirn mal wieder in Schwung gebracht. :)
 
Zurück
Oben Unten