Sequelize.js postgres LATERAL usage - sql

I have a postgres table with JSONB field.
json contains array of objects
| id | my_json_field |
-------------------------------------------------------
| 1234 | [{"id": 1, type: "c"}, {"id": 2, type: "v"}] |
| 1235 | [{"id": 1, type: "e"}, {"id": 2, type: "d"}] |
I need to sort/filter table by type key of json field.
Server accept id, so if id=1 - I need to sort by "c","e", if id=2 - by "v","d"
I have next SQL:
LEFT JOIN LATERAL (
SELECT elem ->> 'type' AS my_value
FROM jsonb_array_elements(my_json_field) a(elem)
WHERE elem ->> 'id' = '1'
) a ON true
this will add my_value field to the results and I can use it to sort/filter the table
This works fine in console, but I didn't find a way to add this using Sequelize.js
Also I'm open for any other solutions, thanks!
Edit, full query:
SELECT my_value FROM "main_table" AS "main_table"
LEFT OUTER JOIN ( "table2" AS "table2"
LEFT OUTER JOIN "form_table" AS "table2->form_table" ON "table2"."id" = "table2->form_table"."table2_id")
ON "main_table"."id" = "table2"."main_table_id"
LEFT JOIN LATERAL (
SELECT elem ->> 'type' AS my_value
FROM jsonb_array_elements("table2->form_table".structure) a(elem)
WHERE elem ->> 'id' = '1'
) a ON TRUE
ORDER BY "my_value" DESC;

You don't really need the keyword LATERAL as that is implied if you use a set returning function directly and not in a sub-select.
The following should do the same thing as your query and doesn't need the LATERAL keyword:
SELECT a.elem ->> 'type' as my_value
FROM "main_table"
LEFT JOIN "table2" ON "main_table"."id" = "table2"."main_table_id"
LEFT JOIN "form_table" AS "table2->form_table" ON "table2"."id" = "table2->form_table"."table2_id")
LEFT JOIN jsonb_array_elements("table2->form_table".structure) a(elem) on a.elem ->> 'id' = '1'
ORDER BY my_value DESC;
I also removed the useless parentheses around the outer joins and the aliases that don't give the table a new name to simplify the syntax.
Maybe that allows you to use the query with your ORM (aka "obfuscation layer")

Related

SQL ORA-00904: invalid identifier - join issue

have a problem with my query:
select Sources.dataset.name setName, x.element_name Commodity_Is, y.element_name Provider_Is
from meta.object_meta_v x, meta.object_meta_v y
join dw.load_set2_curve on x.object_id = dw.load_set2_curve.curve_id
join Sources.dataset on Sources.dataset.id = dw.load_set2_curve.load_set_id
where dw.load_set2_curve.curve_id in (
select max(curve_id) sample_curve_id from dw.load_Set2_curve
group by load_set_id
)
and (meta.object_meta_v.attribute = 'Provider' or meta.object_meta_v.attribute = 'Commodity');
the error is on the line:
join dw.load_set2_curve on x.object_id = dw.load_set2_curve.curve_id
I know why, because, according to this article 'https://stackoverflow.com/questions/10500048/invalid-identifier-on-oracle-inner-join' - "Looks like you cannot refer to an outer table alias in the join condition of the inner query." Unfortunately, I don't know how to find a workaround as I am looking for two different records (Commodity_is and Provider_is) from the same table in my query (with aliases 'x' and 'y').
Do you have any hints?
Your problem is that you are not using the table aliases in the SELECT, ON and WHERE clauses and are trying to refer to identifiers as schema.table.column and, in some cases that is ambiguous and you need to use table_alias.column.
Additionally, you are trying to mix legacy comma joins with ANSI joins (which does work but the comma joins need to be last, not first, so its easier just to use ANSI joins all the way through):
select ds.name setName,
x.element_name Commodity_Is,
y.element_name Provider_Is
from meta.object_meta_v x
CROSS JOIN meta.object_meta_v y
INNER JOIN dw.load_set2_curve lsc
ON x.object_id = lsc.curve_id
INNER JOIN Sources.dataset ds
ON ds.id = lsc.load_set_id
where lsc.curve_id in (
select max(curve_id) sample_curve_id
from dw.load_Set2_curve
group by load_set_id
)
and ( x.attribute = 'Provider'
or y.attribute = 'Commodity');
Which, for the sample data:
CREATE TABLE meta.object_meta_v (object_id, element_name, attribute) AS
SELECT 1, 'A', 'Provider' FROM DUAL UNION ALL
SELECT 2, 'B', 'Commodity' FROM DUAL;
CREATE TABLE dw.load_set2_curve (curve_id, load_set_id) AS
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 2, 200 FROM DUAL;
CREATE TABLE sources.dataset (id, name) AS
SELECT 100, 'DS1' FROM DUAL UNION ALL
SELECT 200, 'DS2' FROM DUAL;
Outputs:
SETNAME
COMMODITY_IS
PROVIDER_IS
DS1
A
B
DS1
A
A
DS2
B
B
db<>fiddle here

