How to write a workdays_between function as table function in SAP HANA? - hana-sql-script

How to calculate workdays_between function for begin date and end date of a table in SAP HANA?

The usage of workdays_between is as follows:
select
WORKDAYS_BETWEEN('01', '2017-11-01', '2017-11-30' , 'SAPABAP1') as number_of_workdays
from dummy;
If you have date ranges stored in a database table, then you can use following query as sample
select
WORKDAYS_BETWEEN('01', date_begin, date_end, 'SAPABAP1') as number_of_workdays
from date_ranges;
Please note that here the schema name SAPABAP1 is where the TFACS table is created
I hope it helps

Related

Create function or stored proc with interval variable

I am new to PostgreSQL but have been working with MSSQL for years. I am currently working with PostgreSQL 13.
In MSSQL I have a script that creates an offset for dates so that I can create a fiscal calendar in the same date table as my regular calendar. I just add the offset to the date in the row and it adjusts my fiscal year as needed.
I am trying to preform something like this in PostgreSQL. I have some code working to do what I want. However, now I am trying to write a function or stored procedure to automate this code. I am having issues converting the code to work in a function or stored procedure.
My code I have working looks like this. This is a very trimmed down version of the code for illustration purposes.
DO $$
DECLARE daydiff INTERVAL;
DECLARE startdate DATE;
DECLARE enddate DATE;
DECLARE fiscalstartdate DATE;
BEGIN
-- Populate these variables
startdate:=(date '2017-01-01');
enddate:=(date '2017-12-31');
fiscalstartdate:=(date '2016-09-01');
-- Calculate date diff
daydiff:=make_interval(days => startdate - fiscalstartdate);
-- Insert records into table
INSERT INTO master.Calendar (
full_date
, year
, fiscal_year
)
SELECT
CAST(generate_series AS date) AS full_date
, EXTRACT(YEAR FROM generate_series)::INTEGER AS year
, EXTRACT(YEAR FROM generate_series + daydiff)::INTEGER AS fiscal_year
FROM (
SELECT generate_series(
startdate::timestamp,
enddate::timestamp,
interval '1 days'
)
) a;
END $$;
It does exactly what I want to do. However, I want to be able to call that in a function or stored procedure to automate rebuilding of tables and data. I then tried a few different things, but where I am at right now is this. Again, this is the trimmed down version, but still the issue is present.
CREATE OR REPLACE PROCEDURE create_calendar_table(startdate DATE, enddate DATE, fiscalstartdate DATE) AS $$
DECLARE
daydiff INTERVAL;
BEGIN
-- Calculate date diff
daydiff:=make_interval(days => startdate - fiscalstartdate);
-- Insert records into table
INSERT INTO master.Calendar (
full_date
, year
, fiscal_year
)
SELECT
CAST(generate_series AS date) AS full_date
, EXTRACT(YEAR FROM generate_series)::INTEGER AS year
, EXTRACT(YEAR FROM generate_series + daydiff)::INTEGER AS fiscal_year
FROM (
SELECT generate_series(
startdate::timestamp,
enddate::timestamp,
interval '1 days'
)
) a;
END $$ LANGUAGE SQL;
I receive an error of 'syntax error at or near "INTERVAL"'. I have tried a few things like trying to add a DO $$ in there and that did not seem to work well at all. Very well could be my ignorance on this version of SQL. The best I can tell is that I can't declare an INTERVAL type when creating a procedure or function like I can in the DO $$.
How can I get around this or what is the proper way of doing this in PostgreSQL? Any help is greatly appreciated.
You need language plpgsql - you can't use variables inside a SQL function.

Delete all data from a table after selecting all data from the same table

All i want is to select all rows from a table and once it is selected and displayed, the data residing in table must get completely deleted. The main concern is that this must be done using sql only and not plsql. Is there a way we can do this inside a package and call that package in a select statement? Please enlighten me here.
Dummy Table is as follows:
ID NAME SALARY DEPT
==================================
1 Sam 50000 HR
2 Max 45000 SALES
3 Lex 51000 HR
4 Nate 66000 DEV
Any help would be greatly appreciated.
select * from Table_Name;
Delete from Table_Name
To select the data from a SQL query try using a pipelined function.
The function can define a cursor for the data you want (or all the data in the table), loop through the cursor piping each row as it goes.
When the cursor loop ends, i.e. all data has been consumed by your query, the function can perform a TRUNCATE table.
To select from the function use the following syntax;
SELECT *
FROM TABLE(my_function)
See the following Oracle documentation for information pipelined functions - https://docs.oracle.com/cd/B28359_01/appdev.111/b28425/pipe_paral_tbl.htm
This cannot be done inside a package, because " this must be done using sql only and not plsql". A package is PL/SQL.
However it is very simple. You want two things: select the table data and delete it. Two things, two commands.
select * from mytable;
truncate mytable;
(You could replace truncate mytable; with delete from mytable;, but this is slower and needs to be followed by commit; to confirm the deletion and end the transaction.)
Without pl/sql it's not possible.
Using pl/sql you can create a function which will populate a row, and then delete
Here is example :
drop table tempdate;
create table tempdate as
select '1' id from dual
UNION
select '2' id from dual
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER
);
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
CREATE OR REPLACE FUNCTION get_tab_tf RETURN t_tf_tab PIPELINED AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
FOR rec in (select * from tempdate) LOOP
PIPE ROW(t_tf_row(rec.id));
END LOOP;
delete from tempdate ; commit;
END;
select * from table(get_tab_tf) -- it will populate and then delete
select * from tempdate --you can check here result of deleting
you can use below query
select * from Table_demo delete from Table_demo
The feature you seek is SERIALIZABLE ISOLATION LEVEL. This feature enables repeatable reads, which in particular guarantee that both SELECTand DELETEwill read and process the same identical data.
Example
Alter session set isolation_level=serializable;
select * from tempdate;
--- now insert from other session a new record
delete from tempdate ;
commit;
-- re-query the table old records are deleted, new recor preserved.

