Eines dieser kritischen Szenarien tritt bei Joins auf, die komplexe Bedingungen beinhalten, insbesondere die Verwendung von OR in Kombination mit on-the-fly-Variablentransformationen in der ON-Klausel.
Dieser Artikel untersucht, wie man eine ineffiziente SQL-Abfrage mithilfe von Hash-Tabellen (Hash Objects) in einen ultraschnellen Data-Schritt umwandelt.
Stellen wir uns folgendes Szenario vor, das bei der Verarbeitung von Bank- oder Verwaltungsdaten häufig vorkommt:
Sie müssen zwei mittelgroße Tabellen (z. B. 500.000 Zeilen und 300.000 Zeilen) zusammenführen. Die Herausforderung besteht darin, dass die Join-Schlüssel nicht direkt sind. Die Kennungen sind numerisch gespeichert, und der Abgleich erfordert die Umwandlung dieser Zahlen in Zeichenketten und die anschließende Extraktion spezifischer Teilzeichenketten. Schlimmer noch, die Join-Bedingung enthält ein OR, was bedeutet, dass eine Übereinstimmung auf zwei verschiedene Weisen erfolgen kann.
So sieht der Engpass in SQL aus:
PROC SQL noprint;
CREATE TABLE Resultat AS
SELECT DISTINCT
T1.ID_Parent_L,
T2.CodeCedant,
T2.ID_Client,
T1.Nom
FROM TABLE_PRINCIPALE T1
INNER JOIN TABLE_SECONDAIRE T2
/* La source du problème : conversions et condition OR */
ON (substr(put(T1.ID_Parent_L,14.),7,8) = substr(put(T2.ID_Client,14.),1,8)
OR substr(put(T1.ID_Parent_L,14.),1,8) = substr(put(T2.ID_Client,14.),1,8))
;
QUIT;
1
PROC SQL noprint;
2
CREATE TABLE Resultat AS
3
SELECT DISTINCT
4
T1.ID_Parent_L,
5
T2.CodeCedant,
6
T2.ID_Client,
7
T1.Nom
8
FROM TABLE_PRINCIPALE T1
9
INNER JOIN TABLE_SECONDAIRE T2
10
/* La source du problème : conversions et condition OR */
11
ON (substr(put(T1.ID_Parent_L,14.),7,8) = substr(put(T2.ID_Client,14.),1,8)
12
OR substr(put(T1.ID_Parent_L,14.),1,8) = substr(put(T2.ID_Client,14.),1,8))
Die OR-Klausel: Die Verwendung von OR in einem Join hindert den SQL-Optimierer oft daran, Indizes effizient zu nutzen. Dies zwingt das System oft, ein teilweises oder vollständiges „kartesisches Produkt“ durchzuführen, bei dem jede Zeile der Tabelle T1 mit einem großen Teil der Zeilen von T2 verglichen wird.
Fehlen von eindeutigen Schlüsseln: Wenn die Join-Schlüssel in beiden Tabellen nicht eindeutig sind, steigt die Komplexität exponentiell an.
In einer solchen Situation ist auch ein klassischer MERGE-Join in einem Data-Schritt schwer umzusetzen, da er eine vorherige Sortierung nach transformierten Schlüsseln erfordert. Hier kommen die Hash-Objekte ins Spiel.
Die Lösung: Der DATA-Schritt und Hash-Objekte
Hash-Objekte ermöglichen es, eine ganze Tabelle (normalerweise die kleinere von beiden) in den Arbeitsspeicher (RAM) zu laden. Dadurch entsteht eine ultraschnelle Suchstruktur, in der man eine Übereinstimmung sofort finden kann, ohne die Daten sortieren zu müssen.
Die Strategie besteht darin:
Die Referenztabelle (die kleinere) vorzubereiten, indem ein sauberer Suchschlüssel erstellt wird.
Diese Tabelle in ein Hash-Objekt im Speicher zu laden.
Die große Tabelle Zeile für Zeile zu lesen, den Suchschlüssel on-the-fly zu berechnen und das Hash-Objekt abzufragen.
Schritt 1: Vorbereitung der Referenzdaten
Anstatt komplexe Berechnungen im Join durchzuführen, bereiten wir die sekundäre Tabelle (TABLE_SECONDAIRE) so vor, dass sie einen gebrauchsfertigen Suchschlüssel (KEY) enthält, der dem Teil der Kennung entspricht, den wir suchen.
PROC SQL;
CREATE TABLE HashData AS
SELECT UNIQUE
CodeCedant,
ID_Client,
/* Création de la clé de jointure standardisée */
put(put(ID_Client,14.),$8.) AS KEY
FROM TABLE_SECONDAIRE;
QUIT;
1
PROC SQL;
2
CREATE TABLE HashData AS
3
SELECT UNIQUE
4
CodeCedant,
5
ID_Client,
6
/* Création de la clé de jointure standardisée */
7
put(put(ID_Client,14.),$8.) AS KEY
8
FROM TABLE_SECONDAIRE;
9
QUIT;
Schritt 2: Der DATA-Schritt mit Hash-Objekt
Hier ist der Code, der den komplexen SQL-Join ersetzt.
DATA Resultat_Hash;
/* On lit la table principale ligne par ligne */
SET TABLE_PRINCIPALE (keep=ID_Parent_L Nom);
/* Initialisation de l'objet de hachage (une seule fois au début) */
if _N_=1 then do;
/* Déclaration : on charge la table préparée en mémoire */
dcl hash H(dataset:'HashData');
/* Définition de la clé de recherche */
H.definekey('KEY');
/* Définition des données à récupérer en cas de succès */
H.definedata('CodeCedant','ID_Client');
H.definedone();
/* Astuce pour éviter de lire HashData ici, mais initialiser les variables */
if 0 then set HashData;
end;
/* --- Cœur du traitement --- */
/* Tentative 1 : On calcule la clé potentielle selon la première logique */
RC = H.find(key: put(put(ID_Parent_L,14.),$8.));
/* Si RC (Code Retour) = 0, la recherche a réussi.
Les variables CodeCedant et ID_Client sont remplies automatiquement. */
if RC=0 then output;
else do;
/* Optionnel : Si votre logique SQL initiale avait un "OR",
vous pouvez tenter une deuxième recherche ici si la première échoue.
Exemple :
RC = H.find(key: substr(put(ID_Parent_L,14.),7,8));
if RC=0 then output;
*/
end;
drop RC;
run;
1
DATA Resultat_Hash;
2
/* On lit la table principale ligne par ligne */
3
SET TABLE_PRINCIPALE (keep=ID_Parent_L Nom);
4
5
/* Initialisation de l'objet de hachage (une seule fois au début) */
6
IF _N_=1THENDO;
7
/* Déclaration : on charge la table préparée en mémoire */
8
dcl hash H(dataset:'HashData');
9
/* Définition de la clé de recherche */
10
H.definekey('KEY');
11
/* Définition des données à récupérer en cas de succès */
12
H.definedata('CodeCedant','ID_Client');
13
H.definedone();
14
/* Astuce pour éviter de lire HashData ici, mais initialiser les variables */
15
IF 0 THENSET HashData;
16
END;
17
18
/* --- Cœur du traitement --- */
19
20
/* Tentative 1 : On calcule la clé potentielle selon la première logique */
21
RC = H.find(key: put(put(ID_Parent_L,14.),$8.));
22
23
/* Si RC (Code Retour) = 0, la recherche a réussi.
24
Les variables CodeCedant et ID_Client sont remplies automatiquement. */
25
IF RC=0 THENOUTPUT;
26
ELSEDO;
27
/* Optionnel : Si votre logique SQL initiale avait un "OR",
28
vous pouvez tenter une deuxième recherche ici si la première échoue.
Der Leistungsgewinn beim Übergang von einem komplexen SQL-Join mit OR-Bedingungen und Konvertierungen zu einer auf Hash-Objekten basierenden Lösung ist oft spektakulär. Verarbeitungen, die Stunden dauerten oder den Server zum Absturz brachten, können in wenigen Sekunden ausgeführt werden.
Die auf WeAreCAS.eu bereitgestellten Codes und Beispiele dienen Lehrzwecken. Es ist zwingend erforderlich, sie nicht blind in Ihre Produktionsumgebungen zu kopieren. Der beste Ansatz besteht darin, die Logik zu verstehen, bevor sie angewendet wird. Wir empfehlen dringend, diese Skripte in einer Testumgebung (Sandbox/Dev) zu testen. WeAreCAS übernimmt keine Verantwortung für mögliche Auswirkungen oder Datenverluste auf Ihren Systemen.
SAS und alle anderen Produkt- oder Dienstleistungsnamen von SAS Institute Inc. sind eingetragene Marken oder Marken von SAS Institute Inc. in den USA und anderen Ländern. ® zeigt die Registrierung in den USA an. WeAreCAS ist eine unabhängige Community-Site und nicht mit SAS Institute Inc. verbunden.
Diese Website verwendet technische und analytische Cookies, um Ihre Erfahrung zu verbessern.
Mehr erfahren.