Inhalt einer Macro-Variable sortieren

Hallo,
ich hätte gerne eine elegante Lösung, um den Inhalt einer Macro-Variable zu sortieren. Die Macro Variable enthält eine Feld-Liste und ich will damit die Variablen-Namen in einem DWH-Standard-Data-Set einheitlich in GROSS-Buchstaben (ist per %upcase gelöst) umsetzen und die Reihenfolge der Variablen in der Datenstruktur soll damit alphabetisch umsortiert werden.
Mich nervt, dass ich immer die gerade benötigte Variable im proc print-Output suchen muß.
Wenn die Variablen sortiert sind, dann brauche ich nicht mehr zu suchen.
Auch ein Edit->Find im Output-Fenster hilft meistens nicht weiter, denn wenn die linesize klein ist, schreibt SAS die Name hochkant ...

Test-Daten könnten wie folgt aussehen:

data test;
  z22="yyy";
  abc=1;
  cde=2;
  a11="xxx";
run;
%let FeldList=%upcase(%VarList(test));
%put INFO: FeldList=&FeldList.;

Die umständliche Variante ist mir klar, z.B. so:

Code der umständlichen Lösung

Jetzt ist das Gerödel fertig und man kann die Feld-Liste verwerten, z.B. so:

proc print data %str(=) test;  /* unsortiert und mit Mixed-Case */
run;
data test;
  retain &FeldLiSo.;
  set test;
run;
proc print data %str(=) test;  /* sortiert und mit upcase */
run;

Bin gespannt auf die Antworten ...!
Gruß
Hans Kneilmann, Schäfer Shop GmbH

P.S. Nicht über das %str(=) wundern, das stört beim Testen überhaupt nicht und es dient als Trick 17 dazu das PHP in/auf dem Redscope-Server auszutricksen. Der "echte" Text/Code würde zu Fehlern beim "Speichern" führen

Sortierung

Hallo,

so müsste es eigentlich gehen:

data test;
  z22=
"yyy";
  abc=
1;
  cde=
2;
  a11=
"xxx";
run;
%macro varsrt(dsn);

%let dsid=%sysfunc(open(&dsn));
%let n=%sysfunc(attrn(&dsid,NVARS));
%do i = 1 %to &n.;
%let varname&i=%upcase(%sysfunc(varname(&dsid,&i)));
%put &&varname&i;
%end;
%let rc=%sysfunc(close(&dsid));
%do %until(&sortiert);
        
%let sortiert = 1;
        
%do i = 1 %to %eval(&n - 1);
            
%let j=%eval(&i+1);
            
%if &&varname&i > &&varname&j %then %do;
                
%let temp = &&varname&i;
                
%let varname&i = &&varname&j;
                
%let varname&j = &temp;
                
%let sortiert = 0;
            
%end;
        
%end;
%end;
%do i = 1 %to &n.;
&&varname&i
%end;


%mend varsrt;
data test;
retain
%varsrt(test);
;
set test;
run;

Gruß
Wolfgang Hornung

Hut ab!

Das ging aber schnell.
Funktioniert einwandfrei bis 100 Variablen.
Wird mit mehr Variablen dann aber sehr langsam, da die Rechenzeit von Bubblesort quadratisch zur Anzahl der sortierten Elemente ansteigt. Wer wollte verlangen, dass jetzt jemand einen Quicksort implementiert... ;-)

Nochmal ein Sort

Hallo,

weil es so schön war eben auch noch ein Quicksort ;-)

data test;
  z22=
"yyy";
  abc=
1;
  cde=
2;
  a11=
"xxx";
  x5=
1;
  aaa=
0;
run;
options mlogic symbolgen;
%macro qsort(left,right);
%if &left < &right %then %do;
   
%let pivot = &&varname&right;
   
%let l     = &left;
   
%let r     = &right;
   
%do %while (&l. <= &r.);
      
%do %while (&&varname&l < &pivot);
         
%let l=%eval(&l.+1);
      
%end;
      
%do %while (&&varname&r > &pivot);
         
%let r=%eval(&r.-1);
      
%end;
      
%if &l. <= &r. %then %do;
         
%let temp=&&varname&l;
         
%let varname&l=&&varname&r;
         
