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.
Los códigos y ejemplos proporcionados en WeAreCAS.eu son con fines educativos. Es imperativo no copiarlos y pegarlos ciegamente en sus entornos de producción. El mejor enfoque es comprender la lógica antes de aplicarla. Recomendamos encarecidamente probar estos scripts en un entorno de prueba (Sandbox/Dev). WeAreCAS no acepta ninguna responsabilidad por cualquier impacto o pérdida de datos en sus sistemas.
SAS y todos los demás nombres de productos o servicios de SAS Institute Inc. son marcas registradas o marcas comerciales de SAS Institute Inc. en los EE. UU. y otros países. ® indica registro en los EE. UU. WeAreCAS es un sitio comunitario independiente y no está afiliado a SAS Institute Inc.
Este sitio utiliza cookies técnicas y analíticas para mejorar su experiencia.
Saber más.