How do I use a date as a variable in a postgreSQL function?

I'm trying to write a function in postgreSQL 9.5 that takes a date as a parameter. My table has a column called inception_date, and I want the function to return all rows from the table where inception_date is greater than the date provided as the variable. Below is my function:
CREATE OR REPLACE FUNCTION somefunc(date) RETURNS setof table AS
$BODY$
DECLARE variable ALIAS FOR $1;
BEGIN
RETURN QUERY SELECT * FROM table WHERE inception_date > variable;
END;
$BODY$
LANGUAGE 'plpgsql';
SELECT somefunc('2014-07-02');
I haven't been able to find any info saying dates are handled differently in posgreSQL functions than other datatypes, but this function doesn't display any output, while the query
SELECT * FROM table WHERE inception_date > '2014-07-02';
returns 15 rows. Does anyone know what might be going wrong here?
What is going here, You interpret Your code differently from PostgreSQL parser. For You '2014-07-02' is a date. For parser this is a srting. But he tries to be nice and when it see You use it for filtering a date column, it tries to interpret it as a date.
But given '2014-07-02', did You mean seventh day of february, or second day of july? He does not know it (and I do not know either). So, basically this is error on Your side and You should not do it this way.
What I would propose, is to create function with named parameter of date type, and use it as date. Then, when invoking this function, and having string as parameter, I would turn this string to date, telling PostgreSQL what I mean.
Examples and references to manual below.
CREATE OR REPLACE FUNCTION somefunc(p_date date)
RETURNS setof table
AS
$BODY$
BEGIN
RETURN QUERY SELECT * FROM table WHERE inception_date > p_date;
END;
$BODY$
LANGUAGE 'plpgsql';
SELECT somefunc(to_date('2014-07-02','YYYY-MM-DD'));
Here You can find more information on date formatting and creating of functions.
I have this 'pattern' for using pseudo variables containing dates. It does NOT rely on a function.
I like using the WITH() CTE for this purpose:
WITH myvar (enddate) AS (VALUES(to_date('2020-06-26', 'YYYY-MM-DD')))
SELECT 'Total downloaded records', sum(d.total_records) FROM download_table d
JOIN myvar m ON date(d.created) BETWEEN '2020-01-01' AND m.enddate
Please notice that the WITH Common Table Expression contains the date cast to a date format. You need this because you need to compare between date types and not date <= text.

How to use query created in stored procedure in other procedure