%let varname&r=&temp;
         
%let l=%eval(&l.+1);
         
%let r=%eval(&r.-1);
      
%end;
   
%end;
   
%if &left. < &r. %then %qsort(&left.,&r.);
   
%if &l. < &right. %then %qsort(&l.,&right.);
%end;
%do i = 1 %to &n.;
    &&varname&i
%end;
%mend qsort;

%macro dsinit(dsn);
%let dsid=%sysfunc(open(&dsn));
%global n;
%let n=%sysfunc(attrn(&dsid,NVARS));
%do i = 1 %to &n.;
   
%global varname&i.;
   
%let varname&i=%upcase(%sysfunc(varname(&dsid,&i)));
%end;
%let rc=%sysfunc(close(&dsid));
%qsort(
1,&n.);
%mend dsinit;

data test;
retain
%dsinit(test);
;
set test;
run;

Eine schöne Beschreibung der Sortvarianten mit diversen Implementierungen (leider nicht in SAS) steht, wo auch sonst hier .
Ein Quicksort "kostet" im Worst Case O(n^2), wie auch ein Bubblesort.
Im "Normalfall" kostet er O(n*log(n)) und ist hier günstiger als der Bubblesort (O(n^2)). Nur im Best Case ist der Bubblesort schneller als der Quicksort ( O(n) O(n*log(n)) ).

Gruß
Wolfgang Hornung

Beispiel funktioniert ...

und ist auch sehr beeindruckend, und ein tolles Beispiel dafür, wie man mit Makros rekursiv programmieren kann, aber: wenn man mal mprint einschaltet und symbolgen und mlogic aus, sieht man, dass die Variablenliste sechsmal ausgegeben wird. Da scheint noch ein kleiner Fehler bei der Rückgabe der Makrovariablen zu existieren, oder täusche ich mich?

Fehler

das ist wohl richtig, die letzte %do... Schleife im Makro %qsort ist wohl dafür veranwortlich.

So geht es aber:

data test;
  z22=
"yyy";
  abc=
1;
  cde=
2;
  a11=
"xxx";
  x5=
1;
  aaa=
0;
run;
options mlogic symbolgen mprint;
%macro qsort(left,right);
%if &left < &right %then %do;
   
%let pivot = &&varname&right;
   
%let l     = &left;
   
%let r     = &right;
   
%do %while (&l. <= &r.);
      
%do %while (&&varname&l < &pivot);
         
%let l=%eval(&l.+1);
      
%end;
      
%do %while (&&varname&r > &pivot);
         
%let r=%eval(&r.-1);
      
%end;
      
%if &l. <= &r. %then %do;
         
%let temp=&&varname&l;
         
%let varname&l=&&varname&r;
         
%let varname&r=&temp;
         
%let l=%eval(&l.+1);
         
%let r=%eval(&r.-1);
      
%end;
   
%end;
   
%if &left. < &r. %then %qsort(&left.,&r.);
   
%if &l. < &right. %then %qsort(&l.,&right.);
%end;
%mend qsort;

%macro dsinit(dsn);
%let dsid=%sysfunc(open(&dsn));
%global n;
%let n=%sysfunc(attrn(&dsid,NVARS));
%do i = 1 %to &n.;
   
%global varname&i.;
   
%let varname&i=%upcase(%sysfunc(varname(&dsid,&i)));
%end;
%let rc=%sysfunc(close(&dsid));

%qsort(
1,&n.) /* Hier darf dann kein Semikolon stehen, warum eigentlich ??*/

%do i = 1 %to &n.;
    &&varname&i
%end;

%mend dsinit;

data test;
retain
%dsinit(test);
;
set test;
run;

Gruß
Wolfgang Hornung

Warum kein Semikolon?

... weil Semikolons in Makros nur nach Makroanweisungen die Anweisung beenden, wie man es von SAS gewohnt ist. Ein Makroaufruf ist aber keine Makroanweisung, sondern erzeugt Programmtext. Wenn man ein Semikolon dahinter schreibt, dann ist das für den Makro Programmtext, den er einfach zurückgibt. Das gilt eigentlich immer dann, wenn man Makros nicht benutzt, um komplette SAS-Anweisungen zu generieren, sondern um Teile von SAS-Anweisungen zu generieren, wie es hier der Fall ist.

