Postgres EXECUTE with CTE - postgresql

Is it possible to EXECUTE a prepared statement using parameters you'd get from a CTE ?
The below samples are simplified versions of my code, but this is replicating exactly the problem I have.
Here's how far I've been able to go - without a CTE :
BEGIN;
CREATE TEMPORARY TABLE testTable
(
col1 NUMERIC,
col2 TEXT
) ON COMMIT DROP;
INSERT INTO testTable
VALUES (1, 'foo'), (2, 'bar');
PREPARE myStatement AS
WITH cteTable AS
(
SELECT col1, col2
FROM testTable
WHERE col1 = $1
)
SELECT col2 FROM cteTable;
EXECUTE myStatement(2);
DEALLOCATE myStatement;
COMMIT;
Here's the result:
col2
bar
And now, here's what I am trying to achieve :
BEGIN;
CREATE TEMPORARY TABLE testTable
(
col1 NUMERIC,
col2 TEXT
) ON COMMIT DROP;
INSERT INTO testTable
VALUES (1, 'foo'), (2, 'bar');
PREPARE myStatement AS
WITH cteTable AS
(
SELECT col1, col2
FROM testTable
WHERE col1 = $1
)
SELECT col2 FROM cteTable;
-- Using a CTE here to get the parameters for the prepared statement
WITH parameters AS
(
SELECT 2 val
)
EXECUTE myStatement(SELECT val FROM parameters);
DEALLOCATE myStatement;
COMMIT;
The error message I'm having is
Syntax error at or near EXECUTE
Even if try to run the EXECUTE part without attempting to use the CTE values, I still have the same error message.
As I haven't been able to find anyone else having the same issue despite my researches, I guess I could be doing it wrong.. If so could someone please point me into the right direction ?
Thanks

As Vao Tsun commented, this is not possible.
I have converted my prepared statement to a function which will return a table, then used a CTE to SELECT UNION my function with multiple parameters.

Related

select/delete can not use default in postgresql?

begin;
create table test101(col1 int default 2, col2 text default 'hello world');
insert into test101 values (1,default);
insert into test101 values (default,default);
insert into test101 values (default,'dummy');
insert into test101 values (5,'dummy');
commit;
update: OK.
update test101 set col2 = default where col1 = 4;
select, delete not OK.
select * from test101 where col1 = COALESCE (col1,default);
delete from test101 where col1 = COALESCE (col1,default);
error code:
ERROR: 42601: DEFAULT is not allowed in this context
LINE 1: delete from test101 where col1 = COALESCE (col1,default);
^
LOCATION: transformExprRecurse, parse_expr.c:285
also tried: delete from test101 where col1 = default;
default value is not easy to find.
Get the default values of table columns in Postgres? Then select/delete operation with default operation is not that weird.
In the question you linked to they do:
SELECT column_name, column_default
FROM information_schema.columns
WHERE (table_schema, table_name) = ('public', 'test101')
ORDER BY ordinal_position;
which produces something like:
column_name | column_default
-------------+---------------------
col1 | 2
col2 | 'hello world'::text
Maybe, you can combine this query with your query? (But I would not recommend it, because ... )

Temp table intermittent issue

We used to have a really badly performing CTE that used to take 3-5 mins to execute. I have modified the CTE and used a function with temp tables to accomplish the same task. Now the new function runs in less than 5 secs.
This is what I did:
From
WITH CTE1 AS (
SELECT ...
FROM ...
),
CTE2 AS (
SELECT ...
FROM ...
),
CTEn AS (
SELECT ...
FROM ...
)
SELECT A,B,C,D
FROM CTE1
JOIN CTE2 ON ...
JOIN CTEn ON ...;
TO
CREATE OR REPLACE FUNCTION FUNC_ABC(a integer)
RETURNS TABLE(A integer, B integer, C integer, D integer)
LANGUAGE plpgsql
AS $function$
DECLARE
x ALIAS for $1;
BEGIN
DROP TABLE IF EXISTS CTE1;
DROP TABLE IF EXISTS CTE2;
DROP TABLE IF EXISTS CTEn;
CREATE TEMP TABLE CTE1 AS
( SELECT ...
FROM ...
);
CREATE TEMP TABLE CTE2 AS
( SELECT ...
FROM ...);
CREATE TEMP TABLE CTEn AS
( SELECT ...
FROM ...);
CREATE INDEX ix_cte1 ON CTE1(A);
CREATE INDEX ix_cte2 ON CTE2(B);
CREATE INDEX ix_cten ON CTEn(C);
CREATE INDEX ix_cten ON CTEn(D);
RETURNS QUERY SELECT A,B,C,D
FROM CTE1
JOIN CTE2 ON ...
JOIN CTEn ON ...
END;
$function$
;
As I stated above, the function pretty fast. The reason behind adding "DROP TABLE" is that, within a transaction, this function can be executed any number of times. But, intermittently, we see an error like:
ERROR: must be owner of relation CTE1
I am not able to reproduce this error. And there is only one user that runs this function. No other user has permissions to execute this function.
I couldn't think of a scenario when this would fail. Any thoughts of insights will be appreciated.

