Use result of postgres CTE in function - postgresql

I am having difficulty using the results from a CTE in a function. Given the following Postgres table.
CREATE TABLE directory (
id SERIAL PRIMARY KEY
, name TEXT
, parent_id INTEGER REFERENCES directory(id)
);
INSERT INTO directory (name, parent_id)
VALUES ('Root', NULL), ('D1', 1), ('D2', 2), ('D3', 3);
I have this recursive CTE that returns the descendants of a directory.
WITH RECURSIVE tree AS (
SELECT id
FROM directory
WHERE parent_id = 2
UNION ALL
SELECT directory.id
FROM directory, tree
WHERE directory.parent_id = tree.id
)
The returned values are what I expect and can be made to equal an array
SELECT (SELECT array_agg(id) FROM tree) = ARRAY[3, 4];
I can use an array to select values from the table
SELECT * FROM directory WHERE id = ANY(ARRAY[3, 4]);
However, I cannot use the results of the CTE to accomplish the same thing.
SELECT * FROM directory WHERE id = ANY(SELECT array_agg(id) FROM tree);
The resulting error indicates that there is a type mismatch.
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
However, I am unsure how to correctly accomplish this.

Use:
SELECT *
FROM directory
WHERE id = ANY(SELECT unnest(array_agg(id)) FROM tree);
See detailed explanation in this answer.
Using unnest() in a subquery is a general method for dealing with arrays:
where id = any(select unnest(some_array))
Because array_agg() and unnest() are inverse operations, the query can be as simply as:
SELECT *
FROM directory
WHERE id = ANY(SELECT id FROM tree);

Related

how can I translate a query to CTE?

I'm still having trouble understanding how CTE works.
I'm looking to make an insert. In case of conflict I use the on conflict do nothing but I want it to return the id to me (for the success of the insert or the conflict)
WITH inserted AS (
INSERT INTO fiche(label)
VALUES ('label')
ON CONFLICT (label) DO NOTHING
RETURNING *
)
SELECT * FROM inserted
WHERE NOT EXISTS (SELECT 1 FROM inserted);
Note that
SELECT * FROM some_relation
WHERE NOT EXISTS (SELECT 1 FROM some_relation);
will always give you an empty result. Either some_relation is empty itself or if it is not empty SELECT 1 FROM some_relation is not empty and therefore NOT EXISTS ... always returns false and so no record is matching the WHERE clause.
What you want is to have the VALUES as a CTE. You can then reference the values from your INSERT statement and in a SELECT to compare those values to the result of the RETURNING clause.
WITH
vals AS (
VALUES ('label')
),
inserted AS (
INSERT INTO fiche(label)
SELECT * FROM vals
ON CONFLICT (label) DO NOTHING
RETURNING label, id
)
SELECT
vals.column1,
inserted.id
FROM vals
LEFT JOIN inserted ON vals.column1 = inserted.label
This should give you a row for each row in your VALUES clause and the second column will be NULL if it was not inserted due to a conflict or the inserted ID otherwise.

How to insert values from a select query

how do I insert the std_id value and sub_id value in the student_subject table
insert into student_subjects(student_id,subject_id)
values(std_id,(select id from subjects
where guid in
(select * from
unnest(string_to_array(subjects_colls,',')::uuid[])))::int);
ERROR: more than one row returned by a subquery used as an expression
Get rid of the values clause and use the SELECT directly as the source for the INSERT statement:
You also don't need to unnest your array, using = any() will be a bit more efficient (although I would recommend you do not pass comma separated strings, but an array of uuid directly)
insert into student_subjects(student_id,subject_id)
select std_id, s.id
from subjects s
where guid = any(string_to_array(subjects_colls,',')::uuid[])
I assume this is part of a procedure or function and std_id and subjects_colls are parameters passed to it.

access subquery return value in union to other table postgres