Super-high-tech-Lösung

Hallo,
die obige Lösung (Wolfgang Hornung, 11 Juli 2006 - 14:03) ist ja echt super. Vielen Dank!
Frage an Andreas Mangold: Ihre Aussage "Funktioniert einwandfrei bis 100 Variablen" gilt doch sicherlich nur in Bezug auf die Laufzeit, oder?
Bei uns sind diese DWH-Standard-Data-Sets recht "breit", der Spitzenreiter hat zur Zeit 93 Variablen ....
Die Laufzeit dieses Schrittes ist (für uns) relativ unkritisch. Er wäre Teil eines Batch-Jobs, der sowieso "ewig" läuft. Z.B. 1H15M für 8 solcher DWH-Standard-Data-Sets nach der täglichen Übername aus dem operativen System. Es käme sogar auf 8 Minuten "mehr" nicht so richtig an, falls der %varsrt wirklich jew. 1 Minute brauchen würde. Aber das ist nicht der Fall!
Test mit 93 Vars, 10.000 obs ergibt 1.52 sec 'ohne' %varsrt und 5.32 seconds 'mit' %varsrt (auf einer Sun V880, CPU=4, 900MHz, RAM=8GB mit Solaris 8).

Hans Kneilmann, Schäfer Shop GmbH (SSI)

Laufzeit

Ja, diese Antwort bezog sich nur auf die Laufzeit, richtige Ergebnisse liefert es auch bei 10000 Variablen. Aber da die Laufzeit von Quicksort quadratisch ansteigt, wird es bei 1000 Variablen eben 100 mal so lange brauchen wie bei 100 Variablen und nicht 10 mal so lange.

Sort

Wenn ich es richtig verstehe, soll kein DATA- oder PROC-Schritt verwendet werden?
Dann müsste sich nach meiner Einschätzung wohl jemand daran machen, einen Bubblesort o.ä. für Makrovariablen zu programmieren.

Makrovariablen sortieren

Hallo,
es ist mir eigentlich egal "wie" es geht, es sollte nur etwas eleganter sein als meine umständliche Lösung.
Ich dachte, vielleicht kennt jemand den super-eleganten Trick oder es gibt eine (mir bisher unbekannte) SAS-Funktion oder aus der Sample-Library gibt es etwas oder ...
Die Lösung oben ist ja echt "high tech"!
Gruß
Hans Kneilmann, Schäfer Shop GmbH (SSI)

Makrovariablen sortieren

Hallo Herr Kneilmann,

vielleicht finden Sie ja meine Lösung elegant. Jedenfalls ist sie schön kurz. Für Datasets mit sehr vielen Variablen müßte man das natürlich testen.

Schöne Grüsse
Adolf Quast

/* Testdaten erzeugen */
data TEST ;
  Z =
1 ;
  F =
2 ;
  A =
3 ;
run ;

/* Liste der Variablen erzeugen */
data FELDLIST (keep=name);
  
set sashelp.vcolumn ;
  
where memname = "TEST" ;
run ;

/* Variablenliste transponieren und in Macrovariable schreiben
 * Gleichzeitig sortieren.
 */
proc sql noprint ;
  
select name into:Feldlist separated by ' '
  
from FELDLIST
  
order by name ;
quit ;
%put &Feldlist ;

Warum so kompliziert wenn es auch einfach geht?

Ich freue mich, daß sich mal jemand ohne Macro rangewagt hat, anbei meine Lösung:


data test;
  satz=
1;
  z22=
"yyy";
  abc=
1;
  cde=
2;
  a11=
"xxx";
run;
title 'Vor der Umsortierung der Variablenreihenfolge';
proc print; run;
proc contents data=test out=varlist(keep=name) short noprint; run;
proc sort data=Varlist; by name; run;

/* Die Länger der Variable geht auch dynamisch, aber bei wenigen Spalten geht es auch so. Die dynamische Variante würde einfach die echte Länge jedes Spaltenames bestimmen plus Anzahl der Spalten minus 1, daß in eine Macrovariable geschrieben und "length varlist $_vsize.;" */
data _null_;
  
length varlist $1000;
  
retain varlist '';
  