RETURNING in INSERT INTO statement with a SELECT statement

I am inserting data into a table and want to return the inserted data. The inserted data contains foreign keys. I would like to get the whole data with the joins of the foreign keys.
I have tried putting a SELECT in RETURNING without luck. Is this even possible or do I just have to do another query after inserting the data?
Insert statement:
INSERT INTO someTable (col1, col2, col3, foreign_id)
VALUES ('value1', 'value2', 'value3', 1);
So in this case, could I have a RETURNING that basically would give me:
SELECT someTable.*, foreignTable.*
FROM someTable
JOIN foreignTable ON someTable.foreign_id = foreignTable.id;
demo:db<>fiddle
You can use a CTE for this:
WITH inserting AS (
INSERT INTO...
RETURNING <new data>
)
SELECT i.*, ft.*
FROM inserting i JOIN foreign_table ft ...
In this case the INSERT statement will be executed. The SELECT statement will be executed after that. This can reference the inserted data.
You can use a CTE for that:
with new_row as (
INSERT INTO some_table (col1, col2, col3, foreign_id)
VALUES ('value1', 'value2', 'value3', 1)
returning *
)
SELECT new_row.*, ft.*
FROM new_row
JOIN foreign_table ft ON new_row.foreign_id = ft.id;

How to create a return statement in Redshift

I have following sql on SQL server
declare #groupName varchar(50)
select #groupName=groupIdentifier from tab1 where col3 ='ABC'
if #groupName is not NULL
select #groupName
return
select #groupName=col2 from tab2 where col1= 'BCD'
if #groupName is not NULL
select #groupName
return
I translated into RedShift as follows:
create temp table t1
(groupName)
---- Statement 1 -----
insert into t1 select groupIdentifier from tab1 where col3 ='ABC'
---- Statement 2 -----
insert into t1 select col2 from tab2 where col1= 'BCD' and (select count(1) from t1)=0
select * from t1
As there is no return statement in RedShift , Statement 2 always gets executed even when Statement 1 was able to fetch row. As second statement is expensive, how do I efficiently prevent it from being executed if Statement 1 is successful.
You cannot recreate this logic using only SQL statements on Redshift. Redshift SQL syntax does not contain control-of-flow commands like IF and RETURN.
Typically, you will need some kind of external program or script to manage the control-of-flow logic to work out which SQL statements to execute and what result to return.

Postgresql use string_agg results in an IN statement

Anyone know how string_agg results need to be "massaged" so they can be used in an IN statement?
The following is some sample code. Thanks for your time.
P.S: Before scratching your head and asking what the hell. I'm only using this code to show the problem of the string_agg b/c as you can see the query otherwise is a bit pointless.
Henry
WITH TEMP AS
(
SELECT 'John' AS col1
UNION ALL
SELECT 'Peter' AS col1
UNION ALL
SELECT 'Henry' AS col1
UNION ALL
SELECT 'Mo' AS col1
)
-- results that are being used in the IN statement
--SELECT string_agg('''' || col1::TEXT || '''',',') AS col1 FROM TEMP
SELECT col1 FROM TEMP
WHERE col1 IN
(
SELECT string_agg('''' || col1::TEXT || '''',',') AS col1
FROM TEMP
)
You can't mix dynamic code with static code. Your example is not very clear as to what exactly is it that you want to do. Your sample could be written as:
WITH TEMP(col1) AS (values ('John'), ('Peter'), ('Henry'), ('Mo'))
SELECT col1 FROM TEMP
WHERE col1 IN (SELECT col1 FROM TEMP)
or using an array:
WITH TEMP(col1) AS (values ('John'), ('Peter'), ('Henry'), ('Mo'))
SELECT col1 FROM TEMP
WHERE col1 = ANY (SELECT ARRAY(SELECT col1 FROM TEMP))
or simply (in this case since the main from and the subselect are the same table without any filters):
WITH TEMP(col1) AS (values ('John'), ('Peter'), ('Henry'), ('Mo'))
SELECT col1 FROM TEMP