sql procedure creates temporary table which should used in other sql procedure.
I tried
CREATE or replace FUNCTION f_createquery()
RETURNS TABLE ( kuupaev date
) AS $f_createquery$
-- actually this is big and time consuming select statement which should evaluated only once:
select current_date as kuupaev
$f_createquery$ LANGUAGE sql STABLE;
CREATE or replace FUNCTION f_usequery()
RETURNS TABLE ( kuupaev date
) AS $f_usequery$
-- big query tehing is used several times in query:
select kuupaev from tehing
union all
select kuupaev+1 from tehing
union all
select kuupaev+2 from tehing
$f_usequery$ LANGUAGE sql STABLE;
with tehing as (
select * from f_createquery() _
)
select * from f_usequery() _
but got error
ERROR: relation "tehing" does not exist
tehing contains temporary data created by stored procedure and it does not exist in database. How to allow other stored procedure to use it ?
How to fix it for Postgres 9.1+ ?
Is there simething like
external table (kuupaev date)
which allows to define external tables?
In real applicaton select statement in f_createquery is big and time consuming and should evaluated only once.
Replacing select * from tehing to dynamic sql in f_usequery() probably works but this prevents procedure compiling at run time. Is there better solution or can tehing passed in better way to other stored procedure, e.q. like parameter ?
Or should f_createquery create temporary table with fixed name tehing?
If f_createquery() produces the temporary table tehing then it should not return anything. Your function should look like:
CREATE FUNCTION f_createquery() RETURNS void AS $f_createquery$
CREATE TEMPORARY TABLE tehing AS
SELECT current_date AS kuupaev; -- complex query here
$f_createquery$ LANGUAGE sql STABLE;
You can then use the tehing table in the current session like any other table:
CREATE FUNCTION f_usequery() RETURNS TABLE (kuupaev date) AS $f_usequery$
SELECT kuupaev FROM tehing
UNION ALL
SELECT kuupaev+1 FROM tehing
UNION ALL
SELECT kuupaev+2 FROM tehing;
$f_usequery$ LANGUAGE sql STABLE;
Note that the temporary table is dropped at the end of the session.
You can also integrate the 2 functions so you can call f_usequery() without worrying about calling another function first:
CREATE FUNCTION f_usequery() RETURNS TABLE (kuupaev date) AS $f_usequery$
BEGIN
PERFORM 1 FROM information_schema.tables WHERE table_name = 'tehing';
IF NOT FOUND THEN -- temp table tehing does not exist
CREATE TEMPORARY TABLE tehing AS
SELECT current_date AS kuupaev; -- etc, etc
END IF;
RETURN QUERY
SELECT kuupaev FROM tehing
UNION ALL
SELECT kuupaev+1 FROM tehing
UNION ALL
SELECT kuupaev+2 FROM tehing;
END; $f_usequery$ LANGUAGE plpgsql STABLE;
Note that this is now a plpgsql function so the syntax is slightly different.
The construct
with tehing as (
select * from f_createquery() _
)
select * from f_usequery() _
won't work, because you re-declare tehing as the result of the CTE. Instead, f_usequery() works with the tehing temporary table and you can select from it or do further analysis with the result from f_usequery():
SELECT f_createquery(); -- this creates the tehing temporary table
SELECT * FROM f_usequery(); -- this operates on the tehing table and returns some results
SELECT *
FROM tableX
JOIN f_usequery() USING (kuupaev)
WHERE kuupaev < '2015-09-19';
Simple case: CTE
If you can do everything in a single query, you typically don't need a temp table nor a function at all. A CTE does the job:
WITH tehing AS (
SELECT current_date AS kuupaev -- expensive query here
)
SELECT kuupaev FROM tehing
UNION ALL
SELECT kuupaev+1 FROM tehing
UNION ALL
SELECT kuupaev+2 FROM tehing;
If you need a temp table
A temporary table only makes sense if you actually have to run multiple queries using the result of the expensive query. Or if you need to create an index on the table or something.
Just try to create the temp table:
CREATE TEMP TABLE tehing AS
SELECT current_date AS kuupaev; -- expensive query here
If the table already exists you get an error, and that's fine as well.
Then run your queries using tehing.
If you really need a function that avoids this error (which I doubt):
CREATE FUNCTION f_create_tbl()
RETURNS text AS
$func$
BEGIN
IF to_regclass('pg_temp.tehing') IS NULL THEN -- temp table does not exist
CREATE TEMP TABLE tehing AS
SELECT current_date AS kuupaev; -- expensive query here
RETURN 'Temp table "tehing" created.';
ELSE
RETURN 'Temp table "tehing" already exists';
END IF;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT f_create_tbl();
The function cannot be declared STABLE, it's a VOLATILE function (default).
A test like this would be subtly inaccurate:
PERFORM 1 FROM information_schema.tables WHERE table_name = 'tehing';
It would find any table named "tehing" in the search path. But you are only interested if a temporary table of that name exists. The temporary schema is first in the search path by default:
How does the search_path influence identifier resolution and the "current schema"
Related:
How to check if a table exists in a given schema

SQL script to UNION a large number of tables

I have to do a union of a large number of disjoint daily tables from 2012-12-17 to 2012-10-30 in this example. The code for this gets ugly here is the snippet:
CREATE table map
with (appendonly=true, compresstype = quicklz)
AS
SELECT * FROM final_map_12_17
UNION ALL
SELECT * FROM final_map_12_16
UNION ALL
SELECT * FROM final_map_12_15
UNION ALL
SELECT * FROM final_map_12_14
UNION ALL
....
SELECT * FROM final_map_10_30;
Can I do this type of thing with a sequence or PL/PGSQL function instead of writing out each individual select by hand?
You can loop over date range in plpgsql function like this:
create or replace function add_map(date_from date, date_to date)
returns void language plpgsql as $$
declare
day date;
begin
for day in
select generate_series(date_from, date_to, '1 day')
loop
execute 'insert into map select * from final_map_'||
to_char(extract(month from day), '09')|| '_' ||
to_char(extract(day from day), '09');
end loop;
end; $$;
Calling the function:
-- create table map (....);
select add_map('2012-11-30', '2012-12-02');
is equivalent to:
insert into map select * from final_map_11_30;
insert into map select * from final_map_12_01;
insert into map select * from final_map_12_02;
There isn't a SQL function that would do this.
I would recommend that you put the list of tables in Excel. Then put in a formula such as:
="select * from "&a1&" union all"
Copy this formula down. Voila! You almost have the view defniition.
Copy the column with these statements into the SQL command tool. Add the create view at the top. Remove the union all at the end. And voila. You can easily create the view.
Have a think about redefining your list of tables as a partitioned table, with a single master table and multiple child tables. http://www.postgresql.org/docs/9.2/static/ddl-partitioning.html
Alternatively, maintain a view to union all the tables together, and when you add a new table to the schema add it to the view also.