set Varlist end=fertig;
  varlist = trim(varlist) !!
' '!!compress(name);
  
if fertig then call symput('Varlist',trim(varlist) );
run;
%put VarList: &varlist.;
data test;
  
retain &varlist.;
  
set test;
run;
title 'Nach der Umsortierung der Variablenreihenfolge';
proc print; run;

Ich denke daß man ruhig die Werkzeuge von SAS verwenden darf, jede selbst erdachte Lösung in Ehren, aber ist es dann auch für Außenstehende immer verständlich? Ich denke da vor allem an die Wartung.

Die Macro-Lösung ist flexibeler bzw. einfacher einsetzbar

Hallo,
wenn man dieses Sortieren häufig einsetzen will, dann ist die Macro-Lösung einfach und elegant (also wartungsfreundlich) einsetzbar, z.B. so:

data test;
  
retain %varsrt(test);
  
set test;
run;
Die Macro-Definition kann "irgendwo" an einem zenmtralen Ort (einmal) stehen. Bei der "Warum so kompliziert wenn es auch einfach geht"-Lösung (SAS-Base-Lösung) muß der Code zur Erzeugung der Macro-Variable varlist jedesmal vor ihrer Verwendung ausgeführt werden, der Quell-Code wird dadurch wesentlich länger und wenn die Logik (zur Erzeugung der Macro-Variable varlist) sich irgendwann mal ändert, dann ....... hat man viel Arbeit das an allen Stellen zu ändern (Okay, man kann den SAS-Base-Code mit einem Makro zentralisieren, aber die SAS-Base-Lösung produziert mehr [unnötigen] LOG als die SAS-Macro-Lösung).
Das ist der Grund warum ich nach so einer Lösung suchte, eine Variante ohne Makros hatte ich auch schon in meiner Eingangs-Frage aufgeführt, siehe oben (HansKneilmann, 11 Juli 2006 - 12:29), dort aber nur als Link: Code der umständlichen Lösung.
Mein Wunsch war "einfaches verwenden", es mußte nicht einfach zu erstellen sein.
Den Macro einmal umständlich zu erstellen ist und x-mal einfach zu verwenden erscheint mir als die wartungsfreundlichste Lösung.
Gruß
Hans Kneilmann, Schäfer Shop GmbH (SSI)

Noch eine Lösung ...

Viele gute Vorschläge! Besonders der mit dem SASHELP.VCOLUMN Zugriff. Die Performance dabei ist aber stark von der Anzahl der gelinkten Librarys und den darin enthaltenen Dateien und Attributen abhängig. Bei mir derzeit etwa 160.000 Attribute (Variablen). Da kann der Zugriff schon mal 2 Minuten dauern.

Wirklich schnell geht's mit folgendem Konstrukt welches die Dictionary-Tables nutzt:

/* Testdaten erzeugen
   Anzahl der Attribute als Parameter im Aufruf des
   Test-Macros mitgeben */

%macro test(anz);
  data TEST ;
    
%do i=&anz %to 1 %by (-1);
      var&i=
'ABC';
    
%end;
  run;
%mend;
%test(
200);


/* Zugriff auf die Dictionary Table 'Columns' und sortiertes
   Übertragen in Macro-Variable  
   Das folgende Macro kann mit Dateiname (Bibliothek.Datei)
   aufgerufen werden und liefert als Ergebnis immer die Macro-
   variable Varlist mit den sortierten Inhalten */

%macro varlist(datei);
  
%global Varlist;
  proc sql noprint ;
    select name into:Varlist separated by
' '
    
from dictionary.columns
    where (memname =
"%upcase(%scan(&datei,2))" and
           libname =
"%upcase(%scan(&datei,1))")
    order by name ;
  quit ;
%mend;

%Varlist(work.test);

%put &Varlist ;

Sieht auf den ersten Blick lang aus, ist aber im Kern eigentlich nur der kleine Proc SQL. Das andere aussenrum ist schmückendes Beiwerk. Im Beispiel wird die Test-Datei mit 200 Attributen (Spalten) bestückt. Man kann aber gerne auch mal 1000 probieren (irgendwo zereisst dann aber mal den Arbeitsspeicher).

Gruß Jens Wegener (neu im Forum)