I've encountered this error:
Execution error in stored procedure: SQL execution internal error: Processing aborted due to error at Snowflake.execute
when running this script:
CREATE OR REPLACE PROCEDURE DATES_TABLE (INITIALDATE VARCHAR, FINALDATE VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
var DATESDIFF = (Date.parse(formatDate(FINALDATE)) - Date.parse(formatDate(INITIALDATE)))/ (1000 * 3600 * 24);
snowflake.execute(
{
sqlText: ` CREATE OR REPLACE TEMPORARY TABLE TEMP_DATE_RANGE AS SELECT DATE FROM (
SELECT
CAST(DATEADD (DAY, DatesDiff.n, :1) AS DATE) AS DATE
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY 1) - 1
FROM
TABLE (generator (rowcount => :3))) DatesDiff (n)
); `,
binds: [formatDate(INITIALDATE), formatDate(FINALDATE), DATESDIFF]
}
);
function formatDate(date) {
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [year, month, day].join('-');
}
$$
;
CALL DATES_TABLE('2021-04-01','2021-05-24');
Which when ran outside of the stored procedure, creates a table with dates between the range inputted.
Any idea why this is happening, how to sort it out?
The problem is in binding a variable to TABLE (generator (rowcount => :3)), as Snowflake expects a constant there.
Instead, you could do something like:
SELECT ROW_NUMBER() OVER (ORDER BY 1) - 1 AS rn
FROM TABLE (generator (rowcount => 1000))
QUALIFY rn < :2
I did some cleanup, and this works:
CREATE OR REPLACE PROCEDURE DATES_TABLE (INITIALDATE VARCHAR, FINALDATE VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
EXECUTE AS CALLER
AS
$$
var DATESDIFF = (Date.parse(formatDate(FINALDATE)) - Date.parse(formatDate(INITIALDATE)))/ (1000 * 3600 * 24);
snowflake.execute(
{
sqlText: `
CREATE OR REPLACE TEMPORARY TABLE TEMP_DATE_RANGE AS
SELECT CAST(DATEADD(DAY, DatesDiff.rn, :1) AS DATE) AS DATE
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY 1) - 1 AS rn
FROM TABLE (generator (rowcount => 1000))
QUALIFY rn < :2
)
;`
, binds: [formatDate(INITIALDATE), DATESDIFF]
}
);
function formatDate(date) {
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [year, month, day].join('-');
}
$$
;
CALL DATES_TABLE('2021-04-01','2021-05-24');
select * from TEMP_DATE_RANGE;
For a shorter way of generating a sequence of dates, see my answer to https://stackoverflow.com/a/66449068/132438.
We have a view which contains 2 columns: pattern_start_time, pattern_end_time.
The select query in the function will convert it to minutes and using that result we are processing to get the shift unused coverage.The function is getting created but the processing is not happening and getting the below error:
SQLError[IX000]:Routine (my_list) cant be resolved.
Also please enter image description heresuggest to loop till the length of the result.
CREATE function myshifttesting(orgid int) returning int;
DEFINE my_list LIST( INTEGER not null );
DEFINE my_list1 LIST( INTEGER not null );
define i, j, sub, sub1 int;
define total int;
TRACE ON;
TRACE 'my testing starts';
INSERT INTO TABLE( my_list )
select
((extend(current, year to second) + (dots.v_shift_coverage.pattern_start_time - datetime(00:00) hour to minute) - current)::interval minute(9) to minute)::char(10)::INTEGER
from
dots.v_shift_coverage
where
org_guid = orgid;
INSERT INTO TABLE( my_list1 )
select
((extend(current, year to second) + (dots.v_shift_coverage.pattern_end_time - datetime(00:00) hour to minute) - current)::interval minute(9) to minute)::char(10)::INTEGER
from
dots.v_shift_coverage
where
org_guid = orgid;
let sub = 0;
let sub1 = 0;
let total = 0;
for j = 0 to 4
if (my_list(j) < my_list1(j))
then
if (my_list(j + 1) > my_list1(j))
then
let sub = sub + my_list(j + 1) - my_list1(j);
end if;
end if;
end for
if (my_list(0) > my_list1(4))
then
let sub1 = my_list(0) - my_list1(4);
end if;
let total = sub + sub1;
return total;
end function;
The error that you are receiving is because my_list(j) is not valid Informix syntax to access a LIST element. Informix is interpreting my_list(j) as a call to a function named mylist.
You can use a temporary table to "emulate" an array with your logic, something like this:
CREATE TABLE somedata
(
letter1 CHAR( 2 ),
letter2 CHAR( 2 )
);
INSERT INTO somedata VALUES ( 'a1', 'a2' );
INSERT INTO somedata VALUES ( 'b1', 'b2' );
INSERT INTO somedata VALUES ( 'c1', 'c2' );
INSERT INTO somedata VALUES ( 'd1', 'd2' );
INSERT INTO somedata VALUES ( 'e1', 'e2' );
DROP FUNCTION IF EXISTS forloop;
CREATE FUNCTION forloop()
RETURNING CHAR( 2 ) AS letter1, CHAR( 2 ) AS letter2;
DEFINE number_of_rows INTEGER;
DEFINE iterator INTEGER;
DEFINE my_letter1 CHAR( 2 );
DEFINE my_letter2 CHAR( 2 );
-- Drop temp table if it already exists in the session
DROP TABLE IF EXISTS tmp_data;
CREATE TEMP TABLE tmp_data
(
tmp_id SERIAL,
tmp_letter1 CHAR( 2 ),
tmp_letter2 CHAR( 2 )
);
-- Insert rows into the temp table, serial column will be the access key
INSERT INTO tmp_data
SELECT 0,
d.letter1,
d.letter2
FROM somedata AS d
ORDER BY d.letter1;
-- Get total rows of temp table
SELECT COUNT( * )
INTO number_of_rows
FROM tmp_data;
FOR iterator = 1 TO number_of_rows
SELECT d.tmp_letter1
INTO my_letter1
FROM tmp_data AS d
WHERE d.tmp_id = iterator;
-- Check if not going "out of range"
IF iterator < number_of_rows THEN
SELECT d.tmp_letter2
INTO my_letter2
FROM tmp_data AS d
WHERE d.tmp_id = iterator + 1;
ELSE
-- iterator + 1 is "out of range", return to the beginning
SELECT d.tmp_letter2
INTO my_letter2
FROM tmp_data AS d
WHERE d.tmp_id = 1;
END IF;
RETURN my_letter1, my_letter2 WITH RESUME;
END FOR;
END FUNCTION;
-- Running the function
EXECUTE FUNCTION forloop();
-- Results
letter1 letter2
a1 b2
b1 c2
c1 d2
d1 e2
e1 a2
5 row(s) retrieved.
I require an output that shows the total number of hours worked in a rolling 24 hour window. The data is currently stored such that each row is one hourly slot (for example 7-8am on Jan 2nd) per person and how much they worked in that hour stored as "Hour". What I need to create is another field that is the sum of the most recent 24 hourly slots (inclusive) for each row. So for the 7-8am example above I would want the sum of "Hour" across the 24 rows: Jan 1st 8-9am, Jan 1st 9-10am... Jan 2nd 6-7am, Jan 2nd 7-8am.
Rinse and repeat for each hourly slot.
There are 6000 people, and we have 6 months of data, which means the table has 6000 * 183 days * 24 hours = 26.3m rows.
I am currently done this using the code below, which works on a sample of 50 people very easily, but grinds to a halt when I try it on the full table, somewhat understandably.
Does anyone have any other ideas? All date/time variables are in datetime format.
proc sql;
create table want as
select x.*
, case when Hours_Wrkd_In_Window > 16 then 1 else 0 end as Correct
from (
select a.ID
, a.Start_DTTM
, a.End_DTTM
, sum(b.hours) as Hours_Wrkd_In_Window
from have a
left join have b
on a.ID = b.ID
and b.start_dttm > a.start_dttm - (24 * 60 * 60)
and b.start_dttm <= a.start_dttm
where datepart(a.Start_dttm) >= &report_start_date.
and datepart(a.Start_dttm) < &report_end_date.
group by ID
, a.Start_DTTM
, a.End_DTTM
) x
order by x.ID
, x.Start_DTTM
;quit;
The most performant DATA step solution most likely involves a ring-array to track the 1hr time slots and hours worked within. The ring will allow a rolling aggregate (sum and count) to be computed based on what goes into and out of the ring.
If you have a wide SAS license, look into the procedures in SAS/ETS (Econometrics and Time Series). Proc EXPAND might have some rolling aggregate capability.
This sample DATA Step code took <10s (WORK folder on SSD) to run on simulated data for 6k people with 6months of complete coverage of 1hr time slots.
data have(keep=id start_dt end_dt hours);
do id = 1 to 6000;
do start_dt
= intnx('dtmonth', datetime(), -12)
to intnx('dtmonth', datetime(), -6)
by dhms(0,1,0,0)
;
end_dt = start_dt + dhms(0,1,0,0);
hours = 0.25 * floor (5 * ranuni(123)); * 0, 1/4, 1/2, 3/4 or 1 hour;
output;
end;
end;
format hours 5.2;
run;
/* %let log= ; options obs=50 linesize=200; * submit this (instead of next) if you want to log the logic; */
%let log=*; options obs=max;
data want2(keep=id start_dt end_dt hours hours_rolling_sum hours_rolling_cnt hours_out_:);
array dt_ring(24) _temporary_;
array hr_ring(24) _temporary_;
call missing (of dt_ring(*));
call missing (of hr_ring(*));
if 0 then set have; * prep pdv column order;
hours_rolling_sum = 0;
hours_rolling_cnt = 0;
label hours_rolling_sum = 'Hours worked in prior 24 hours';
index = 0;
do until (last.id);
set have;
by id start_dt;
index + 1;
if index > 24 then index = 1;
hours_out_sum = 0;
hours_out_cnt = 0;
do clear = 1 by 1 until (clear=0);
if sum (dt_ring(index), 0) = 0 then do;
* index is first go through ring array, or hit a zeroed slot;
&log putlog 'NOTE: ' index= 'clear for empty ring item. ';
clear = 0;
end;
else
if start_dt - dt_ring(index) >= %sysfunc(dhms(0,24,0,0)) then do;
&log putlog / 'NOTE: ' index= 'reducting and zeroing.' /;
hours_out_sum + hr_ring(index);
hours_out_cnt + 1;
hours_rolling_sum = hours_rolling_sum - hr_ring(index);
hours_rolling_cnt = hours_rolling_cnt - 1;
dt_ring(index) = 0;
hr_ring(index) = 0;
* advance item to next item, that might also be more than 24 hours ago;
index = index + 1;
if index > 24 then index = 1;
end;
else do;
&log putlog / 'NOTE: ' index= 'back off !' /;
* index was advanced to an item within 24 hours, back off one;
index = index - 1;
if index < 1 then index = 24;
clear = 0;
end;
end; /* do clear */
dt_ring(index) = start_dt;
hr_ring(index) = hours;
hours_rolling_sum + hours;
hours_rolling_cnt + 1;
&log putlog 'NOTE: ' index= 'overlaying and aggregating.' / 'NOTE: ' start_dt= hours= hours_rolling_sum= hours_rolling_cnt=;
output;
end; /* do until */
format hours_rolling_sum 5.2 hours_rolling_cnt 2.;
format hours_out_sum 5.2 hours_out_cnt 2.;
run;
options obs=max;
When reviewing the results you should notice the delta for hours_rolling_sum is +(hours in slot) - (hours_out_sum{which is hrs removed from ring})
If you must use SQL, I would suggest following #jspascal and index the table, but rearrange the query to left join original data to inner-joined subselect (so that SQL will do an index involved hash join on the ids) . For same amount of few people it should faster than original query, but still be too slow for doing all 6K.
proc sql;
create index id on have;
create index id_slot on have (id, start_dt);
quit;
proc sql _method;
reset inobs=50; * limit data so you can see the _method;
create table want as
select
have.*
, case
when ROLLING.HOURS_WORKED_24_HOUR_PRIOR > 16
then 1
else 0
end as REVIEW_TIME_CLOCKING_FLAG
from
have
left join
(
select
EACH_SLOT.id
, EACH_SLOT.start_dt
, count(*) as SLOT_COUNT_24_HOUR_PRIOR
, sum(PRIOR_SLOT.hours) as HOURS_WORKED_24_HOUR_PRIOR
from
have as EACH_SLOT
join
have as PRIOR_SLOT
on
EACH_SLOT.ID = PRIOR_SLOT.ID
and EACH_SLOT.start_dt - PRIOR_SLOT.start_dt between 0 and %sysfunc(dhms(0,24,0,0))-0.1
group by
EACH_SLOT.id, EACH_SLOT.start_dt
) as ROLLING
on
have.ID = ROLLING.ID
and have.start_dt = ROLLING.start_dt
order by
id, start_dt
;
%put NOTE: SQLOOPS = &SQLOOPS;
quit;
The inner join is pyramid-like and still involves a lot of internal looping.
A compound index on the columns being accessed in the joined table - id + start_dttm + hours - would be useful if there isn't one already.
Using msglevel=i will print some diagnostics about how the query is executed. It may give some additional hints.
I use SQL Server 2012. I have a sample table named 'Table1' with seven columns.
CREATE TABLE TABLE1
(
Field1 INT ,
Field2 INT ,
Field3 INT ,
Field4 INT ,
Field5 INT ,
Field6 INT ,
Field7 INT
)
GO
INSERT INTO TABLE1 VALUES (1,2,9,5,1,5,85)
INSERT INTO TABLE1 VALUES (2,6,8,4,1,4,45)
INSERT INTO TABLE1 VALUES (3,5,7,3,5,6,1)
INSERT INTO TABLE1 VALUES (4,4,6,1,51,4,1)
INSERT INTO TABLE1 VALUES (5,5,5,4,7,2,7)
INSERT INTO TABLE1 VALUES (6,5,4,6,4,7,8)
INSERT INTO TABLE1 VALUES (7,12,5,3,2,5,3)
INSERT INTO TABLE1 VALUES (8,1,6,5,9,5,1)
INSERT INTO TABLE1 VALUES (9,1,13,2,1,7,3)
INSERT INTO TABLE1 VALUES (10,6,9,3,6,2,6)
INSERT INTO TABLE1 VALUES (11,2,1,2,8,7,7)
INSERT INTO TABLE1 VALUES (12,7,6,1,3,3,2)
INSERT INTO TABLE1 VALUES (13,7,2,6,4,7,1)
GO
I have created the below Stored Procedure, This SP is able to query data considering the asked order by column.
I can query my table with each possibility of single column and kind of order by (ASC or Desc).
CREATE Procedure ProceName
(
#OrderByField INT = 1,
#OrderDirection INT = 0 -- 0 = Asc , 1 = Desc
)
As
Begin
SELECT
*
FROM Table1
ORDER BY
CASE WHEN #OrderDirection=0 AND #OrderByField=1 THEN Field1 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=2 THEN Field2 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=3 THEN Field3 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=4 THEN Field4 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=5 THEN Field5 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=6 THEN Field6 END ASC,
CASE WHEN #OrderDirection=0 AND #OrderByField=7 THEN Field7 END ASC,
CASE WHEN #OrderDirection=1 AND #OrderByField=1 THEN Field1 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=2 THEN Field2 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=3 THEN Field3 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=4 THEN Field4 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=5 THEN Field5 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=6 THEN Field6 END DESC,
CASE WHEN #OrderDirection=1 AND #OrderByField=7 THEN Field7 END DESC End
GO
EXECUTE ProceName #OrderByField=1, #OrderDirection=0
EXECUTE ProceName #OrderByField=6, #OrderDirection=1
Now I need to change this sp for accepting multi columns as order by series columns. They can be pass by name or by order of columns.
In this case i should be able to execute my SP like below command:
EXECUTE ProceName #OrderByField='6,7,2', #OrderDirection='0,1,1'
How can I achive this gool with out using the sp_executesql (Dynamic Query)?
Well, your ascending and descending wouldn't matter in your case unless you were trying to choose asc and desc for each column. Here is using the last column.
DECLARE #OrderByField VARCHAR(64) = '1,3,7'
DECLARE #DynamicColumns VARCHAR(256) = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#OrderByField,1,'FIELD1'),2,'FIELD2'),3,'FIELD3'),4,'FIELD4'),5,'FIELD5'),6,'FIELD6'),7,'FIELD7')
DECLARE #OrderDirection INT = 1
DECLARE #order varchar(4)
SET #order = CASE WHEN #OrderDirection = 1 THEN 'DESC' ELSE 'ASC' END
--uncomment this line of code to add the sort to every column which only matters for DESC since ASC is default
--SET #DynamicColumns = REPLACE(#DynamicColumns,',', ' ' + #order + ',')
DECLARE #sql VARCHAR(MAX) = (
'SELECT
*
FROM Table1
ORDER BY ' + #DynamicColumns + ' ' + #order)
SELECT #sql
EXEC(#sql)
CREATE Procedure ProceName
(
#OrderByField VARCHAR(100),
#OrderDirection VARCHAR(100),
)
As
Begin
Declare #SQL VARCHAR(MAX)
if OBJECT_ID('Example1') is not null
begin
drop table Example
end
if OBJECT_ID('Example2') is not null
begin
drop table Example2
end
Create table Example1
(
id int identity(1,1),
Field varchar(20)
)
Create table Example2
(
id int identity(1,1),
OrderNumber varchar(10)
)
--iterate each element for both #OrderByField and #OrderDirection
Declare #separator char(1)=','
Declare #position int = 0
Declare #name varchar(20)
Set #OrderByField = #OrderByField + #separator
---------------------------------------------------------------------------
/*iterate each for #OrderByField */
---------------------------------------------------------------------------
While CHARINDEX (#separator,#OrderByField,#position) != 0
BEGIN
SET #name = SUBSTRING (#OrderByField,#position,CHARINDEX (#separator,#OrderByField,#position)-#position)
SET #SQL = 'Insert into Example1([Field]) Values(' + char(39) + #name + char(39) + ')'
EXEC(#SQL)
SET #position = CHARINDEX(#separator,#OrderByField,#position)+1
END
---------------------------------------------------------------------------
/*iterate each for #OrderDirection */
---------------------------------------------------------------------------
SET #position = 0 --do not forget to reset the position number
Set #OrderDirection = #OrderDirection + #separator
While CHARINDEX (#separator,#OrderDirection,#position) != 0
BEGIN
SET #name = SUBSTRING (#OrderDirection,#position,CHARINDEX (#separator,#OrderDirection,#position)-#position)
SET #SQL = 'Insert into Example2([OrderNumber]) Values(' + char(39) + #name + char(39)+ ')'
EXEC(#SQL)
SET #position = CHARINDEX(#separator,#OrderDirection,#position)+1
END
Set #name = '' --reset the #name for the use of Cursor
declare #NewName varchar(500) =''
Declare row_cursor CURSOR
FOR
select 'Field'+a.Field + ' '+ case when b.OrderNumber = 0 then 'ASC' else 'DESC' end +',' as command
from Example1 as a
inner join Example2 as b
on b.id = a.id
OPEN row_cursor
FETCH NEXT FROM row_cursor into #name
WHILE (##FETCH_STATUS =0)
begin
Set #NewName = #NewName + #name
FETCH NEXT FROM row_cursor into #name
end
close row_cursor
deallocate row_cursor
SET #NewName = REVERSE(STUFF(REVERSE(#NewName),1,1,''))
SET #SQL = 'Select * From Table1 Order by ' + #NewName
--Print(#SQL)
EXEC (#SQL)
END
I am new to DB2 queries.
Here, I am passing a comma separated value as an IN parameter in a Stored Procedure. I want to search on the basis of those values.
Select * from USER where user_id in (IN_User);
Here, IN_User will have values of the kind ('val1','val2','val3')
It should return all the rows which has val1 or val2 or val3 as the User_id. As much as I know this can be done using UDF but I want to know is there any other way to do it without UDF.
please create a function to split the comma separated string
Please see the below function
CREATE FUNCTION StringToRows(
cString1 CLOB (10 M) ,
cStringSplitting1 VARCHAR(10) )
RETURNS TABLE (Lines VARCHAR(500))
SPECIFIC StringToRows_Big
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL
BEGIN ATOMIC
DECLARE cStringSplitting VARCHAR(10);
DECLARE LenSplit SMALLINT;
SET cStringSplitting = cStringSplitting1;
SET LenSplit = LENGTH(cStringSplitting);
IF LENGTH(TRIM(cStringSplitting)) = 0 THEN
SET cStringSplitting = ' ', LenSplit = 1 ;
END IF ;
RETURN WITH
TEMP1 ( STRING) as (values (cString1) ),
TEMP2 ( Lines, STRING_left) as
(SELECT
SUBSTR(STRING,1, CASE WHEN LOCATE(cStringSplitting, STRING) = 0 THEN LENGTH(STRING) ELSE LOCATE(cStringSplitting,STRING) - 1 END),
(CASE WHEN (LOCATE(cStringSplitting, STRING) = 0) THEN '' ELSE SUBSTR(STRING, LOCATE(cStringSplitting,STRING) + LenSplit) END)
FROM TEMP1 WHERE LENGTH(STRING) > 0
UNION ALL
SELECT
SUBSTR(STRING_left,1, CASE LOCATE(cStringSplitting,STRING_left) WHEN 0 THEN LENGTH(STRING_left) ELSE LOCATE(cStringSplitting,STRING_left) - 1 END),
(CASE WHEN LOCATE(cStringSplitting,STRING_left) = 0 THEN '' ELSE SUBSTR(STRING_left, LOCATE(cStringSplitting,STRING_left) + LenSplit) END)
FROM TEMP2 WHERE LENGTH(STRING_left) > 0 )
SELECT Lines FROM TEMP2;
END
please see the sample stored procedure to call the function
CREATE PROCEDURE TEST_USR(IN #inputParam CLOB (10 M))
SPECIFIC TEST_USR
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE CURSOR1 CURSOR WITH RETURN FOR
Select * from USER where user_id IN (SELECT * FROM TABLE(StringToRows(#inputParam, ',')) AS test);
OPEN CURSOR1;
END P1