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
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';
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...
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])...)
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;
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']