sql datetime in sas date format - sql

proc sql noprint;
%let today=%sysfunc(today(),yymmdd10.);
%put &today.;
select
COALESCE(sum(abc.step_sum),0)
into :SumLoans_12m
from
RTDM_ABT.ABT_CONTRACT abc
where abc.CLIENT_ID = "&T_CLIENT_ID"
and abc.CONTRACT_BEGINDATE - &today. <= 360
and abc.DML_FLAG NE 1;
quit;
I understand it is necessary to convert CONTRACT_BEGINDATE (2021-01-14 00:00: 00.000) to format SAS, I think it would help , how do you do that? Thanks for your help!
ERROR:
SYMBOLGEN: Macro variable TODAY resolves to 2021-05-18
2021-05-18
SYMBOLGEN: Macro variable T_CLIENT_ID resolves to 00000031-B081-4E97-9437-F20CF874F857
MPRINT(MEQUIBEHSCOREUPRE): select COALESCE(sum(abc.step_sum),0) into :SumLoans_12m from RTDM_ABT.ABT_CONTRACT abc where abc.CLIENT_ID = "00000031-B081-4E97-9437-F20CF874F857" and
abc.CONTRACT_BEGINDATE - today() <= 360 and abc.DML_FLAG NE 1;
175369 1621341661 no_name 0 SQL (503
SQLSRV_39799: Prepared: on connection 0 175370 1621341661 no_name 0 SQL (503
SELECT * FROM rtdm_abt . ABT_CONTRACT 175371 1621341661 no_name 0 SQL (503
175372 1621341661 no_name 0 SQL (503
SAS_SQL: Unable to convert the query to a DBMS specific SQL statement due to an error. 175373 1621341661 no_name 0 SQL (503
ACCESS ENGINE: SQL statement was not passed to the DBMS, SAS will do the processing. 175374 1621341661 no_name 0 SQL (503

Try comparing the datetime variable to a datetime value. First let's rewrite your expression to make it easier to code. The comparison abc.CONTRACT_BEGINDATE - <TODAY> <= 360 is the same as abc.CONTRACT_BEGINDATE <= 360 + <TODAY>. So let's make a macro variable that contains that datetime value.
%let now_plus_360_days = %sysfunc(intnx(dtday,%sysfunc(datetime()),360)) ;
And then use that macro variable in the expression
and abc.CONTRACT_BEGINDATE <= &now_plus_360_days

proc sql noprint;
select sum(step_sum)
into :SumLoans_12m
from
RTDM_ABT.ABT_CONTRACT abc
where abc.client_id = "&T_CLIENT_ID"
and abc.DML_FLAG <> 1
and datepart(abc.CONTRACT_BEGINDATE) between today() - 365 and today()
;
quit;
It worked for me, in double it's nice that I came up with it myself))

SAS date values are integers and are the number of days from epoch 01-JAN-1960.
TODAY() returns a SAS data value, an integer nnnnn.
You are formatting that value as yymdd10. which produces a string with construct yyyy-mm-dd. (The string is more understandable to humans than the data value nnnnn.) and assigning that string to macro symbol today. When the symbol is resolved the string is inserted into your step as source code.
So,
abc.CONTRACT_BEGINDATE - &today. <= 360
becomes
abc.CONTRACT_BEGINDATE - 2021-05-18 <= 360
You want to resolve the macro symbol in the context of a SAS date literal which is construct "dd-mon-yyyy"D
Try
%let today_literal = "%sysfunc(today(),date11.)"D;
%put &=today_literal;
and
abc.CONTRACT_BEGINDATE - &today_literal <= 360
However, a lot depends on the options of the libname statement for RDTM_ABT that you do not show. You might be required to use pass-through SQL statement in order to get the result you want
connect using RDTM_ABT;
select * from connection to RDTM_ABT
(
... my query in remote database syntax ...
)
You could use the remote system functions for today, or construct the query using the appropriate source code for the remote system date literal or string to date conversion functions.

Related

SAS YYMMDD10. works but YYMMDDn10 doesn't

This is my data (sorry for no script, it's just proc create table from mssql):
testdb.testtable
id - numeric
date_from - numeric (datetime from mssql)
date_to - numeric (datetime from mssql)
base_id - numeric
base_id2 - string (length 64)
What I tried to do was:
proc sql;
update testdb.testtable tt
set base_id2 = CATX('_',
('data from other table'),
put(datepart(date_from),yymmddn10.),
put(datepart(date_to),yymmddn10.),
put(base_id,z4.)
)
where (....)
;
quit;
And I get this error:
The width value for YYMMDDN is out of bounds. It should be between 2 and 8.
The width value for YYMMDDN is out of bounds. It should be between 2 and 8.
What I really don't understand is that when I use format with separators, YYMMDD10., it works.
When i run:
proc sql;
select datepart(date_from) format=yymmddn10. from testdb.testtable;
quit;
It returns 20191227 - It's great. When I run
proc sql;
select put(datepart(date_from),yymmddn10.) from testdb.testtable;
quit;
It fails with the same error.
What do I miss?
There seems to be a bug in PROC SQL that allows you to attach a format that cannot work (the maximum width needed for a date without separators is 8 bytes).
It is also interesting that PROC PRINT (and the simple SELECT query in PROC SQL, like in your example) do not mind that the format width is invalid.
542 data test1;
543 now=date();
544 run;
NOTE: The data set WORK.TEST1 has 1 observations and 1 variables.
545
546 data test2 ;
547 set test1;
548 format now yymmddn10.;
----------
29
ERROR 29-185: Width specified for format YYMMDDN is invalid.
549 run;
NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.TEST2 may be incomplete. When this step was stopped there were 0 observations
and 1 variables.
WARNING: Data set WORK.TEST2 was not replaced because this step was stopped.
550
551 proc sql;
552 create table test2 as select now format=yymmddn10. from test1;
NOTE: Table WORK.TEST2 created, with 1 rows and 1 columns.
553 select * from test2;
554 quit;
555
556 proc print data=test2;
557 run;
NOTE: There were 1 observations read from the data set WORK.TEST2.
558
559 data test3;
560 set test2;
561 run;
ERROR: Width specified for format YYMMDDN is invalid.
NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.TEST3 may be incomplete. When this step was stopped there were 0 observations
and 1 variables.
WARNING: Data set WORK.TEST3 was not replaced because this step was stopped.
Also interesting is that if you use that format specification in PROC FREQ
proc freq data=test2; tables now; run;
it adds a space and a 'F7'x character in front of the data string.
The FREQ Procedure
Cumulative Cumulative
now Frequency Percent Frequency Percent
---------------------------------------------------------------
÷20200218 1 100.00 1 100.00
The number in format is the given width. Format YYMMDDn has 8 chars, so I should have used YYMMDDn8. And it worked.
I was having a long struggle with it and I still don't understand why did it work in format= and not in put().

SAS data step vs. proc sql dates

I hope someone can help me answer this query: I have two programs, one in proc sql and one in data step. The proc sql works, the data step doesn't. I can't see why?
%let _run_date = '30-jun-2017';
proc sql;
connect to oracle (path='EDRPRD' authdomain='EDRProduction'
buffsize=32767);
create table customer_sets as
select * from connection to oracle (
select *
from customer_set
where start_date <= &_run_date.
and nvl(end_date, &_run_date.) >= &_run_date.
and substr(sets_num,1,2) = 'R9');
quit;
This works fine. However, this doesn't:
libname ora oracle path='EDRPRD' authdomain='EDRProduction' schema='CST';
data customer_sets;
set ora.customer_set;
where start_date le &_run_date. and
coalesce(end_date, &_run_date.) ge &_run_date. and
substr(sets_num,1,2) = "R9";
run;
Can anyone tell me why?
Thanks!
It would have helped to see the error log but, for starters, your date macro variable, as it is used in your data step is interpreted by SAS as a string literal, not a date. In SAS, date literals are enclosed in quotes (single or double) and followed by a d.
You can modify your data step as follows and see if that's any better:
%let _run_date = '30-jun-2017';
data customer_sets;
set ora.customer_set;
where start_date le &_run_date.d and
coalesce(end_date, &_run_date.d) ge &_run_date.d and
substr(sets_num,1,2) = "R9";
run;
If that's not the issue, please post the log containing the error.
EDIT
Here is the above code with a small test data created beforehand:
libname ora (work);
data ora.customer_set;
infile datalines dlm='09'x;
input ID start_date :anydtdte. end_date :anydtdte. sets_num $;
format start_date end_date date.;
datalines;
1 30-may-2017 . R9xxx
2 30-may-2017 31-may-2017 R9xxx
;
run;
%let _run_date = '30-jun-2017';
data customer_sets;
set ora.customer_set;
where start_date le &_run_date.d and
coalesce(end_date, &_run_date.d) ge &_run_date.d and
substr(sets_num,1,2) = "R9";
run;
You can copy paste and run this as-is and you will see that it works fine.

Proc Sql doesn’t execute the 'order by' line when creating a new table in Teradata

I'm trying to use SAS (via SAS EG 4.3) to create a new table in Teradata from an existing sas dataset. The existing sas data set is sorted as needed.
The code will run ok with no errors, and a new table is created in Teradata but it does not execute the order by line in the code . If I set the trace option and look in the sas log the 'orderby ..' line doesn't show in the trace.
The TD table is not ordered as needed , any ideas ?
Regards
sas code
%include "$HOME/tdpp_5200.sas";
options SASTRACE=',,,d' SASTRACELOC=SASLOG MLOGIC MPRINT;
%let Teradata_db = U_DOATDB;
%let Teradata_tb = TBL_AS2_AUDIT_AGG_18MTH;
%let primary_key = 'primary index(CAT)';
libname tdata &rdbms &dbc_info database=&Teradata_db.;
libname datalib "/wload/ar3p/gpfs/teamproj/intr/Projects/AS2_CONTROL_RPT/";
proc sql;
create table tdata.&Teradata_tb. (FASTLOAD=yes dbcreate_table_opts= primary_key) as
select * from datalib.tbl_AS2_audit_agg_18mth
order by AS_YEAR, AS2_MONTH, EVENT_TYPE, RESULT_TYPE,REASON_TYPE, OPERATOR_TYPE;
Quit;
libname tdata clear;
libname datalib clear;
SAS Log
0 1475134126 trprep 0 SQL (2)
TERADATA_0: Prepared: on connection 1 1 1475134126 trprep 0 SQL (2)
SELECT * FROM U_DOATDB."TBL_AS2_AUDIT_AGG_18MTH" 2 1475134126 trprep 0 SQL (2)
3 1475134126 trprep 0 SQL (2)
TERADATA: trforc: COMMIT WORK 4 1475134126 trforc 0 SQL (2)
NOTE: SAS variable labels, formats, and lengths are not written to DBMS tables.
5 1475134132 trexec 0 SQL (2)
TERADATA_1: Executed: on connection 2 6 1475134132 trexec 0 SQL (2)
CREATE MULTISET TABLE U_DOATDB."TBL_AS2_AUDIT_AGG_18MTH" ("CAT" CHAR (200),"AS_Year" DECIMAL(11),"As2_Month"
DECIMAL(11),"EVENT_TYPE" INTEGER,"RESULT_TYPE" CHAR (1),"REASON_TYPE" INTEGER,"OPERATOR_TYPE" CHAR (8),"VOL" FLOAT) primary
index(CAT);COMMIT WORK 7 1475134132 trexec 0 SQL (2)
8 1475134132 trexec 0 SQL (2)
Teradata does not support ordering of data in tables, so SAS removed the ORDER BY clause when passing the code to Teradata. Otherwise the code would have failed.
When reading the data back into SAS you can use any variables you want on a BY statement and SAS will tell Teradata to order the data on the way out of the table.
proc print data=datalib.tbl_AS2_audit_agg_18mth;
by AS_YEAR AS2_MONTH EVENT_TYPE RESULT_TYPE REASON_TYPE OPERATOR_TYPE;
run;

SAS/SQL - Create SELECT Statement Using Custom Function

UPDATE
Given this new approach using INTNX I think I can just use a loop to simplify things even more. What if I made an array:
data;
array period [4] $ var1-var4 ('day' 'week' 'month' 'year');
run;
And then tried to make a loop for each element:
%MACRO sqlloop;
proc sql;
%DO k = 1 %TO dim(period); /* in case i decide to drop something from array later */
%LET bucket = &period(k)
CREATE TABLE output.t_&bucket AS (
SELECT INTX( "&bucket.", date_field, O, 'E') AS test FROM table);
%END
quit;
%MEND
%sqlloop
This doesn't quite work, but it captures the idea I want. It could just run the query for each of those values in INTX. Does that make sense?
I have a couple of prior questions that I'm merging into one. I got some really helpful advice on the others and hopefully this can tie it together.
I have the following function that creates a dynamic string to populate a SELECT statement in a SAS proc sql; code block:
proc fcmp outlib = output.funcs.test;
function sqlSelectByDateRange(interval $, date_field $) $;
day = date_field||" AS day, ";
week = "WEEK("||date_field||") AS week, ";
month = "MONTH("||date_field||") AS month, ";
year = "YEAR("||date_field||") AS year, ";
IF interval = "week" THEN
do;
day = '';
end;
IF interval = "month" THEN
do;
day = '';
week = '';
end;
IF interval = "year" THEN
do;
day = '';
week = '';
month = '';
end;
where_string = day||week||month||year;
return(where_string);
endsub;
quit;
I've verified that this creates the kind of string I want:
data _null_;
q = sqlSelectByDateRange('month', 'myDateColumn');
put q =;
run;
This yields:
q=MONTH(myDateColumn) AS month, YEAR(myDateColumn) AS year,
This is exactly what I want the SQL string to be. From prior questions, I believe I need to call this function in a MACRO. Then I want something like this:
%MACRO sqlSelectByDateRange(interval, date_field);
/* Code I can't figure out */
%MEND
PROC SQL;
CREATE TABLE output.t AS (
SELECT
%sqlSelectByDateRange('month', 'myDateColumn')
FROM
output.myTable
);
QUIT;
I am having trouble understanding how to make the code call this macro and interpret as part of the SQL SELECT string. I've tried some of the previous examples in other answers but I just can't make it work. I'm hoping this more specific question can help me fill in this missing step so I can learn how to do it in the future.
Two things:
First, you should be able to use %SYSFUNC to call your custom function.
%MACRO sqlSelectByDateRange(interval, date_field);
%SYSFUNC( sqlSelectByDateRange(&interval., &date_field.) )
%MEND;
Note that you should not use quotation marks when calling a function via SYSFUNC. Also, you cannot use SYSFUNC with FCMP functions until SAS 9.2. If you are using an earlier version, this will not work.
Second, you have a trailing comma in your select clause. You may need a dummy column as in the following:
PROC SQL;
CREATE TABLE output.t AS (
SELECT
%sqlSelectByDateRange('month', 'myDateColumn')
0 AS dummy
FROM
output.myTable
);
QUIT;
(Notice that there is no comma before dummy, as the comma is already embedded in your macro.)
UPDATE
I read your comment on another answer:
I also need to be able to do it for different date ranges and on a very ad-hoc basis, so it's something where I want to say "by month from june to december" or "weekly for two years" etc when someone makes a request.
I think I can recommend an easier way to accopmlish what you are doing. First, I'll create a very simple dataset with dates and values. The dates are spread throughout different days, weeks, months and years:
DATA Work.Accounts;
Format Opened yymmdd10.
Value dollar14.2
;
INPUT Opened yymmdd10.
Value dollar14.2
;
DATALINES;
2012-12-31 $90,000.00
2013-01-01 $100,000.00
2013-01-02 $200,000.00
2013-01-03 $150,000.00
2013-01-15 $250,000.00
2013-02-10 $120,000.00
2013-02-14 $230,000.00
2013-03-01 $900,000.00
RUN;
You can now use the INTNX function to create a third column to round the "Opened" column to some time period, such as a 'WEEK', 'MONTH', or 'YEAR' (see this complete list):
%LET Period = YEAR;
PROC SQL NOPRINT;
CREATE TABLE Work.PeriodSummary AS
SELECT INTNX( "&Period.", Opened, 0, 'E' ) AS Period_End FORMAT=yymmdd10.
, SUM( Value ) AS TotalValue FORMAT=dollar14.
FROM Work.Accounts
GROUP BY Period_End
;
QUIT;
Output for WEEK:
Period_End TotalValue
2013-01-05 $540,000
2013-01-19 $250,000
2013-02-16 $350,000
2013-03-02 $900,000
Output for MONTH:
Period_End TotalValue
2012-12-31 $90,000
2013-01-31 $700,000
2013-02-28 $350,000
2013-03-31 $900,000
Output for YEAR:
Period_End TotalValue
2012-12-31 $90,000
2013-12-31 $1,950,000
As Cyborg37 says, you probably should get rid of that trailing comma in your function. But note you do not really need to create a macro to do this, just use the %SYSFUNC function directly:
proc sql;
create table output.t as
select %sysfunc( sqlSelectByDateRange(month, myDateColumn) )
* /* to avoid the trailing comma */
from output.myTable;
quit;
Also, although this is a clever use of user-defined functions, it's not very clear why you want to do this. There are probably better solutions available that will not cause as much potential confusion in your code. User-defined functions, like user-written macros, can make life easier but they can also create an administrative nightmare.
I could make all sorts of guesses as to why you're getting errors, but fundamentally, don't do it this way. You can do exactly what you're trying to do in a data step that is much easier to troubleshoot and much easier to implement than a FCMP function which is really just trying to be a data step anyway.
Steps:
1. Create a dataset that has your possible date pulls. If you're using this a lot, you can put this in a permanent library that is defined in your SAS AUTOEXEC.
2. Create a macro that pulls the needed date strings from it.
3. If you want, use PROC FCMP to make this a function-style macro, using RUN_MACRO.
4. If you do that, use %SYSFUNC to call it.
Here is something that does this:
1:
data pull_list;
infile datalines dlm='|';
length query $50. type $8.;
input type $ typenum query $;
datalines;
day|1|&date_field. as day
week|2|week(&date_field.) as week
month|3|month(&date_field.) as month
year|4|year(&date_field.) as year
;;;;
run;
2:
%macro pull_list(type=,date_field=);
%let date_field = datevar;
%let type = week;
proc sql noprint;
select query into :sellist separated by ','
from pull_list
where typenum >= (select typenum from pull_list where type="&type.");
quit;
%mend pull_list;
3:
proc fcmp outlib = work.functions.funcs;
function pull_list(type $,date_field $) $;
rc = run_macro('pull_list', type,date_field);
if rc eq 0 then return("&sellist.");
else return(' ');
endsub;
run;
4:
data test;
input datevar 5.;
datalines;
18963
19632
18131
19105
;;;;
run;
option cmplib = (work.functions);
proc sql;
select %sysfunc(pull_list(week,datevar)) from test;
quit;
One of the big advantages of this is that you can add additional types without having to worry about the function's code - just add a row to pull_list and it works. If you want to set it up to do that, I recommend using something other than 1,2,3,4 for typenum - use 10,20,30,40 or something so you have gaps (say, if "twoweek" is added, it would be between 2 and 3, and 25 is easier than 2.5 for people to think about). Create that pull_list dataset, put it on a network drive where all of your users can use it (if anybody beyond you uses it, or a personal one if not), and go from there.

Converting CYYMMDD to SAS Date in DB2 via SAS

I'm looking to convert a date that is in the format of CYYMMDD (where C is either 0 for 20th century or 1 for 21st century) to a standard SAS date. This code will be placed inside of a SAS query using 'proc sql' so that it can compare a SAS date against a date stored in DB2.
Example: Input data=1130101, Output='1Jan2013'd
Examples I've tried are:
(substr(t1.'EffectDate'n,4,2)|| '/' || substr(t1.'EffectDate'n,6,2) || '/' || cast(substr(t1.'EffectDate'n,1,3) AS INTEGER) + 1900)
That fails to the cast() function (appears it doesn't exist?)
Also tried:
convert(varchar(10), convert(datetime, right(t1.'EffectDate'n, 6), 12), 101)
But varchar(10) doesn't exist.
My query looks like this:
proc sql;
create table CLAIMS as select
t1.CID,
t1.MID,
t1.DOS
OTHER_TABLE.ChangeDate AS EffectDate
FROM
SOURCE.REJECTED t1
INNER JOIN
EGTASK.OTHER_TABLE
ON
t1.DOS >= *Converted_Date*
[... goes on a couple more lines...]
Where *Converted_Date* is what I need.
(However, I should clarify that this particular query/join doesn't necessarily need to be SQL)
To convert your variable from it's current coded format into a proper SAS date variable, you will need to turn it into a character string and then read the result using the INPUT function. For example:
data _null_;
do EffectDate = 1130101,0130101;
cEffectDate = put(EffectDate,z7.);
if substr(cEffectDate,1,1) = '0'
then SASEffectDate = input('19' || substr(cEffectDate,2),yymmdd8.);
else SASEffectDate = input('20' || substr(cEffectDate,2),yymmdd8.);
put EffectDate=
/ SASEffectDate=
/ ;
end;
format SASEffectDate yymmdd10.;
run;
This is just an illustration and a bit long-winded; it creates a new SAS variable named SASEffectDate to preserve the original variable. Once you have it as a SAS variable, you don't need to do anything else; the SAS Access product will know how to make the references to the external database.
Here is an example of doing something similar using PROC SQL:
data have; /* Just a dummy data set for illustration */
do EffectDate = 1130101,0130101;
i+1;
output;
end;
run;
proc sql;
create table want as
select t2.*
, case when t2.EffectDate < 999999 /* starts with 0 */
then input('19' || substr(put(EffectDate,z7.),2),yymmdd8.)
else input('20' || substr(put(EffectDate,z7.),2),yymmdd8.)
end as SASEffectDate format=yymmdd10.
from have t2
;
quit;