![]() |
Datenbank: Firebird • Version: 3.x • Zugriff über: IBDAC
Abfrage: Kund hat Artikel X aber nicht Y gekauft
Hallo,
ich möchte alle Kunden haben, die den Artikel X gekauft haben und nie den Artikel Y gekauft haben. (Hinweis die Artikel X und Y können in verschiedenen Rechnungen des selben Kunden auftreten). Die Tabellen sehen so aus:
Code:
Danke!
Kunden
------ KdNr, Name, ... Rechnungen ----------- KdNr, ReNr, .... Positionen ---------- ReNr, Artikel, .... |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Syntaktisch sehr altbackene Version:
SQL-Code:
select k1.*
from kunden k1, rechnungen r1, positionen p1 where k1.kdnr = r1.kdnr and r1.renr = p1.renr and p1.artikel = 'X' and not exists ( select 1 from kunden k2, rechnungen r2, positionen p2 where k2.kdnr = r2.kdnr and r2.renr = p2.renr and p2.artikel = 'Y' and k1.kdnr = k2.kdnr ) |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Danke, das klappt!
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Noch ein Versuch:
SQL-Code:
select kdnr
from ( select kdnr, Sum(HatX) as HatX, /* Wie oft kommt der Artikel X vor? */ Sum(HatY) as HatY /* Wie oft kommt der Artikel Y vor? */ from ( select k1.kdnr, case when p1.artikel = 'X' then 1 else 0 end as HatX, /* Jeder Artikel X zählt. */ case when p1.artikel = 'Y' then 1 else 0 end as HatY /* Jeder Artikel Y zählt. */ from kunden k1, rechnungen r1, positionen p1 where k1.kdnr = r1.kdnr and r1.renr = p1.renr and p1.artikel in ('X','Y') /* Alle Kunden, die irgendwo Artikel X oder Y erworben haben. */ ) group by kdnr ) where HatY = 0; /* Nur die, bei denen Artikel Y nicht vorkommt. */ |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Das müsste doch aber auch mit Joins anstatt Sub-Selects funktionieren.
So aus dem Ärmel, ohne das hier testen zu können:
SQL-Code:
select k1.*
from kunden k1 join rechnungen r1 on r1.kdnr = k1.kdnr join positionen p1 on p1.renr = r1.renr and p1.artikel = 'X' join positionen p2 on p2.renr = r1.renr and p2.artikel = 'Y' having (Count(p1.artiklel) > 0) and (Count(p2.artikel) = 0) |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
Druch das "group by" ist auch kein "select k1.*" mehr möglich. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Ich würde das ja auch lieber an realen Daten ausprobieren. In der Theorie sieht ja vieles einfacher aus als es hinterher in der Praxis ist.
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Wenn bei dem SQL ein Kunde keinen Artikel Y gekauft hat, ist die Ergebnismenge leer, egal wieviele X er gekauft hat.
Wenn ein Kunde zwar Artikel Y gekauft hat, aber keinen Artikel X ist die Ergebnismenge ebenfalls leer. Gut, die Kunden interessieren uns nicht. Nur wenn ein Kunde X und Y gekauft hat und diese sich in der gleichen Rechnung befinden, wird die Ergebnismenge nicht leer sein. Aber sie enthält dann nur Kunden, die wir nicht haben wollen. Positionen p2 muss über eine eigene Rechnung mit den Kunden verbunden werden, um sicherzustellen, dass Artikel X und Artikel Y nicht in einer Rechnung erscheinen müssen.
SQL-Code:
Auch das wird noch nicht funktionieren, Frage ist, wo genau muss hier ein Left vor den Join und wie kann man dann sicherstellen, dass wirklich nur die Kunden erscheinen, die Artikel X erworben haben? Momentan hab' ich dafür absolut keine Idee. Bin mir nicht sicher, ob ein Left vor Positionen p2 ausreicht oder doch irgendwelche unerwünschten Nebenwirkungen hat. Ohne Daten und entsprechende Tabellen ist das halt doch eher Gehirnjogging mit großem Ratefaktor, als fundierte Lösungsfindung.
select k1.kdnr
from kunden k1 join rechnungen r1 on r1.kdnr = k1.kdnr join positionen p1 on p1.renr = r1.renr and p1.artikel = 'X' join rechnungen r2 on r2.kdnr = k1.kdnr join positionen p2 on p2.renr = r2.renr and p2.artikel = 'Y' having (Count(p1.artiklel) > 0) and (Count(p2.artikel) = 0) Aggregatfunktionen benötigen immer ein Group by. Select * funktioniert bei Group by nie. Alles, was im Select aufgeführt wird, muss entweder im Group By stehen oder als Aggregatfunktion im Select erscheinen. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Wie wäre es so?
SQL-Code:
Eine Zählung ist ja nicht notwendig. Es geht ja nur um die Existenz.
SELECT DISTINCT K.*
FROM Kunden K INNER JOIN Rechnungen R ON K.KdNr = R.KdNr INNER JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = A LEFT JOIN Positionen P2 ON R.ReNr = P2.ReNr AND P2.Artikel = B WHERE P2.ReNr IS NULL; Testcode für ![]()
SQL-Code:
create table Kunden (
KdNr int, Name varchar(255) ); create table Rechnungen ( KdNr int, ReNr int ); create table Positionen ( ReNr int, Artikel int ); insert into Kunden (KdNr, Name) VALUES (1, 'Meier'); insert into Kunden (KdNr, Name) VALUES (2, 'Schulz'); insert into Rechnungen (KdNr, ReNr) VALUES (1, 100); insert into Rechnungen (KdNr, ReNr) VALUES (2, 200); insert into Positionen (ReNr, Artikel) VALUES (100, 4701); insert into Positionen (ReNr, Artikel) VALUES (100, 4702); insert into Positionen (ReNr, Artikel) VALUES (200, 4701); insert into Positionen (ReNr, Artikel) VALUES (200, 5702); SELECT DISTINCT K.* FROM Kunden K INNER JOIN Rechnungen R ON K.KdNr = R.KdNr INNER JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = 4701 LEFT JOIN Positionen P2 ON R.ReNr = P2.ReNr AND P2.Artikel = 5702 WHERE P2.ReNr IS NULL; |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
Code:
oder
select k2.*
from ( select k1.kdnr from kunden k1 join rechnungen r1 on r1.kdnr = k1.kdnr join positionen p1 on p1.renr = r1.renr and p1.artikel = 'X' left outer join positionen p2 on p2.renr = r1.renr and p2.artikel = 'Y' group by k1.kdnr having (Count(p1.artiklel) > 0) and (Count(p2.artikel) = 0)) as Treffer join kunden k2 on k2.kdnr = Treffer.kdnr
Code:
Es gibt meines Wissens aber DBs, die eine Begrenzung bei der Anzahl im Ergebnis von "in" haben. Vermutlich ist die zweite Variante bei großen Datenmengen auch langsamer.
select k2.*
from kunden k2 where k2.kdnr in ( select k1.kdnr from kunden k1 join rechnungen r1 on r1.kdnr = k1.kdnr join positionen p1 on p1.renr = r1.renr and p1.artikel = 'X' left outer join positionen p2 on p2.renr = r1.renr and p2.artikel = 'Y' group by k1.kdnr having (Count(p1.artiklel) > 0) and (Count(p2.artikel) = 0)) So müssten auch alle Kundenfelder verfügbar sein. Es gibt auch noch andere Lösungen. Man könnte z.B. mit "with" arbeiten oder auf der DB passende Views erstellen. Die SQL sind ungetestet. Könnte also sein, dass das nicht auf Anhieb funktioniert. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
ich finde sowas einfacher zu verstehen und modularer, weil auch mit anderen bedingungen einfach zu kombinieren und trotzdem
die unterbedingungen einfach lesbar sind.
Code:
SELECT DISTINCT K.*
FROM Kunden K where exists(select * from Rechnungen R JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = 4701 where K.KdNr = R.KdNr) and not exists(select * from Rechnungen R JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = 5702 where K.KdNr = R.KdNr) |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
Man könnte auch nur LEFT JOINs verwenden, aber sofern die Tabellen indiziert sind, sollte es mit dem INNER JOIN schneller sein. Beim dritten JOIN muss wiederum ein LEFT JOIN verwendet werden, damit die Nullwerte erhalten bleiben. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
Code:
werden ja nur Rechnungen selektiert, bei denen mindestens ein A in den Positionen vorkommt. Damit werden Rechnungen, bei denen kein A aber ein B vorkommt nicht berücksichtigt. Wenn also ein Kunde eine Rechnung nur mit A und eine nur mit B hat, würde er fälschlicherweise in die Ergebnismenge aufgenommen. Oder sehe ich das falsch?
INNER JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = A
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Nein, denn wie du sagst, die Kunden mit B fliegen in der Zeile ja schon raus.
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Hmmm, ich habe mal dein Beispiel entsprechend ergänzt:
Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Ja, da hast du Recht. Mit einem join in der Form sollte es aber gehen:
SQL-Code:
SELECT DISTINCT K.KdNr, K.Name
FROM Kunden K INNER JOIN Rechnungen R ON K.KdNr = R.KdNr INNER JOIN Positionen PA ON R.ReNr = PA.ReNr AND PA.Artikel = 4701 LEFT JOIN ( SELECT DISTINCT R.KdNr FROM Rechnungen R INNER JOIN Positionen PB ON R.ReNr = PB.ReNr WHERE PB.Artikel = 5702 ) AS KundenMitArtikelB ON K.KdNr = KundenMitArtikelB.KdNr WHERE KundenMitArtikelB.KdNr IS NULL; |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Aber damit ist die Vermutung widerlegt:
Zitat:
SQL-Code:
Schade, dass FireBird
create table Kunden (
KdNr int, Name varchar(255) ); create table Rechnungen ( KdNr int, ReNr int ); create table Positionen ( ReNr int, Artikel int ); insert into Kunden (KdNr, Name) VALUES ( 1, 'Meier'); insert into Rechnungen (KdNr, ReNr) VALUES ( 1, 100); insert into Rechnungen (KdNr, ReNr) VALUES ( 1, 101); insert into Positionen (ReNr, Artikel) VALUES (100, 4701); insert into Positionen (ReNr, Artikel) VALUES (100, 4702); insert into Positionen (ReNr, Artikel) VALUES (101, 5702); insert into Kunden (KdNr, Name) VALUES ( 2, 'Schulz'); insert into Rechnungen (KdNr, ReNr) VALUES ( 2, 200); insert into Positionen (ReNr, Artikel) VALUES (200, 4701); insert into Positionen (ReNr, Artikel) VALUES (200, 5702); insert into Kunden (KdNr, Name) VALUES ( 3, 'Gärtner'); insert into Rechnungen (KdNr, ReNr) VALUES ( 3, 300); insert into Positionen (ReNr, Artikel) VALUES (300, 4701); insert into Kunden (KdNr, Name) VALUES ( 4, 'Bauer'); insert into Rechnungen (KdNr, ReNr) VALUES ( 4, 400); insert into Positionen (ReNr, Artikel) VALUES (400, 4702); commit; select kdnr, name from ( select K.kdnr, k.name, sum(case when Coalesce(p1.ReNr,0) <> 0 then 1 else 0 end) as HatX, sum(case when Coalesce(p2.ReNr,0) <> 0 then 1 else 0 end) as HatY from Kunden K inner join Rechnungen R1 on K.KdNr = R1.KdNr left join Positionen P1 on R1.ReNr = P1.ReNr and P1.Artikel = 4701 inner join Rechnungen R2 on K.KdNr = R2.KdNr left join Positionen P2 on R2.ReNr = P2.ReNr and P2.Artikel = 5702 group by k.kdnr, k.name ) where HatX > 0 and HatY = 0 order by kdnr, name ![]()
SQL-Code:
möglich.
select * from kunden
join rechnungen on rechnungen.kdnr = kunden.kdnr join positionen on positionen.renr = rechnungen.renr and positionen.artikel = 4701 minus select * from kunden join rechnungen on rechnungen.kdnr = kunden.kdnr join positionen on positionen.renr = rechnungen.renr and positionen.artikel = 5702 |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Da es mir keine Ruhe lässt, eine Lösung ohne Subselect zu finden, hab' ich mal den ChatBot meiner Wahl befragt, diese Lösung wurde extrem schnell geliefert:
Zitat:
Zitat:
SQL-Code:
SELECT k.*
FROM Kunden k WHERE EXISTS ( SELECT 1 FROM Rechnungen r JOIN Positionen p ON r.ReNr = p.ReNr WHERE r.KdNr = k.KdNr AND p.Artikel = 'X' ) AND NOT EXISTS ( SELECT 1 FROM Rechnungen r JOIN Positionen p ON r.ReNr = p.ReNr WHERE r.KdNr = k.KdNr AND p.Artikel = 'Y' ) Zitat:
Zitat:
Zitat:
SQL-Code:
SELECT k.KdNr, k.Name
FROM Kunden k JOIN Rechnungen r ON k.KdNr = r.KdNr JOIN Positionen p ON r.ReNr = p.ReNr GROUP BY k.KdNr, k.Name HAVING SUM(CASE WHEN p.Artikel = 4701 THEN 1 ELSE 0 END) > 0 AND SUM(CASE WHEN p.Artikel = 5702 THEN 1 ELSE 0 END) = 0 Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
sum über case ist aber vermutlich die langsamste Variante, weil dies kaum optimiert werden kann. Meine letzte Variante ist auch kein echtes Subselect, weil die zweite Abfrage mit join kombiniert und nicht in jeder Zeile ausgeführt wird.
Holger hat mit seinem Einwand aber natürlich auch Recht: Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
SQL-Code:
Allerdings lassen die nur rudimentär vorhandenen Testdaten eigentlich keine belastbare Aussage zu.
SELECT K.KdNr, K.Name
FROM Kunden K INNER JOIN Rechnungen R ON K.KdNr = R.KdNr LEFT JOIN Positionen P1 ON R.ReNr = P1.ReNr AND P1.Artikel = 4701 LEFT JOIN Positionen P2 ON R.ReNr = P2.ReNr AND P2.Artikel = 5702 GROUP BY K.KdNr, K.Name HAVING COUNT(P1.ReNr) > 0 and COUNT(P2.ReNr) = 0; Unschön ist halt, dass man die Kunden-Felder zumindest im GROUP BY einzeln aufführen muss (Ich kenne zumindest kein System, das dort ein K.* akzeptiert). Allerdings ist man dann vielleicht eher gewillt, nur die wirklich benötigten Felder anzugeben. Wäre ja denkbar, dass lediglich die KdNr benötigt wird, dann wird es recht simpel. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
Zitat:
Zitat:
Zitat:
![]() Interessant ist: Wir haben jetzt acht unterschiedliche und funktionierende Lösungen zur Aufgabenstellung. |
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
Zitat:
|
AW: Abfrage: Kund hat Artikel X aber nicht Y gekauft
so als technische basisinfo noch:
ein sum befehl wird immer die komplette ergebnismenge abklappern, spricht, wenn du tausend kunden hast die alle je 1000 rechnungen mit dem verkauften produkt hatten, kommt der server nicht drumherum, alle eine Million datensätze zu analysieren, um die komplett unwichtige Information der summe zu ermitteln (war ja im threadtitel irrelevant). Ein exists ist zumindest bei firebird so implementiert, das dieser sobald ein record in der ergebnismenge gefunden wurde (bei der hoffentlich passende indizes nutzbar sind) sofort die weiteren teilergebnisse ignoriert, also in dem o.a. fall mit passenden indizes statt 1000000 operationen das ergebnis mit 1000 operationen ermitteln kann. Bei single user datenbanken wird das kaum keiner merken, wenn aber dutzende oder hunderte user solche abfragen in schlechter version parallel machen und so ermittelte unwichtige count oder sum befehle auf einem Startscreen einer Applikation darstellt, dann wird der server niemals performant sein, weil der immer nur schwachsinn mit mörderaufwand treibt. wenn dann noch dazu kommt, das die 1000000 rechnungen und die 1000 kunden nicht über passende indizes verfügen, reden wir auch intern nicht über 1000000 operationen sondern schnell mal über 1000000000 operationen. firebird liefert sehr gute hilfsmittel um so was für die eigenen SQLs zu erkennen, aber das Design der eigenen SQLs muss der programmierer verantworten. Wenn man eine summe brauchen würde geht es nicht anders, aber aus der summe >= eigentlich nur die existenz auszuwerten ist um es mal freundlich zu sagen unglücklich programmiert. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:58 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz