Create table with stored function postgresql - postgresql

I have a query for create table like below
DROP TABLE IF EXISTS BAJUL;
CREATE TABLE BAJUL AS (
SELECT dt_trx, row_number() OVER (ORDER BY dt_trx DESC) AS row_number
FROM stock_trx_idx
WHERE dt_trx BETWEEN '2017-01-01' AND '2017-02-28'
GROUP BY 1
ORDER BY 1 DESC);
How able to create above table with stored function in Postgresql?
I tried with below script
CREATE OR REPLACE FUNCTION my_function (dt1 DATE, dt2 DATE)
RETURNS VOID AS
$func$
BEGIN
EXECUTE format('
DROP TABLE IF EXISTS tblq;
CREATE TABLE IF NOT EXISTS tblq AS(
SELECT dt_trx, row_number() OVER (ORDER BY dt_trx DESC) AS row_number
FROM stock_trx
WHERE dt_trx BETWEEN dt1 AND dt2
GROUP BY 1
ORDER BY 1 DESC
)' );
END
$func$ LANGUAGE plpgsql;
but when I try to execute SF like below
SELECT my_function ('2017-01-01', '2017-02-28');
I got error --> ERROR: column "dt1" does not exist
Would like to seek your help.
Thanks & rgds,
Bayu

Use
format('CREATE ... WHERE dt_trx BETWEEN %L AND %L ...', dt1, dt2)

You error is obvious. In the SELECT statement,
SELECT dt_trx, row_number() OVER (ORDER BY dt_trx DESC) AS row_number
FROM stock_trx
WHERE dt_trx BETWEEN dt1 AND dt2
GROUP BY 1
ORDER BY 1 DESC
The dt1 column doesn't exist. You didn't tell him you wanted to use your variable. Try concatenate your string with your variables.
By the way, you can drop your ORDER BY if your creating a table with that statement.

Related

Postgresql strange behavior with update trigger

I have a table1: id int, id_2 int, date timestamp, vec float[]
And table2 : id int, vec float[]
My target is to create trigger on update of table1 which will take last 10 (by date) rows for id_2, take average of vectors by first axis(10 x N -> N) and write it to table2 under id = id_2.
My code:
CREATE OR REPLACE FUNCTION public.foo()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
WITH rows AS (
SELECT DISTINCT t1.id_2, t2.id, t2.vec, t2.date, DENSE_RANK() OVER (PARTITION BY t1.id_2 ORDER BY t2.date desc) AS counter
FROM new_table AS t1
LEFT JOIN table1 t2 ON t1.id_2 = t2.id_2
),
elements_average AS (
SELECT id_2, AVG(unnest::float) AS av
FROM rows,
unnest(vec) with ORDINALITY
WHERE counter < 11
GROUP BY id_2, ORDINALITY
ORDER BY ORDINALITY
),
avr AS (
SELECT id_2, array_agg(av::float) AS averages
FROM elements_average
GROUP BY id_2
)
UPDATE table2 SET vec = averages FROM avr WHERE table2.id = user_av.id_2;
RETURN NULL;
END;
$function$
;
CREATE TRIGGER foo_trigger AFTER
UPDATE
ON
public.table1 REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE FUNCTION foo()
The problem: when I update few rows in table1 with different id_2 in one transactions a value in table2 becomes wrong. Not the average.
What's even more strange is that this code gives correct values in same situation:
...
avr AS (
SELECT id_2, array_agg(av::float) AS averages
FROM elements_average
GROUP BY id_2
),
strange_thing AS (
SELECT * from elements_average
)
UPDATE table2 SET vec = averages FROM avr WHERE table2.id = user_av.id_2;
RETURN NULL;
END;
$function$
;
So, small meaningless and unimportant SELECT changes the behavior of the function. Is it a bug of postgres or my fault?

Postgres - SELECT FOR UPDATE with union all

I am trying to put row level lock on a table in one postgres function.
do $$
declare tabname text :='locktest' ;
begin
execute 'create temp table temp1 as
select v.* from (
select row_number() over (partition by a.id) as row_num,a.*
from '||tabname||' a,locktest2 b where a.id=b.id and b.val=111
union all
select row_number() over (partition by a.id) as row_num,a.*
from '||tabname||' a,locktest2 b where a.id=b.id and b.val=222
)v where v.row_num=1 for update';
raise notice 'Completed';
end $$;
But while compiling it , getting below error.
ERROR: FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT
Please suggest.
The way the statement is written, the database does not know in which table to lock the rows.
Rewrite the query along these lines:
SELECT ... FROM
(SELECT ...
FROM tab1
WHERE ...
FOR NO KEY UPDATE) AS t1
UNION ALL
(SELECT ...
FROM tab2
WHERE ...
FOR NO KEY UPDATE) AS t2;
FOR NO KEY UPDATE is the correct lock if you plan to update. FOR UPDATE is the lock if you intend to delete.

T-SQL - Pivot/Crosstab - variable number of values

I have a simple data set that looks like this:
Name Code
A A-One
A A-Two
B B-One
C C-One
C C-Two
C C-Three
I want to output it so it looks like this:
Name Code1 Code2 Code3 Code4 Code...n ...
A A-One A-Two
B B-One
C C-One C-Two C-Three
For each of the 'Name' values, there can be an undetermined number of 'Code' values.
I have been looking at various examples of Pivot SQL [including simple Pivot sql and sql using the XML function?] but I have not been able to figure this out - or to understand if it is even possible.
I would appreciate any help or pointers.
Thanks!
Try it like this:
DECLARE #tbl TABLE([Name] VARCHAR(100),Code VARCHAR(100));
INSERT INTO #tbl VALUES
('A','A-One')
,('A','A-Two')
,('B','B-One')
,('C','C-One')
,('C','C-Two')
,('C','C-Three');
SELECT p.*
FROM
(
SELECT *
,CONCAT('Code',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
FROM #tbl
)t
PIVOT
(
MAX(Code) FOR ColumnName IN (Code1,Code2,Code3,Code4,Code5 /*add as many as you need*/)
)p;
This line
,CONCAT('Code',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
will use a partitioned ROW_NUMBER in order to create numbered column names per code. The rest is simple PIVOT...
UPDATE: A dynamic approach to reflect the max amount of codes per group
CREATE TABLE TblTest([Name] VARCHAR(100),Code VARCHAR(100));
INSERT INTO TblTest VALUES
('A','A-One')
,('A','A-Two')
,('B','B-One')
,('C','C-One')
,('C','C-Two')
,('C','C-Three');
DECLARE #cols VARCHAR(MAX);
WITH GetMaxCount(mc) AS(SELECT TOP 1 COUNT([Code]) FROM TblTest GROUP BY [Name] ORDER BY COUNT([Code]) DESC)
SELECT #cols=STUFF(
(
SELECT CONCAT(',Code',Nmbr)
FROM
(SELECT TOP((SELECT mc FROM GetMaxCount)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) t(Nmbr)
FOR XML PATH('')
),1,1,'');
DECLARE #sql VARCHAR(MAX)=
'SELECT p.*
FROM
(
SELECT *
,CONCAT(''Code'',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
FROM TblTest
)t
PIVOT
(
MAX(Code) FOR ColumnName IN (' + #cols + ')
)p;';
EXEC(#sql);
GO
DROP TABLE TblTest;
As you can see, the only part which will change in order to reflect the actual amount of columns is the list in PIVOTs IN() clause.
You can create a string, which looks like Code1,Code2,Code3,...CodeN and build the statement dynamically. This can be triggered with EXEC().
I'd prefer the first approach. Dynamically created SQL is very mighty, but can be a pain in the neck too...

How to "loop" through dates in PostgreSQL

Say I have a query with a nested query inside of a where condition.
SELECT COUNT(id)
FROM table
WHERE create_date = date_trunc('month', current_timestamp)
and id NOT IN (
SELECT DISTINCT id
FROM some_table
WHERE date_trunc('month', current_timestamp)
)
This query gets the metric for this month. However, what if I want it for all months?
I tried this query, although it doesn't seem to run/takes a very long time:
SELECT date_trunc('month', t.create_date), COUNT(id)
FROM table t
WHERE id NOT IN (
SELECT DISTINCT id
FROM some_table tt
WHERE date_trunc('month', tt.create_date)= date_trunc('month', t.create_date)
)
GROUP BY date_trunc('month', t.create_date)
I would like to execute this command via Postgres CLI (from the command line).
Any guidance to make this query more efficient or logical appreciated!

How to write a multi-parameter CTE script?

I am trying to write a TSQL script for an SSRS report that uses a CTE to select records based on the parameters chosen. I'm looking for the most efficient way to do this, either all in TSQL and/or SSRS. I have 4 parameters which can be set to NULL (All values) or one specific value. Then in my CTE, I have the following line:
ROW_NUMBER() over(partition by G.[program_providing_service],G.people_id
order by G.[actual_date] desc) as rowID
This above CTE is for the case when Program is NULL and People is not null. My 4 parameters are:
Program, Facility, Staff, and People.
So I only want to partition values when they are NULL. Currently I implement this by one CTE depending on the parameter values. For example, if they choose NULL for all parameters except People, then this CTE would look like:
ROW_NUMBER() over(partition by G.people_id
order by G.[actual_date] desc) as rowID
Or if all 5 parameters are null:
ROW_NUMBER() over(partition by G.[program_providing_service], G.[site_providing_service], G.staff_id, G.people_id
order by G.[actual_date] desc) as rowID
If they do not choose NULL for any of the 4 parameters, then I probably do not need to partition by any field since I just want the top 1 record ordered by actual_date descending. This is what my CTE looks like:
;with cte as
(
Select distinct
G.[actual_date],
G.[site_providing_service],
p.[program_name],
G.[staff_id],
G.program_providing_service,
ROW_NUMBER() over(partition by G.[program_providing_service],G.people_id
order by G.[actual_date] desc) as rowID
From
event_log_rv G With (NoLock)
WHERE
...
AND (#ClientID Is Null OR [people_id]=#ClientID)
AND (#StaffID Is Null OR [staff_id] = #StaffID)
AND (#FacilityID Is Null OR [site_providing_service] = #FacilityID)
AND (#ProgramID Is Null OR [program_providing_service] = #ProgramID)
and (#SupervisorID is NULL OR staff_id in (select staff_id from #supervisors))
)
SELECT
[actual_date],
[site_providing_service],
[program_name],
[staff_id],
program_providing_service,
people_id,
rowID
FROM cte WHERE rowid = 1
ORDER BY [Client_FullName]
where the ROW_NUMBER line varies depending on the parameters chosen. Currently I have 5 IF statements in this TSQL script that look like:
IF #ProgramID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
with one CTE in each of these IF statements:
IF #FacilityID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #ProgramID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #StaffID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #ClientID IS NOT NULL
BEGIN
...
END
How can I code for all possible options, whether they choose NULL or else specific values?
OMG.... it took me long time to try to understand what you want to do. There is some contradiction in your description. Pleas revist your description. Like you said you only want to partition values when they are NULL; then you also said, when they choose NULL for all parameter except for people, then you partition on people....
No matter what way you want to achieve, partition on 'null' or 'not null', you can construct dynamic sql to achieve this, instead of adding a lot of [if...else]
Following code is pseudo, definitely not tested. Just give you a hint. The following code has one assumption, which is your parameters have priority in partition order, for example, if Program is not null (or null), Program is in the first location.
declare #sql varchar(max)
set #sql = '
;with cte as
(
Select distinct
G.[actual_date],
G.[site_providing_service],
p.[program_name],
G.[staff_id],
G.program_providing_service,
ROW_NUMBER() over(partition by
'
if(#progarm is null)
set #sql = #sql + 'G.[program_providing_service],'
if(#facility is null)
set #sql = #sql + 'G.[site_providing_service],'
if(#staff is null )
set #sql = #sql + 'G.staff_id,'
if(#people is null)
set #sql = #sql + 'G.people_id'
set #sql = #sql + '
order by G.[actual_date] desc) as rowID
From
event_log_rv G With (NoLock)
WHERE
...
AND (#ClientID Is Null OR [people_id]=#ClientID)
AND (#StaffID Is Null OR [staff_id] = #StaffID)
AND (#FacilityID Is Null OR [site_providing_service] = #FacilityID)
AND (#ProgramID Is Null OR [program_providing_service] = #ProgramID)
and (#SupervisorID is NULL OR staff_id in (select staff_id from #supervisors))
)
SELECT
[actual_date],
[site_providing_service],
[program_name],
[staff_id],
program_providing_service,
people_id,
rowID
FROM cte WHERE rowid = 1
ORDER BY [Client_FullName]
'
exec(#sql)