Macros for SAS Application Developers
https://github.com/sasjs/core
mp_filterstore.sas
Go to the documentation of this file.
1 /**
2  @file
3  @brief Checks & Stores an input filter table and returns the Filter Key
4  @details Used to generate a FILTER_RK from an input query dataset. This
5  process requires several permanent tables (names are configurable). The
6  benefit of storing query values at backend is to enable stored 'views' of
7  filtered tables at frontend (ie, when building [SAS-Powered Apps](
8  https://sasapps.io)). This macro is also used in [Data Controller for SAS](
9  https://datacontroller.io).
10 
11  A more recent feature of this macro is the ability to support filter queries
12  on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
13  parameter - where the "ds" in this case is the catalog name.
14 
15 
16  @param [in] libds= The target dataset to be filtered (lib should be assigned).
17  If filtering a format catalog, add the following suffix: `-FC`.
18  @param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
19  be validated. Has the following format:
20 |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
21 |---|---|---|---|---|---|
22 |AND|AND|1|SOME_BESTNUM|>|1|
23 |AND|AND|1|SOME_TIME|=|77333|
24  @param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
25  summary filter values. The definition is available by running
26  mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
27  values:
28 |FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
29 |---|---|---|---|
30 |`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
31 |`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
32 |`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
33  @param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
34  detailed (raw) filter values. The definition is available by running
35  mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
36  values:
37 |FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
38 |---|---|---|---|---|---|---|---|---|
39 |`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
40 |`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
41 |`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
42  @param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
43  manage concurrent access. The definition is available by running
44  mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
45  @param [in] maxkeytable= (0) Optional permanent reference table used for
46  retained key tracking. Described in mp_retainedkey.sas.
47  @param [in] mdebug= (1) set to 1 to enable DEBUG messages
48  @param [out] outresult= (work.result) The result table with the FILTER_RK
49  @param [out] outquery= (work.query) The original query, taken as extract
50  after table load
51 
52 
53  <h4> SAS Macros </h4>
54  @li mddl_sas_cntlout.sas
55  @li mf_getuniquename.sas
56  @li mf_getvalue.sas
57  @li mf_islibds.sas
58  @li mf_nobs.sas
59  @li mp_abort.sas
60  @li mp_filtercheck.sas
61  @li mp_hashdataset.sas
62  @li mp_retainedkey.sas
63 
64  <h4> Related Macros </h4>
65  @li mp_filtercheck.sas
66  @li mp_filtergenerate.sas
67  @li mp_filtervalidate.sas
68  @li mp_filterstore.test.sas
69 
70  @version 9.2
71  @author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
72 
73 **/
74 
75 %macro mp_filterstore(libds=,
76  queryds=work.filterquery,
77  filter_summary=PERM.FILTER_SUMMARY,
78  filter_detail=PERM.FILTER_DETAIL,
79  lock_table=PERM.LOCK_TABLE,
80  maxkeytable=PERM.MAXKEYTABLE,
81  outresult=work.result,
82  outquery=work.query,
83  mdebug=1
84 );
85 %put &sysmacroname entry vars:;
86 %put _local_;
87 
88 %local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
89 %let libds=%upcase(&libds);
90 %let orig_libds=&libds;
91 
92 %mp_abort(iftrue= (&syscc ne 0)
93  ,mac=mp_filterstore
94  ,msg=%str(syscc=&syscc on macro entry)
95 )
96 %mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)
97  ,mac=mp_filterstore
98  ,msg=%str(Invalid filter_summary value: &filter_summary)
99 )
100 %mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)
101  ,mac=mp_filterstore
102  ,msg=%str(Invalid filter_detail value: &filter_detail)
103 )
104 %mp_abort(iftrue= (%mf_islibds(&lock_table)=0)
105  ,mac=mp_filterstore
106  ,msg=%str(Invalid lock_table value: &lock_table)
107 )
108 
109 /**
110  * validate query
111  * use format catalog export, if a format
112  */
113 %if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
114  %let libds=%scan(&libds,1,-); /* chop off -FC extension */
115  %let ds0=%mf_getuniquename(prefix=fmtds_);
116  %let libds=&ds0;
117  /*
118  There is no need to export the entire format catalog here - the validations
119  are done against the data model, not the data values. So we can simply
120  hardcode the structure based on the cntlout dataset.
121  */
122  %mddl_sas_cntlout(libds=&ds0)
123 
124 %end;
125 %mp_filtercheck(&queryds,targetds=&libds,abort=YES)
126 
127 /* hash the result */
128 %let ds1=%mf_getuniquename(prefix=hashds);
129 %mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
130 %let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
131 %if &mdebug=1 %then %do;
132  data _null_;
133  putlog "filter_hash=&filter_hash";
134  set &ds1;
135  putlog (_all_)(=);
136  run;
137 %end;
138 
139 /* check if data already exists for this hash */
140 data &outresult;
141  set &filter_summary;
142  where filter_hash="&filter_hash";
143 run;
144 
145 %mp_abort(iftrue= (&syscc ne 0)
146  ,mac=mp_filterstore
147  ,msg=%str(syscc=&syscc after hash check)
148 )
149 %mp_abort(iftrue= ("&filter_hash "=" ")
150  ,mac=mp_filterstore
151  ,msg=%str(problem with filter_hash generation)
152 )
153 
154 %if %mf_nobs(&outresult)=0 %then %do;
155 
156  /* first update summary table */
157  %let ds3=%mf_getuniquename(prefix=filtersum);
158  data work.&ds3;
159  if 0 then set &filter_summary;
160  filter_table="&orig_libds";
161  filter_hash="&filter_hash";
162  PROCESSED_DTTM=%sysfunc(datetime());
163  output;
164  stop;
165  run;
166 
167  %mp_lockanytable(LOCK,
168  lib=%scan(&filter_summary,1,.)
169  ,ds=%scan(&filter_summary,2,.)
170  ,ref=MP_FILTERSTORE summary update - &filter_hash
171  ,ctl_ds=&lock_table
172  )
173 
174  %let ds4=%mf_getuniquename(prefix=filtersumappend);
175  %mp_retainedkey(
176  base_lib=%scan(&filter_summary,1,.)
177  ,base_dsn=%scan(&filter_summary,2,.)
178  ,append_lib=work
179  ,append_dsn=&ds3
180  ,retained_key=filter_rk
181  ,business_key=filter_hash
182  ,maxkeytable=&maxkeytable
183  ,locktable=&lock_table
184  ,outds=work.&ds4
185  )
186  proc append base=&filter_summary data=&ds4;
187  run;
188 
189  %mp_lockanytable(UNLOCK,
190  lib=%scan(&filter_summary,1,.)
191  ,ds=%scan(&filter_summary,2,.)
192  ,ref=MP_FILTERSTORE summary update - &filter_hash
193  ,ctl_ds=&lock_table
194  )
195 
196  %if &syscc ne 0 %then %do;
197  data _null_;
198  set &ds4;
199  putlog (_all_)(=);
200  run;
201  %goto err;
202  %end;
203 
204  data &outresult;
205  set &filter_summary;
206  where filter_hash="&filter_hash";
207  run;
208 
209  /* Next, update detail table */
210  %let ds2=%mf_getuniquename(prefix=filterdetail);
211  data &ds2;
212  if 0 then set &filter_detail;
213  set &queryds;
214  format filter_hash $hex32. filter_line 8.;
215  filter_hash="&filter_hash";
216  filter_line=_n_;
217  PROCESSED_DTTM=%sysfunc(datetime());
218  run;
219  %mp_lockanytable(LOCK,
220  lib=%scan(&filter_detail,1,.)
221  ,ds=%scan(&filter_detail,2,.)
222  ,ref=MP_FILTERSTORE update - &filter_hash
223  ,ctl_ds=&lock_table
224  )
225  proc append base=&filter_detail data=&ds2;
226  run;
227 
228  %mp_lockanytable(UNLOCK,
229  lib=%scan(&filter_detail,1,.)
230  ,ds=%scan(&filter_detail,2,.)
231  ,ref=MP_FILTERSTORE detail update &filter_hash
232  ,ctl_ds=&lock_table
233  )
234 
235  %if &syscc ne 0 %then %do;
236  data _null_;
237  set &ds2;
238  putlog (_all_)(=);
239  run;
240  %goto err;
241  %end;
242 
243 %end;
244 
245 proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
246  by filter_line;
247 run;
248 
249 %err:
250 %mp_abort(iftrue= (&syscc ne 0)
251  ,mac=mp_filterstore
252  ,msg=%str(syscc=&syscc on macro exit)
253 )
254 
255 %mend mp_filterstore;