PostgreSQL function for each row - postgresql

I have a query which return to me a table like that:
id | VAL | Type
-- | --- | ----
1 | 10 | A
2 | 20 | B
3 | 30 | C
4 | 40 | B
I want to call some function for each row, which will check type and do some stuff for each type, like IF in C#:
if(type=='A'){}
if(type=='B'){}
if(type=='C'){}
How can I make these 2 things in Postgresql using only sql?

In standard SQL you can use a CASE phrase here:
SELECT id, val, "type",
CASE "type"
WHEN 'A' THEN funcA()
WHEN 'B' THEN funcB()
WHEN 'C' THEN funcC()
END AS func_result,
FROM <table>;
All functions should return a scalar value (a single value).

Related

How can I filter on the results of a set function

I have a function that returns a SETOF, and I want to filter on the set column.
Here's a minimal reproducible example of what I'm trying to do:
=> \d test1
Table "public.test1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | jsonb | | |
b | integer | | |
=> SELECT * FROM test1;
a | b
--------------------------+----
{"X": 1, "Y": 2} | 17
{"X": 4, "Y": 8, "Z": 3} | 22
(2 rows)
=> \ef test1function
CREATE OR REPLACE FUNCTION public.test1function(_item test1)
RETURNS SETOF text
LANGUAGE sql
AS $function$
SELECT jsonb_object_keys(_item.a)
$function$
With this setup, I can do queries like so:
=> SELECT test1.b, test1.test1function FROM test1;
b | test1function
----+---------------
17 | X
17 | Y
22 | X
22 | Y
22 | Z
(5 rows)
However, if I try to filter on the test1function field, I don't seem to be able to:
=> SELECT test1.b, test1.test1function FROM test1 HAVING test1function = "Z";
ERROR: column "test1function" does not exist
=> SELECT test1.b, test1.test1function FROM test1 HAVING test1.test1function = "Z";
ERROR: set-returning functions are not allowed in HAVING
Note: I am aware that, for this actual example, I could just write something like
SELECT b, 'Z' AS test1function FROM test1 WHERE a -> 'Z' IS NOT NULL;
b | test1function
----+---------------
22 | Z
(1 row)
As it happens, though, my actual analogue of test1function is more complicated than just a call to json_object_keys.
Is it just impossible to filter on the results of something returning SETOF at all?
EDIT: I'm also aware that I can do something like
=> SELECT * FROM (SELECT test1.b, test1.test1function FROM test1) q WHERE q.test1function = 'X';
b | test1function
----+---------------
17 | X
22 | X
(2 rows)
But that's awful... do I really have to do a subquery just to give this field a name I can reference?
You need to put set returning functions into the FROM clause. If you have to pass a column from a table, you need a lateral join. Then you can reference the columns of the function in the WHERE clause:
select *
from test1 t
cross join lateral test1function(t) as x(item)
where x.item = 'Z';

postgresql find similar word groups

I have a table1 containing a column A, where ~100,000 strings (varchar) are stored. Unfortunately, each string has multiple words which are seperated with spaces. Further they have different length, i.e. one string can consist of 3 words while an other string contains 7 words.
Then I have a column B stored in a second table2 which contains only 100 strings in the same manner. Hence, multiple words per string, seperated by spaces.
The target is, to look how likely a record of Column B is matching with probably multiple records of column A based on the words. The result should also have a ranking. I was thinking of using full text search in a loop but I don't know how to do this, or if there is a proper way to achieve this?
I don't know if you can "tturn" table to a dictionary to use full text for ranking here. But you can query it with some primityve ranking quite easily, eg:
t=# with a(a) as (values('a b c'),('a c d'),('b e f'),('r b t'),('q w'))
, b(i,b) as (values(1,'a b'), (2,'e'), (3,'b'))
, p as (select unnest(string_to_array(b.b,' ')) arr,i from b)
select a phrases,arr match_words,count(1) over (partition by arr) words_in_matches, count(1) over (partition by i) matches,i from a left join p on a.a like '%'||arr||'%';
phrases | match_words | words_in_matches | matches | i
---------+-------------+------------------+---------+---
r b t | b | 6 | 5 | 1
a b c | b | 6 | 5 | 1
b e f | b | 6 | 5 | 1
a b c | a | 2 | 5 | 1
a c d | a | 2 | 5 | 1
b e f | e | 1 | 1 | 2
r b t | b | 6 | 3 | 3
a b c | b | 6 | 3 | 3
b e f | b | 6 | 3 | 3
q w | | 1 | 1 |
(10 rows)
phrases are rows from your big table.
match_words are tokens from your small table (splitted by spaces)
words_in_matches amount of tokens in phrases
matches is amount of matches in big table phrases from small table phrases
i index of phrase from small table
So you can order by third or fourth column to get some sort of ranking...

How to aggregate more than one column of many rows inside one in PostgreSQL?

