The Lost Datarows: DISTINCT - Verhalten bei Join, Concatenate

Normalerweise erzähle ich meinen QlikView-Novizen immer, dass man ein QlikView Skript von oben nach unten (und - falls Reiter vorhanden - von links nach rechts) liest. Innerhalb eines Load&Select-Statements gilt die Ausnahme, dass man diesen Statement-Block von unten nach oben lesen muss. Zuerst kommen die Daten aus einer Datenquelle (unten), dann werden sie mit einem (oder mehreren) Load-Statement weiterverarbeitet.

Hier ein einfaches Beispiel - aus der Ursprungstabelle lade ich mit einem oberhalb liegenden LOAD DISTINCT die eindeutigen Preise für die 3 Produkte. Das Statement muss von unten (Inputtabelle hat 4 Zeilen*3 Spalten) nach oben  (Ergebnistabelle hat 3 Zeilen*2 Spalten) gelesen werden.

Es gibt jedoch einen Fall den man genauer betrachten muss: LOAD DISTINCT in Kombination mit JOIN bzw. CONCATENATE:

Ergänzt man das Skript um einen Left Join auf die Faktentabelle, sieht das Skript folgendermaßen aus:

Facts:
LOAD * INLINE [
    Year, Product, Quantity
    2013, ProductA, 500
    2013, ProductA, 500 
    2013, ProductB, 300
    2012, ProductA, 700
    2012, ProductB, 400
];

left join (Facts)
Load
Distinct
 Product,
 Price;
LOAD * INLINE [
    Product, Price, Variante
    ProductA, 100, X
    ProductA, 100, Y
    ProductB, 10, A
    ProductC, 700, A
];


Liest man das Skript nach den genannten Regeln, würde man folgendes Join-Verhalten erwarten:
  • (Schritt -1: Die Faktentabelle wird  mit 5 Zeilen geladen)
  • Schritt 0: Die Produkttabelle wird mit 4 Zeilen INLINE geladen
  • Schritt 1: Load Distinct reduziert die Produkttabelle auf 3 Zeilen
  • Schritt 2: Der Left Join beläst per Definition die Faktentabelle (linke Tabelle) gleich und ergänzt die passenden Preise aus der Produkttabelle (rechten Tabelle).
Symbolische Darstellung des "zu erwartenden" Verhalten

Als Ergebnis würde man sich also eine Quantity von (500+500+300+700+400) = 2400 erwarten.

Lässt man das Script aber in QlikView laufen, erhält man überraschenderweise ein Chart mit der Quantity 1900:


Quanity für ProductA ist 2013 nur 500
Was ist passiert? Die Quantity für ProductA ist im Jahr 2013 nur 500, statt der ursprünglichen 1000 aus der Faktentabelle. Die Faktentabelle enthält auch nur 4 statt der ursprünglichen 5 Zeilen. Es scheint also, als ob der LEFT JOIN Einfluss auf unsere Faktentabelle genommen hat.

Betrachtet man das Ergebnis genauer, so ist es nicht der LEFT JOIN, sondern vielmehr das Keyword DISTINCT auf der Produkttabelle das Schuld an unserem fehlenden Datensatz ist. Das DISTINCT hat nicht nur Einfluss auf die geladene Produkttabelle, sondern reduziert auch alle Einträge in der Faktenabelle auf eindeutige Werte.  


Beim Lesen eines QlikView Skrips muss also folgende zusätzliche Regel beachtet werden:


DISTINCT wird in QlikView nicht auf die Inputtabelle angewandt, sondern am auf die gesamte, geladene Tabelle! Wird eine Tabelle also einmalig als DISTINCT markiert (direkt oder indirekt [durch Join/Concatenate]) sind die Zeilen diese Tabelle in allen weiteren Operationen IMMER DISTINCT!

Um aus der ursprünglichen Faktentabelle nicht "versehentlich" Zeilen zu entfernen, benötigen wir somit eine Zwischentabelle. Das Skript sieht dann folgendermaßen aus

FixFacts:
LOAD * INLINE [
    FixYear, FixProduct, FixQuantity
    2013, ProductA, 500
    2013, ProductA, 500 
    2013, ProductB, 300
    2012, ProductA, 700
    2012, ProductB, 400
];

Temp:
Load
Distinct
 FixProduct,
 FixPrice;
LOAD * INLINE [
    FixProduct, FixPrice, FixVariante
    ProductA, 100, X
    ProductA, 100, Y
    ProductB, 10, A
    ProductC, 700, A
];

left join (FixFacts)
load
*
resident Temp;

drop table Temp;
 
 
Quantity 2400 als korrektes Ergebnis

Hier klappt es nun wie erwartet, weil nur die "Temp"-Tabelle mit dem Keyword DISTINCT markiert wurde. Die "FixFacts" Tabelle ist an keiner Stelle mit dem Keyword DISTINCT versehen. Somit bleibt das ProduktA im Jahr 2013 mit einer Quantity von 500 zweimal in der Tabelle erhalten.

Ein zweites Szenario wo man dieses Verhalten manchmal unabsichtlich auslöst, ist der Befehl CONCATENATE. Das Skript unterhalb löscht Zeilen aus meiner Istdaten-Tabelle "ConcatFacts", obwohl ich eigentlich nur die Plandaten DISTINCT daran "anhängen" wollte:

ConcatFacts:
LOAD * INLINE [
    CYear, CProduct, CQuantity
    2013, ProductA, 500
    2013, ProductA, 500 
    2013, ProductB, 300
];

//Append Plan Data
concatenate(ConcatFacts)
LOAD distinct * INLINE [
    CYear, CProduct, Plan
    2013,ProductA, 900
    2013,ProductA, 900
    2013,ProductB, 200
]; 
 
Ist-Quantity von ProduktA ist nach CONCATENATE & DISTINCT nur 500

Auch hier hilft wieder der Umweg über eine temporäre "DISTINCT"-Tabelle
FixConcatFacts:
LOAD * INLINE [
    CCYear, CCProduct, CCQuantity
    2013, ProductA, 500
    2013, ProductA, 500 
    2013, ProductB, 300
];

//Append Plan Data
Tmp:
LOAD distinct * INLINE [
    CCYear, CCProduct, CCPlan
    2013,ProductA, 900
    2013,ProductA, 900
    2013,ProductB, 200
];

Concatenate(FixConcatFacts)
load
*
Resident Tmp;

drop table Tmp;


Quantity 1000 für ProductA als korrektes Ergebnis


Zuletzt hatten wir dieses "Phänomen" am MasterSummit for QlikView diskutiert. Das Verhalten ist zumindest seit QlikView 8.* (meiner ersten QlikView Version) konsistent. Falls Sie diese Eigenschaft von DISTINCT noch nicht kannten, und Ihnen in Ihren Skripten Datenzeilen abgehen, durchsuchen Sie Ihre QlikView Applikationen nach den beschriebenen Mustern! Das komplette Beispiel zu diesem Blogeintrag findet sich unter http://content.heldendaten.eu/DistinctVerhalten.zip




heldendaten GmbH,2017