Is it possible to merge more than two datasets together in a SAS Data Step with different keys?

Viewed 2088

This is pretty trivial in (PROC) SQL, but looking at doing it with a data step, and I'm finding a serious lack of documentation on the subject.

Most multi-data set merges have the same keys being used in the BY statement.

2 Answers

It is, technically, possible to do this even using the merge statement, which will probably come to the surprise of most. Sometimes you even get what you expect in terms of data.

This works as expected:

proc means data=sashelp.class;
  class age;
  types age;
  var height;
  output out=mean_height_age mean= /autoname;
run;

proc means data=sashelp.class;
  class age sex;
  types age*sex;
  var weight;
  output out=mean_weight_sex mean= /autoname;
run;

proc sort data=sashelp.class out=class;
  by age sex;
run;


data class_means;
  merge class mean_height_age;
  by age;
  merge class mean_weight_sex;
  by age sex;
run;

These work because the order isn't incongruous (the second by is compatible with the first by).

However, if you're thinking more of a relational database kind of thing where you have totally separate merge keys, it's possible to trick SAS into doing something that appears to work, but it doesn't. Note that the final dataset seems to be sort of mixed up when it comes to ages - it's because the incoming rows on the second merge statement from class_index overwrite the first set of records, and are coming in a different order (per the index).

proc means data=sashelp.class;
  class age;
  types age;
  var height;
  output out=mean_height_age mean= /autoname;
run;

proc means data=sashelp.class;
  class sex;
  types sex;
  var weight;
  output out=mean_weight_sex mean= /autoname;
run;

data class_index(index=(sex) index=(age));
  set class;
run;


data class_means;
  merge class_index mean_height_age;
  by age;
  merge class_index mean_weight_sex;
  by sex;
run;

You can see this clearly by re-setting again the class_index dataset.

data class_means;
  merge class_index mean_height_age;
  by age;
  merge class_index mean_weight_sex;
  by sex;
  set class_index;
  by age;
run;

Back to correct by age, but wrong by sex.


If you're going to do that (two separate, unrelated keys), you have a bunch of options. The most commonly used is probably a user defined format. This uses the format lookup table to store the relationship, and then you just put (or input if you want a number, but you may have to input(put( if you must make a format and not an informat).

data for_fmt_age;
  set mean_height_age;
  start = age;
  label = height_mean;
  fmtname='HEIGHTAGEF';
  output;
run;


data for_fmt_sex;
  set mean_weight_sex;
  start = sex;
  label = weight_mean;
  fmtname='$WEIGHTSEXF';
  output;
run;
proc format cntlin=for_fmt_sex;
quit;

proc format cntlin=for_fmt_age;
quit;


data want;
  set sashelp.class;
  mean_height = put(age,heightagef.);
  mean_weight = put(sex,$weightsexf.);
run;

A second option is the keyed set; that's most similar to a merge, and requires only that an index be created on the merged-in dataset.

proc datasets lib=work;
  modify mean_height_age;
  index create age;
  run;
  modify mean_weight_sex;
  index create sex;
  run;
quit;

data class_nomerge;
  set class_index;
  set mean_height_age key=age;
  set mean_weight_sex key=sex;
run;

Finally, you could use hash tables, a bit more esoteric but very easy to use really. No sorting or anything else required, just the data step itself.

data want;
  set sashelp.class;
  if 0 then set mean_height_Age mean_weight_sex;
  if _n_=1 then do;
    declare hash h_age(dataset:'mean_height_age');
    h_age.defineKey('age');
    h_age.defineData('height_mean');
    h_age.defineDone();

    declare hash h_sex(dataset:'mean_weight_sex');
    h_sex.defineKey('sex');
    h_sex.defineData('weight_mean');
    h_sex.defineDone();

  end;
  rc_age = h_age.find();
  rc_sex = h_sex.find();
run;

A search for sas 9.4 "combining SAS data sets: methods" should get you deep into some documentation.

DATA Step MERGE / BY processing requires all incoming data sources to have the same column names and types.

The incoming data can be superficially tweaked using RENAME=() in order to make the column names align with the columns listed in the BY statement. If character data lengths don't match there is a possibility of truncation and false matches.

For the case when more complicated transformations (for example numeric id in remote db and character id in local data set) are necessary to perform a merge you can do so using SQL views for the preprocessing transform + ordering. This is very useful for the case of doing array based processing across a row that is onerous or difficult in SQL constructs.

An understanding of the Program Data Vector and how the DATA Step compiler builds it based on variables in order of introduction via length, attrib, SET and MERGE statements is essential for a complex MERGE.

Related