Given the following query:
select parent_id from (
select parent_id, (most_recent_sit(date, child_id, key)).*
from links
where child_id = ANY(source_ids)
group by parent_id
) as subquery
union select * from link_traversal(
array(select parent_id from subquery),
);
Postgres throws the following error for the link_traversal function call:
relation "subquery" does not exist
link_traversal() and most_recent_sit() are custom functions I've created. I'd like to use the two functions and union the results together, where I reference the results of most_recent_sit() as the input for link_traversal().
Is it possible to reference a subquery select like so in a subsequent function call?

Populate column with query results in Postgresql

Table columns structured like:
longitude, latitude, gid, Hash
-78.885636, 36.854, 1, empty
Using PostgreSQL 9.4 and trying to update column Hash with results of a geohash function:
SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(longitude::float, latitude::float), 4326))
FROM my_table;
To update the column, I am using:
UPDATE my_table SET Hash = (SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(longitude::float, latitude::float), 4326))
FROM my_table);
But I get an error:
ERROR: more than one row returned by a subquery used as an expression.
I'm new to this so I may be asking a tedious question. Any help would be appreciated. For now I'll be RTFM-ing.
To update Hash column with a calculated value you need to use this query, where gid is a primary key(probably you need to change it).
UPDATE my_table
SET Hash = my_table_2.geo_hash
FROM
(SELECT gid,
ST_GeoHash(ST_SetSRID(ST_MakePoint(longitude::float, latitude::float), 4326)) as geo_hash
FROM my_table) as my_table_2
WHERE my_table.gid = my_table_2.gid
This worked for me:
UPDATE my_table SET "Hash" = (
SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(longitude::float, latitude::float), 4326))
FROM my_table p
WHERE my_table.gid = p.gid
);

Postgres ANY operator with array selected in a subquery

Can someone explain to me why the 4th select works, but the first 3 do not? (I'm on PostgreSQL 9.3.4 if it matters.)
drop table if exists temp_a;
create temp table temp_a as
(
select array[10,20] as arr
);
select 10 = any(select arr from temp_a); -- ERROR: operator does not exist: integer = integer[]
select 10 = any(select arr::integer[] from temp_a); -- ERROR: operator does not exist: integer = integer[]
select 10 = any((select arr from temp_a)); -- ERROR: operator does not exist: integer = integer[]
select 10 = any((select arr from temp_a)::integer[]); -- works
Here's a sqlfiddle: http://sqlfiddle.com/#!15/56a09/2
You might be expecting an aggregate. Per the documentation:
Note: Boolean aggregates bool_and and bool_or correspond to standard SQL aggregates every and any or some. As for any and some, it seems that there is an ambiguity built into the standard syntax:
SELECT b1 = ANY((SELECT b2 FROM t2 ...)) FROM t1 ...;
Here ANY can be considered either as introducing a subquery, or as being an aggregate function, if the subquery returns one row with a Boolean value. Thus the standard name cannot be given to these aggregates.
In Postgres, the any operator exists for subqueries and for arrays.
The first three queries return a set of values of type int[] and you're comparing them to an int. Can't work.
The last query is returning an int[] array but it's only working because you're returning a single element.
Exhibit A; this works:
select (select i from (values (array[1])) rows(i))::int[];
But this doesn't:
select (select i from (values (array[1]), (array[2])) rows(i))::int[];
This works as a result (equivalent to your fourth query):
select 1 = any((select i from (values (array[1])) rows(i))::int[]);
But this doesn't (equivalent to your fourth query returning multiple rows):
select 1 = any((select i from (values (array[1]), (array[2])) rows(i))::int[]);
These should also work, btw:
select 1 = any(
select unnest(arr) from temp_a
);
select 1 = any(
select unnest(i)
from (values (array[1]), (array[2])) rows(i)
);
Also note the array(select ...)) construct as an aside, since it's occasionally handy:
select 1 = any(array(
select i
from (values (1), (2)) rows(i)
));
select 1 = any(
select i
from (values (1), (2)) rows(i)
);