how to delete data array on jsonb postgresql

how to update array data in jsonb column on database postgresql?
for example on table table1 i have column attribute that have value like this:
id
attribute
1
[{"task_customs": ["a", "b", "c"]}]
2
[{"task_customs": ["d", "e", "f"]}]
for example if i want to delete b from id 1, so it will be like this on attribute column
id
attribute
1
[{"task_customs": ["a", "c"]}]
2
[{"task_customs": ["d", "e", "f"]}]
already do some research but didn't get what i need..
try this :
(a) Delete 'b' acccording to its position in the array :
UPDATE table1
SET attribute = attribute #- array['0', 'task_customs', '1'] :: text[]
WHERE id = 1
(b) Delete 'b' without knowing its position in the array :
WITH list AS
( SELECT id, to_jsonb(array[jsonb_build_object('task_customs', jsonb_agg(i.item ORDER BY item_id))]) AS new_attribute
FROM table1
CROSS JOIN LATERAL jsonb_array_elements_text(attribute#>'{0,task_customs}') WITH ORDINALITY AS i(item,item_id)
WHERE id = 1
AND i.item <> 'b'
GROUP BY id
)
UPDATE table1 AS t
SET attribute = l.new_attribute
FROM list AS l
WHERE t.id = l.id
see the test result in dbfiddle.
One option is to start splitting the JSONB value by using jsonb_to_recordset such as
UPDATE table1 AS t
SET attribute =
(
SELECT json_build_array(
jsonb_build_object('task_customs',task_customs::JSONB - 'b')
)
FROM table1,
LATERAL jsonb_to_recordset(attribute) AS (task_customs TEXT)
WHERE id = t.id
)
WHERE id = 1
Demo
Edit : If you need more elements as expressed within the comment then you can rather prefer using
UPDATE table1 AS t
SET attribute =
(
SELECT jsonb_agg(
jsonb_build_object(key,je.value::JSONB - 'b')
)
FROM table1,
LATERAL jsonb_array_elements_text(attribute) AS atr,
LATERAL jsonb_each_text(atr::JSONB) AS je
WHERE id = t.id
)
WHERE id = 1
Demo
i solve this issue by combining both answer from Edouard and Barbaros
this is my final query
UPDATE table1 AS t
SET attribute =
jsonb_set(
attribute,
'{0,task_customs}',
(
SELECT task_customs::JSONB - 'b'
FROM table1
CROSS JOIN LATERAL jsonb_to_recordset(attribute) AS (task_customs TEXT)
WHERE id = t.id
)
)
WHERE id = 1
see the test result in dbfiddle

How to process json data and put it into arrays in postgresql

I have a postgresql table with two columns. name VARCHAR(255) and notes JSON. A sample dataset might look like this:
| name | notes |
|-----------|----------------------------------|
| 'anna' | {'link_to': ['bob']} |
| 'bob' | {'link_to': ['anna', 'claudia']} |
| 'claudia' | {'link_to': []} |
Now I want to do two things.
put the list from the json at 'link_to' into another column called referrals_to (which must be of array VARCHAR type then). From my example:
name
notes
referrals_to
'anna'
{'link_to': ['bob']}
['bob']
'bob'
{'link_to': ['anna', 'claudia']}
['anna', 'claudia']
'claudia'
{'link_to': []}
[]
create another column called referrals_from, where I want to store all names from which a name was referred. In my example:
name
notes
referrals_from
'anna'
{'link_to': ['bob']}
['bob']
'bob'
{'link_to': ['anna', 'claudia']}
['anna']
'claudia'
{'link_to': []}
['bob']
How do I do this using postgresql queries? I could easily do it using python, but this would be slower than using postgresql directly, I guess.
The referrals_to column can be done using the -> operator to extract the array. For the referrals_from column I would use a scalar sub-select to collect all referring names into a single column (a JSON array)
select name,
notes,
notes -> 'link_to' as referrals_to,
(select jsonb_agg(name)
from the_table t2
where t2.name <> t1.name
and t2.notes -> 'link_to' ? t1.name) as referrals_from
from the_table t1
;
The ? operator tests if a JSON array contains a specific string. In this case it tests if the link_to array contains the name from the outer query.
Online Example
Assuming name is unique in your table you can use that query to update the new columns in the table:
update the_table
set referrals_to = notes -> 'link_to',
referrals_from = t.referrals_from
from (
select t1.name,
(select jsonb_agg(name)
from the_table t2
where t2.name <> t1.name
and t2.notes -> 'link_to' ? t1.name) as referrals_from
from the_table t1
) t
where t.name = the_table.name;
More or less what you described in your steps:
WItH cte AS (
SELECT
name,
BTRIM(value:: text, '"') AS referrals
FROM
(
SELECT
name,
foo.notes:: json - > > 'link_to' AS link_to
FROM
foo
) a
left JOIN LATERAL json_array_elements (a.link_to:: json) ON TRUE
)
SELECT
a1.name,
array_to_json(array_agg(a1.referrals))
FROM
cte a1
JOIN cte a2 ON a1.name = a2.referrals
GROUP BY
a1.name

How to group multiple columns into a single array or similar?

I would like my query to return a result structured like this, where tags is an array of arrays or similar:
id | name | tags
1 a [[1, "name1", "color1"], [2, "name2", color2"]]
2 b [[1, "name1", "color1"), (3, "name3", color3"]]
I expected this query to work, but it gives me an error:
SELECT i.id, i.name, array_agg(t.tag_ids, t.tag_names, t.tag_colors) as tags
FROM ITEMS
LEFT OUTER JOIN (
SELECT trm.target_record_id
, array_agg(tag_id) as tag_ids
, array_agg(t.tag_name) as tag_names
, array_agg(t.tag_color) as tag_colors
FROM tags_record_maps trm
INNER JOIN tags t on t.id = trm.tag_id
GROUP BY trm.target_record_id
) t on t.target_record_id = i.id;
Error:
PG::UndefinedFunction: ERROR: function array_agg(integer[], character varying[], character varying[]) does not exist
LINE 1: ..., action_c2, action_c3, action_name, action_desc, array_agg(...
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
This query works and produces similar results (but not quite what I want):
SELECT i.id, i.name, t.tag_ids, t.tag_names, t.tag_colors as tags as tags
FROM ITEMS
LEFT OUTER JOIN (
SELECT trm.target_record_id, array_agg(tag_id) as tag_ids, array_agg(t.tag_name) as tag_names, array_agg(t.tag_color) as tag_colors
FROM tags_record_maps trm
INNER JOIN tags t on t.id = trm.tag_id
GROUP BY trm.target_record_id
) t on t.target_record_id = i.id;
Result:
id | name | tag_ids | tag_names | tag_colors
1 a [1, 2] ["name1, "name2"] ["color1", "color2"]
1 a [1, 3] ["name1, "name3"] ["color1", "color3"]
Edit:
This query almost produces what I'm looking for, except it names the json keys f1, f2, f3. It would be perfect if I could name them id, name, color:
SELECT trm.target_record_id, json_agg( (t.id, t.tag_name, t.tag_color) )
FROM tags_record_maps trm
INNER JOIN tags t on t.site_id = trm.site_id and t.id = trm.tag_id
GROUP BY trm.target_record_id
having count(*) > 1;
Result:
[{"f1":1,"f2":"name1","f3":"color1"},{"f1":2,"f2":"name2","f3":"color2"}]
(t.id, t.tag_name, t.tag_color) is short syntax for ROW(t.id, t.tag_name, t.tag_color) - and a ROW constructor does not preserve nested attribute names. The manual:
By default, the value created by a ROW expression is of an anonymous record type. If necessary, it can be cast to a named composite type —
either the row type of a table, or a composite type created with
CREATE TYPE AS.
Bold emphasis mine. To also get proper key names in the result, cast to a registered composite type as advised in the quote, use a nested subselect, or simply use json_build_object() in Postgres 9.4 or newer (effectively avoiding the ROW constructor a priori):
SELECT trm.target_record_id
, json_agg(json_build_object('id', t.id
, 'tag_name', t.tag_name
, 'tag_color', t.tag_color)) AS tags
FROM tags_record_maps trm
JOIN tags t USING (site_id)
WHERE t.id = trm.tag_id
GROUP BY trm.target_record_id
HAVING count(*) > 1;
I use original column names, but you can chose your key names freely. In your case:
json_agg(json_build_object('id', t.id
, 'name', t.tag_name
, 'color', t.tag_color)) AS tags
Detailed explanation:
Return multiple columns of the same row as JSON array of objects
array_agg() puts one argument into an array. You could try to concatenate the values together:
array_agg(t.tag_ids || ':' || t.tag_names || ':' || t.tag_colors)
Or perhaps use a row constructor:
array_agg( (t.tag_ids, t.tag_names, t.tag_colors) )
Why not try a Json_Agg()?
SELECT
json_agg(tag_ids, tag_names, tag_colors)
FROM items
Etc...
DB fiddle
let's play with composite type.
create type tags as(tag_id bigint, tag_name text,tag_color text);
using array_agg:
select item_id,name, array_agg(row(trm.tag_id, tag_name, tag_color)::tags) as tags
from items i join tags_record_maps trm on i.item_id = trm.target_record_id
group by 1,2;
to json.
select item_id,name, to_json( array_agg(row(trm.tag_id, tag_name, tag_color)::tags)) as tags
from items i join tags_record_maps trm on i.item_id = trm.target_record_id
group by 1,2;
access individual/base element of composite type:
with a as(
select item_id,name, array_agg(row(trm.tag_id, tag_name, tag_color)::tags) as tags
from items i join tags_record_maps trm on i.item_id = trm.target_record_id
group by 1,2)
select a.item_id, a.tags[2].tag_id from a;

How to create a subset query in sql?

I have two tables as follows:
CREATE List (
id INTEGER,
type INTEGER REFERENCES Types(id),
data TEXT,
PRIMARY_KEY(id, type)
);
CREATE Types (
id INTEGER PRIMARY KEY,
name TEXT
);
Now I want to create a query that determines all ids of List which has given type strings.
For example,
List:
1 0 "Some text"
1 1 "Moar text"
2 0 "Foo"
3 1 "Bar"
3 2 "BarBaz"
4 0 "Baz"
4 1 "FooBar"
4 2 "FooBarBaz"
Types:
0 "Key1"
1 "Key2"
2 "Key3"
Given the input "Key1", "Key2", the query should return 1, 4.
Given the input "Key2", "Key3", the query should return 3, 4.
Given the input "Key2", the query should return 1, 3, 4.
Thanks!
select distinct l.id
from list l
inner join types t on t.id = l.type
where t.name in ('key1', 'key2')
group by l.id
having count(distinct t.id) = 2
You have to adjust the having clause to the number of keys you are putting in your where clause. Example for just one key:
select distinct l.id
from list l
inner join types t on t.id = l.type
where t.name in ('key2')
group by l.id
having count(distinct t.id) = 1
SQlFiddle example
You can use the following trick to extend Jurgen's idea:
with keys as (
select distinct t.id
from types t
where t.name in ('key1', 'key2')
)
select l.id
from list l join
keys k
on l.type = keys.id cross join
(select count(*) as keycnt from keys) k
group by l.id
having count(t.id) = max(k.keycnt)
That is, calculate the matching keys in a subquery, and then use this for the counts. This way, you only have to change one line to put in key values, and you can have as many keys as you would like. (Just as a note, I haven't tested this SQL so I apologize for any syntax errors.)
If you can dynamically produce the SQL, this may be one of the most efficent ways, in many DBMS:
SELECT l.id
FROM List l
JOIN Types t1 ON t1.id = l.type
JOIN Types t2 ON t2.id = l.type
WHERE t1.name = 'Key1'
AND t2.name = 'Key2' ;
See this similar question, with more than 10 ways to get the same result, plus some benchmarks (for Postgres): How to filter SQL results in a has-many-through relation