If my question is a bit obscure, here is what I mean, we can aggregate one column of multiple rows using array_agg, for instance I have this table
foo | bar | baz
-------+-------+-------
1 | 10 | 20
1 | 12 | 23
1 | 15 | 26
1 | 16 | 21
If I invoke this query :
select
foo,
array_agg(bar) as bars
from table
group by (foo)
resulting in :
foo | bars
-------+----------------
1 | {10,12,15,16}
What would be the query to have this table (using bar,baz) ?
foo | barbazs
-------+------------------------------------
1 | {{10,20},{12,23},{15,26},{16,21}}
I checked into functions-aggregate (postgresql.org) but it doesn't seem to be any functions to have that effect or am I missing something ?
array_agg has arrays as possible input values.
We just need a way to build an array from the two input colums bar and baz, which can be done using the ARRAY constructor:
SELECT foo, array_agg(ARRAY[bar, baz]) as barbaz FROM table GROUP BY foo;
foo | barbaz
-----+-----------------------------------
1 | {{10,20},{12,23},{15,26},{16,21}}
Note : It also works with DISTINCT (...array_agg(distinct array[bar,baz])...)

PostgreSQL JSONB query types of each keys

I have certain table:
CREATE TABLE x(
id BIGSERIAL PRIMARY KEY,
data JSONB
);
INSERT INTO x(data)
VALUES( '{"a":"test", "b":123, "c":null, "d":true}' ),
( '{"a":"test", "b":123, "c":null, "d":"yay", "e":"foo", "f":[1,2,3]}' );
How to query types of each key in that table, so it would give an output something like this:
a | string:2
b | number:2
c | null:2
d | boolean:1 string:1
e | string:1
f | jsonb:1 -- or anything
I only know the way to get the keys and count, but don't know how to get the type of each key:
SELECT jsonb_object_keys(data), COUNT(id) FROM x GROUP BY 1 ORDER BY 1
that would give something like:
a | 2
b | 2
c | 2
d | 2
e | 1
f | 1
EDIT:
As pozs points out, there are two typeof functions: one for JSON and one for SQL. This query is the one you're looking for:
SELECT
json_data.key,
jsonb_typeof(json_data.value),
count(*)
FROM x, jsonb_each(x.data) AS json_data
group by key, jsonb_typeof
order by key, jsonb_typeof;
Old Answer: (Hey, it works...)
This query will return the type of the keys:
SELECT
json_data.key,
pg_typeof(json_data.value),
json_data.value
FROM x, jsonb_each(x.data) AS json_data;
... unfortunately, you'll notice that Postgres doesn't differentiate between the different JSON types. it regards it all as jsonb, so the results are:
key1 | value1 | value
------+--------+-----------
a | jsonb | "test"
b | jsonb | 123
c | jsonb | null
d | jsonb | true
a | jsonb | "test"
b | jsonb | 123
c | jsonb | null
d | jsonb | "yay"
e | jsonb | "foo"
f | jsonb | [1, 2, 3]
(10 rows)
However, there aren't that many JSON primitive types, and the output seems to be unambiguous. So this query will do what you're wanting:
with jsontypes as (
SELECT
json_data.key AS key1,
CASE WHEN left(json_data.value::text,1) = '"' THEN 'String'
WHEN json_data.value::text ~ '^-?\d' THEN
CASE WHEN json_data.value::text ~ '\.' THEN 'Number'
ELSE 'Integer'
END
WHEN left(json_data.value::text,1) = '[' THEN 'Array'
WHEN left(json_data.value::text,1) = '{' THEN 'Object'
WHEN json_data.value::text in ('true', 'false') THEN 'Boolean'
WHEN json_data.value::text = 'null' THEN 'Null'
ELSE 'Beats Me'
END as jsontype
FROM x, jsonb_each(x.data) AS json_data -- Note that it won't work if we use jsonb_each_text here because the strings won't have quotes around them, etc.
)
select *, count(*) from jsontypes
group by key1, jsontype
order by key1, jsontype;
Output:
key1 | jsontype | count
------+----------+-------
a | String | 2
b | Integer | 2
c | Null | 2
d | Boolean | 1
d | String | 1
e | String | 1
f | Array | 1
(7 rows)
You can improve your last query with jsonb_typeof
with jsontypes as (
SELECT
json_data.key AS key1,
jsonb_typeof(json_data.value) as jsontype
FROM x, jsonb_each(x.data) AS json_data
)
select *, count(*)
from jsontypes
group by 1, 2
order by 1, 2;

How do you do a "where in" sql query with an array data type?

I have a field:
dtype ==> character varying(3)[]
... but it's an array. So I have for example:
ID | name | dtype
1 | one | {'D10', 'D20', 'D30'}
2 | sam | {'D20'}
3 | jax | {'D10', 'D20'}
4 | pam | {'D10', 'D30'}
5 | pot | {'D10'}
I want to be able to do something like this:
select * from table where dtype in ('D20', 'D30')
This syntax doesnt work, but the goal is to then return fields 1,2,3,4 but not 5.
Is this possible?
Use the && operator as shown in the PostgreSQL manual under "array operators".
select * from table where dtype && ARRAY['D20', 'D30']