I am trying to export a csv file from SAS and adding datetime() to the name of the file. for some reason nothing happens. The export works fine but I don't get the time stamp in the file name.
This is how i create the datetime variable:
data Dato_eksport;
dato_eksport=today();
dato_eksport_f=datetime();
format dato_eksport_f datetime19.;
run;
PROC SQL NOPRINT;
SELECT DISTINCT
dato_eksport_f
INTO :dato_eksport_f
FROM Dato_eksport_F;
I the use this variable in my export:
%LET filtype_csv = .csv;
%LET filnavn_csv = CAT(OUTPUT_DAGLIG_LCR,&dato_eksport_f);
%LET path_csv = \\path\path\path\path\path\path\path;
%LET kombineret_csv = "&path_csv&filnavn_csv&filtype_csv" dlm = ';';
%PUT &kombineret_csv;
%ds2csv (
data=OUTPUT_DAGLIG_LCR,
runmode=b,
csvfile=&kombineret_csv
);
What am I doing wrong the file gets updated but I get no errors and the file is missing the datetime string.
I hope you cna point me in the right direction.
First mistake is the PROC SQL step that is trying to create the macro variable dato_eksport_f is reading from a dataset you have not defined for us.
Second mistake is inserting text like CAT(...) into the macro variable filnavn_csv. To the macro processor everything is text, it only looks to operate on the text when it sees the macro triggers & or %.
To avoid adding leading/trailing spaces into the macro variable when using PROC SQL and the INTO clause make sure to add the TRIMMED keyword.
proc sql noprint;
select dato_eksport_f into :dato_eksport_f trimmed
from Dato_eksport
;
quit;
There is no need to try to use functions to concatenate text in macro code. To append macro variables together just use the syntax you used in this statement:
%LET kombineret_csv = "&path_csv.\&filnavn_csv.&filtype_csv" dlm=';';
If you do want to use a SAS function in macro code you need to call it with the macro function %SYSFUNC(). You could use that to skip the data and proc sql steps and just call the DATETIME() function directly in macro code. Note that the leading space generated by the datetime19. format will be remove by the %LET statement.
%let dato_eksport_f = %sysfunc(datetime(),datetime19.);
Now you can build your full filename:
%LET path_csv = \\path1\path2\path3;
%LET filnavn_csv = OUTPUT_DAGLIG_LCR_&dato_eksport_f;
%LET filtype_csv = .csv;
%LET kombineret_csv = "&path_csv.\&filnavn_csv.&filtype_csv" dlm=';';
%PUT &kombineret_csv;
You might want to use a different string to timestamp your filenames. Something that avoids colons and also will sort properly.
data _null_;
call symputx('dato_eksport_f'
,translate(put(datetime(),e8601dt.),'___','-T:'));
run;
Example:
1806 %put &=dato_eksport_f;
DATO_EKSPORT_F=2022_08_01_09_38_58
Related
How to write SAS dates to Microsoft SQL Server 2016 Date data type in database?
I got SAS data with a sas date DataEndDay and I want to write that into a database. The following bit is in use (buffer is just to speed up the testing-failing) :
libname valu oledb provider=sqloledb schema="dbo" INSERTBUFF=100
properties=("User ID"="&username." Password="&pw."
"data source" = &database.
"initial catalog"=&catalog.);
proc sql noprint;
insert into valu.Data_upload_from_me
( <some_columns...>,
<more-columns...>
,DataEndDay
)
select
<some_columns_source...>,
<more-columns_source...>
,DataEndDay
from work.SAS_data_to_publish
;quit;
Of course because SAS dates are numbers, direct writing is going to fail. What works is if I hard-code this as:
select
<some_columns_source...>,
<more-columns_source...>
,'2018-12-12'
from work.SAS_data_to_publish
;quit;
But If I convert the SAS date to string in SAS datasteps:
data SAS_data_to_publish ;
set SAS_data_to_publish ;
dataEndday0 = put(DataEndDay, yymmddd10.);
DataEndDay1 = quote(dataEndday0, "'") ;
run;
and try to write either of these, I get conversion error:
ERROR: ICommand::Execute failed. : Conversion failed when converting date and/or time from character string.
When I select the string it looks pretty ok:
proc sql; select DataEndDay1 from SAS_data_to_publish; quit;
'2018-12-12'
previously I've managed to write dateTimes with similar trick, which works:
proc format;
picture sjm
. = .
other='%Y-%0m-%0d %0H:%0M:%0S:000' (datatype=datetime)
;run;
data to_be_written;
set save.raw_data_to_be_written;
DataEndDay0 = put(dhms(DataEndDay,0,0,0), sjm. -L);
run;
Anyone ran into similar issues? How could I write the dates?
I could ask them to change the column to dateTime, maybe....
Thank you in advance.
Edit:
I managed to develop a work-around, which works but is ugly and -frankly- I don't like it. It so happens that my date is same for all rows, so I can assing it to macro variable and then use it in database writing.
data _NULL_;
set SAS_data_to_publish;
call symput('foobar', quote( put (DataEndDay , yymmddd10. -L), "'") ) ;
run;
....
select
<some_columns_source...>,
<more-columns_source...>
,&foobar.
from work.SAS_data_to_publish
;quit;
Of course this would fail immediately should DataEndDay vary, but maybe demonstrates that something is off in Proc SQLs select clause....
Edit Edit Pasted the question to SAS forums
I finally managed to crack the issue. The issue was for the missing values. As I am passing the values as strings into the database the parser interpreted missing values as real dots instead of empty strings. The following works:
data upload;
set upload;
CreatedReportdate2 = PUT(CreatedReportdate , yymmddn8.);
run;
libname uplad_db odbc noprompt =
"DRIVER=SQL Server; server=&server.; Uid=&user.;Pwd=&pw.; DATABASE=&db.;"
INSERTBUFF=32767;
proc sql;
insert into uplad_db.upload_table
(.... )
select
case when CreatedReportdate2 ='.' then '' else CreatedReportdate2 end,
...
from upload;
quit;
SAS does not really properly support the SQL server DATE data type. I imagine this is due to the fact that it's newer, but for whatever reason you have to pass the data as strings.
For missing values, it's important to have a blank string, not a . character. The easiest workaround here is to set:
options missing=' ';
That will allow you to insert data properly. You can then return it to . if you wish. In a production application that might be used by others, I'd consider storing aside the option value temporarily then resetting to that, in order to do no harm.
Normally I just use PROC APPEND to insert observations into a remote database.
proc append base=valu.Data_upload_from_me force
data=work.SAS_data_to_publish
;
run;
Make sure your date variable in your SAS dataset use the same data type as the corresponding variable names in your target database table. So if your MS SQL database uses TIMESTAMP fields for date values then make sure your SAS dataset uses DATETIME values.
If you want to use constants then make sure to use SAS syntax in your SAS code and MS SQL syntax in any pass through code.
data test;
date = '01JAN2017'd ;
datetime = '01JAN2017:00:00'dt ;
run;
proc sql ;
connect to oledb .... ;
execute ( ... date = '2017-01-01' .... datetime='2017-01-01 00:00' ...)
by oledb;
quit;
Is it possible to make a macro of this form work?
%macro tableMath(input1,input2);
%local result;
proc sql; ---some code here using inputs--- quit;
proc sql; ---more code here--- quit;
proc sql;
select something into: result
quit;
&result
%mend;
I want to run some fairly complicated logic on each observation of a dataset, and in any other language I've used before the way to do this would be to encapsulate it in a function that returns a result each time it's called--I'm not sure how to do this logic in SAS however.
EDIT: input1 and input2 would be columns of a dataset and result would be used to create a new column in some other macro in another part of the program. I don't need a specific code solution I just literally don't get how you're supposed to do traditional function logic where you need a return value in SAS...
As Richard wrote, function-style macros emit SAS code. The general rule of developing function-style macros is that they contain only macro language statements. Any SAS code they contain will be emitted. Historically, this made it difficult/annoying to write a function-style macro that would process data like you would with a DATA step. Luckily, SAS has added a function, DOSUBL, which makes it easier to write function-style macros that execute SAS code in a "side session" and emit a result. See Rick Langston's paper.
Here is an example of a function-style macro which used DOSUBL to count the number of records in a table, and emits the count. (This is a very inefficient way to get a record count, just an example of doing something in SQL).
%macro SQLcount(table);
%local rc emit;
%let rc=%sysfunc(dosubl(%nrstr(
proc sql noprint;
select count(*) into :emit trimmed
from &table
quit;
)));
&emit
%mend ;
It can be used like:
proc sql ;
select name
,%SQLcount(sashelp.shoes) as ShoeCount /*emits 395*/
from sashelp.class
;
quit ;
When the above step runs, it will return 19 rows of names from sashelp.class, and the value of ShoeCount will be 395 on every row. Note that the macro SQLcount only executed once. While the PROC SQL step is being compiled/interpreted the call to SQLcount is seen and the macro is executed and emits 395. The step becomes:
proc sql ;
select name
,395 as ShoeCount /*emits 395*/
from sashelp.class
;
quit ;
DOSUBL uses a "side session" to execute code, which allows you to execute a PROC SQL step in the side session while the main session is interpreting a PROC SQL step.
I can't tell from your question if that sort of use case is what you want. It's possible you want a function-style macro where you could pass values to it from a table, and have the macro execute on each value and return something. Suppose you had a table which was a list of table names, and wanted to use SQL to get the count of records in each table:
data mytables ;
input table $20. ;
cards ;
sashelp.shoes
sashelp.class
sashelp.prdsale
;
quit ;
You can do that by using the resolve() function to build macro calls from data, delaying the execution of the macro until the SELECT statement executes:
proc sql ;
select table
,resolve('%SQLcount('||table||')') as count
from mytables
;
quit ;
With that, SQLcount will be called three times, and will return the number of records in each dataset.
table count
---------------------------
sashelp.shoes 395
sashelp.class 19
sashelp.prdsale 1440
The macro call is not seen when the PROC SQL step is interpreted, because it is hidden by the single quotes. The resolve function then calls the macro when the SELECT statement executes, passing the value of table as a parameter value, and the macro emits the record count. This is similar to a CALL EXECUTE approach for using data to drive macro calls.
You state you want to:
run some fairly complicated logic on each observation of a dataset
To do that you should use the SAS language instead of the macro processor or PROC SQL. You can use a data step. Or for even more complicated logic you should look at PROC DS2.
Sounds like you may want to create an FCMP function using proc fcmp. This is basically a way to create your own SAS functions that can be used within proc sql and data steps. For example:
/******************************************************************************
** PROGRAM: COMMON.FCMP_DIV.SAS
**
** DESCRIPTION: PERFORMS A MATHEMATICAL DIVISION BUT WILL RETURN NULL IF THE
** NUMERATOR OR DENOMINATOR IS MISSING (OR IF THE DIVISOR IS 0).
**
******************************************************************************/
proc fcmp outlib=common.funcs.funcs;
function div(numerator, denominator);
if numerator eq . or denominator in (0,.) then do;
return(.);
end;
else do;
return(numerator / denominator);
end;
endsub;
run;
Example Usage (example is data step but works equally well within SQL):
data x;
x1 = div(1,0);
x2 = div(1,.);
x3 = div(1,1);
x4 = div(0,0);
x5 = div(0,.);
x6 = div(0,1);
x7 = div(.,0);
x8 = div(.,.);
x9 = div(.,1);
put _all_;
run;
Macro functions do not return values. A macro function can 'emit' source code that
that are one or more steps,
that is a snippet that code be incorporated in a statement,
that is one or more statements that are part of a step,
etc
For your case of wanting to 'do' things in SQL, you could write SQL views that are then
opened with %sysfunc(open()) and
processed with
%sysfunc(set()) and
%sysfunc(getvarn()) and
%sysfunc(getvarc()).
Not all SQL functionality can utilized by this technique -- the select something into :result, would have to be a view with the select something and the macro would getvarc to read the result.
Access done in the open/set/get manner does not cause a step boundary to occur, so the macro processing can proceed with it's logic and eventually emit source code for snippet level consumption. (The consumer is the SAS executor that processes macro code, and implicitly compiles and runs SAS steps)
I really hope you can assist.
When I run this code:
libname odbc ### user='abc' password='****' dsn='bleh' schema='dbo';
%let date=%sysfunc(intnx(day,%sysfunc(today()),-1,b),yymmddd10.);
%put &date.;
run;
It works!
But if I run it with the call execute I get this error – it reads from sql – yet the date in sql is varchar:
data _null_;
set odbc.SQLTableName;
if ((date= &date.) and (dateComplete ne .))then call execute("%include 'path';");
run;
dateComplete=Jun 10 2015 1:54PM _ERROR_=1 _N_=1
I am looking for a way to convert my date.
So it reads today()-1 (Technically yesterday’s date)
YOUR HELP WILL BE GREATLY APPRECIATED!!!
Shouldn't you be using single quotes instead of double quotes? As you don't want your macro to be executed before the data step ends?
call execute("%include 'path';");
Try:
call execute('%include "path";');
I'd like to use the date as a variable name. I understand I'll need to append a character to the front and also that it's not a good way to store data (it's purely for report aesthetics). I've tried %eval() but can't solve it.
%let var_date = '_'||today();
data date;
%eval(&var_date) = .;
run;
I'd like the variable name to be _02JUN2011. Thanks for any help.
I don't think you want to take this approach. You should use labels in PROC REPORT (or any other reporting PROC), or pivot your data dynamically instead. Perhaps if you describe the data you've got and the output you want, people here will be able to help find an appropriate solution.
For your edification though, the following code does what you're trying to do in your post:
%let var_date=_%sysfunc(today(),date9.);
data test;
&var_date=.;
run;
This does what I'm looking for. Is this maintainable?
data date1;
date = put(today(),date9.);
text = '';
do i = 1 to 5;
output;
end;
run;
proc transpose data = date1 out = date2;
by i;
id date;
var text;
run;
You really want to use a hash - this is not maintainable code..
Are there any statements\functions capable of get the name of variables?
Preferrably putting them into a column of another data set, a text field or a macro variable.
E.g.
- Data set 1
Name age sex
Jk 14 F
FH 34 M
Expected data set
Var_name_of_dataset1
Name
age
sex
PS: I know a statement: select into, which does sth relevantly
It can read the value of a column into a field with customized separetors, and therefore wish there are similar ways of reading column names into a field or a column.
Thanks
PROC CONTENTS would be the quickest way to get that information in a dataset. Column names can be found in the column NAME.
proc contents data=sashelp.class out=contents noprint;
run;
You can also use a datastep and array functions, e.g.
data colnames ;
set sashelp.class (obs=1) ;
array n{*} _NUMERIC_ ;
array c{*} _CHARACTER_ ;
do i = 1 to dim(n) ;
vname = vname(n{i}) ;
output ;
end ;
do i = 1 to dim(c) ;
vname = vname(c{i}) ;
output ;
end ;
run ;
%macro getvars(dsn);
%global vlist;
proc sql;
select name into :vlist separated by ' '
from dictionary.columns
where memname=upcase("&dsn");
quit;
%mend;
This creates a macro variable called &vlist that will contain the names of all the variables in your dataset, separated by a space. If you want commas between the variable names, all you have to do is change the 'separated by' value from ' ' to ', '. The use of the upcase function in the where statement avoids problems with someone passing the dataset name in the wrong case. The global statement is needed since the macro variable created will not necessarily be available outside the macro without defining it as global
Slightly changed from SAS help and documentation.
%macro names(dsid);
%let dsid=%sysfunc(open(&dsid, i));
%let num=%sysfunc(attrn(&dsid,nvars));
%let varlist=;
%do i=1 %to &num ;
%let varlist=&varlist %sysfunc(varname(&dsid, &i));
%end;
%let rc = %sysfunc(close(&dsid)); /*edit by Moody_Mudskipper: omitting this line will lock the dataset */
%put varlist=&varlist;
%mend names;
%names(sasuser.class) ;
Then we preserve case and the order off data, even if numeric and character is mixed.
I'm not sure Rawfocus assertion that reading dictionary tables queries all libraries is true, had the example used sashelp.vcolumn instead then it would be true, that approach is very slow and does access all the libraries allocated. (You can prove this with the SAS RTRACE system option.)
I am of the opinion that a sql query to dictionary.columns is the fastest of the methods outlined here. Obviously the macrotised code would work without the macro but the point of the macro here is I think as a utility; put the code into your favourite macro library and you never need to think about it again.