Trying to join on set returning function - json

I'm trying to join two tables on IDs extracted from json array of one of the tables, I found some topic on lateral joins but I can't get it, I'm failing to implement it on my case.
Or maybe there's other way to do it?
create table jsontable (response jsonb);
insert into jsontable values ('{"SCS":[{"customerId": 100, "referenceId": 215}, {"customerId": 120, "referenceId":544}, {"customerId": 400, "referenceId": 177}]}');
create table message (msg_id integer, status integer, content text);
insert into message values
(544, 1, 'Test'), (134, 1, 'Test2'), (177, 0, 'Test3'), (215, 1, 'Test4');
SELECT m.*
FROM jsontable t
JOIN message m ON m.msg_id = (jsonb_array_elements(t.response -> 'SCS')->>'referenceId')::int
and m.status = 1
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=8b3890efd34199f2356b6abca2f811c2
Of course it throws an ERROR: set-returning functions are not allowed in JOIN conditions

It looks like what you want to join against are the individual objects in the array, not the whole row. So use
SELECT m.*, obj
FROM jsontable t, jsonb_array_elements(t.response -> 'SCS') obj
JOIN message m ON m.msg_id = (obj->>'referenceId')::int AND m.status = 1;
or (a bit more readable imo)
SELECT m.*, obj
FROM jsontable t,
LATERAL jsonb_array_elements(t.response -> 'SCS') obj
JOIN message m ON m.msg_id = (obj->>'referenceId')::int
WHERE m.status = 1;
(updated fiddle)

jsonb_array_elements() :: int returns a set of integer which cannot be equal to one integer m.msg_id.
Try instead :
SELECT m.*
FROM jsontable t
JOIN message m ON m.msg_id IN (SELECT (jsonb_array_elements(t.response -> 'SCS')->>'referenceId')::int)
and m.status = 1

Related

Postgres Json_each_Text() function

I have a Json column 'status' in site_request_info table with data structure as given below:
{
"portStatus":{
"status":"In Progress",
"hrefStatus":{
"155e4d40948647bd947fceb58131982d":"FOC Received",
"155e4d40948647bd947fceb58131983d":"FOC Received"
}
},
"broadCloudStatus":{
"status":"Customer Created",
"orderStatus":{
"124260smbbTesct1188":"Order Created",
"124260smbbTesct1188-CPE":"Order Created"
}
},
"provisioningStatus":"Completed"
}
My Requirement is to filter the data based on HrefStatus, for that i have written query like:
select *
from product_requests p
inner join billing_Account b on p.account_id= b.account_id
inner join network_id n on b.account_id= n.account_id
inner join site_request_info s on n.networkid_uuid=s.networkid_uuid and s.product_request_id=p.request_id
**inner join json_each_text(cast(s.status->'portStatus'->'hrefStatus' as json)) d on true**
where p.request_status='In Progress' and d.value IN ('FOC Received');
because of use of json_each_text() function, i am getting two rows of same data which is something i dont want my query to return.
my aim is to select all those rows having hrefStatus supplied by IN clause
I tried one alternative query but i am not able to use In clasue with this solution
cast(status->'portStatus'->'hrefStatus' as json)::text like '%FOC Received%'
You can move your condition into an EXISTS condition:
select *
from product_requests p
join billing_Account b on p.account_id = b.account_id
join network_id n on b.account_id = n.account_id
join site_request_info s on n.networkid_uuid = s.networkid_uuid and s.product_request_id = p.request_id
where p.request_status = 'In Progress'
and exists (select *
from jsonb_each_text(s.status::jsonb->'portStatus'->'hrefStatus') as x(key,value)
where x.value = 'FOC Received')
With Postgres 12 you can also use a JSON path expression:
and s.status::jsonb #? '$.portStatus.hrefStatus.* ? (# == "FOC Received")'

complex query in mysql?

-table : events
[id=>1 , data_id =>1 , events_reference =>5 , g_id=>0
id=>5 , data_id=>1 , events_reference =>NULL , g_id=>1,
id=>6 . , data_id=>3, events_reference => 5 ,g_id=>0 ]
- table : data
[id=> 0 , name=>test1,
id=>1 , name=>test2,
id=>3 , name=>tes3 ]
I want to get column name g_id_1 including the data name if g_id_1 equal to data.id in data table(not the data_id column), and also I want to get column name g_id_2 that show the data name based on the events reference
ALSO, I want only bring the data that the data.id = 1 for example so event.id =6 will not appear
expected result: (get all events and + new columns calls "g_id_1" + "g_id_2")
{
"id"=>1,
events_reference => 5,
"g_id_1" = > "test 1"
"g_id_2" => "test 2"
},
{
"id"=>5,
events_reference => NULL,
"g_id_1" = > "test 2"
"g_id_2" => NULL
}
How can I do that, I tried with sub select but I manage to get the first level but not the second level because the events_reference is reference to another row in the same table
select id,events_reference , (SELECT name from data as d where events.events_reference = d.id) as g_id_1,
(SELECT name from data as d,events as e where e.events_reference = d.id) as g_id_1
Join the tables. You need to use multiple joins of the same tables to get the nested references. And you have to use LEFT JOIN to handle the cases where there's no match.
SELECT e1.id, e1.events_reference, d1.name AS g_id_1, d2.name AS g_id_2
FROM events AS e1
LEFT JOIN data AS d1 ON e1.g_id = d1.id
LEFT JOIN events AS e2 ON e1.events_reference = e2.id
LEFT JOIN data AS d2 ON e2.g_id = d2.id
DEMO
Im not a mySQL wiz but pretty good is MSSQL and it looks like you are wanting to do a LEFT join.
So what that will do is return another table IF the data exists.
SELECT events.id, events.events_reference, data.g_id_1, data.g_id_2
FROM events
LEFT JOIN data ON events.events_reference = eventtable.events_reference
LEFT JOIN data data2 ON eventtable.g_id_2 = eventtable2.g_id_1;
Pretty sure that is what your asking for, let me know if its not and I will have another look.
Caz

select all data on type , and change select if a row exists

am new here so please redirect me if am posting this wrong
sorry i can't post images yet
I have this table , I want to select all where type = 'Maladie'
but ... if a row with type = 'Reprise' that contains a date between any of the maladie type ones then i must show
maladie row date start -> Reprise rows date start
instead of
maladie row date start -> maladie row date end
so result for that image would look like this
note: rows with type Reprise may or may not exist
thnks
Outer join the reprise records. Where you find a match use its dates, where you don't use the original dates:
select
m.mat,
m.start,
coalesce(r.end, m.end) as end,
m.number_days,
m.number_hours
from (select * from mytable where type = 'Maladie') m
left join (select * from mytable where type = 'Reprise') r
on r.mat = m.mat and r.start between m.start and m.end;
If there can be more than one reprise record per maladie date range and you want to take the first one then, use:
select
m.mat,
m.start,
coalesce(r.repday, m.end) as end,
m.number_days,
m.number_hours
from (select * from mytable where type = 'Maladie') m
left join
(
select mat, min(end) as repday
from mytable
where type = 'Reprise'
group by mat
) r on r.mat = m.mat and r.repday between m.start and m.end;
You need to create self join and filter type that use min to catch first date if there is more that one reprise
SELECT a.mat
,a.start
,min(coalesce(b.END, a.END)) AS END
,a.number_of_days
,a.number_of_hours
,type
FROM table1 a
LEFT JOIN (
SELECT mat
,start AS start
,END AS END
FROM table1 t
WHERE t.type = 'Reprise'
) b ON b.start BETWEEN a.start
AND a.END and a.mat=b.mat
WHERE type = 'Malaide'
GROUP BY a.mat
,a.start
ORDER BY start

Returning adjacency list as nested JSON in Postgres

Given the simplest base case of a two column Postgres table (id, parent_id), is there a way to query an id and get back all of the children as a nested json structure like the following?
{
"id": 1,
"children": [{
"id": 2,
"children": [{
"id": 3,
"children": []
}]
}]
}
I understand how to recurse through the table, but I can't piece together how to use any of the psql json functions to return a result like above. Maybe I should just be using my language of choice to convert in postprocessing?
SQLFiddle of current progress.
Freaking difficult, took me hours to solve :-P
CREATE TABLE foo (
id INT PRIMARY KEY,
parent_id INT);
insert into foo values (1, null);
insert into foo values (2, 1);
insert into foo values (3, 2);
WITH RECURSIVE
tree AS (
SELECT 1 AS round, id, parent_id, ARRAY(SELECT id FROM foo WHERE parent_id = f.id) AS children
FROM foo f
WHERE id = 1
UNION ALL
SELECT round+1, f.id, f.parent_id, ARRAY(SELECT id FROM foo WHERE parent_id = f.id) AS children
FROM tree t
JOIN foo f ON (f.id = ANY(t.children))
),
rev AS (
SELECT r.round-1 AS round,
to_jsonb(ARRAY(
SELECT a
FROM (
SELECT f.parent_id AS id, json_agg(jsonb_build_object('id', f.id, 'children', '{}'::text[])) AS children
FROM tree t
JOIN foo f ON (f.id = t.id)
WHERE t.round = r.round
GROUP BY f.parent_id
) a
)) AS list
FROM (SELECT MAX(round)::int AS round FROM tree) r
UNION ALL
SELECT r.round-1,
to_jsonb(ARRAY(
SELECT a
FROM (
SELECT f.parent_id AS id, json_agg(jsonb_build_object('id', f.id, 'children', t->'children')) AS children
FROM jsonb_array_elements(list) t
JOIN foo f ON (f.id = (t->>'id')::int)
GROUP BY f.parent_id
) a
)) AS list
FROM rev r
WHERE round > 1
)
SELECT list as nested_json_tree
FROM rev
WHERE round = 1
The complexity lies in the requirement to build first the tree (top down), and then build the object from the tree, bottom-up. Recursive bottom-up is tricky due to limitation in recursive queries, such as the recursive alias within the UNION ALL section not being able to being grouped, nor included in a subquery. I solved this by doing the unwrapping via reversed rounds.
This query should properly build complex trees, with multiple children per node, and any number of nesting levels.

feeding result of one query into another

I tried to simplify my question to a basic example I wrote down below, the actual problem is much more complex so the below queries might not make much sense but the basic concepts are the same (data from one query as argument to another).
Query 1:
SELECT Ping.ID as PingID, Base.ID as BaseID FROM
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "ping"
ORDER BY l.ID DESC
) Ping
INNER JOIN
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "Base"
ORDER BY l.ID DESC
) Base
ON Base.DateTime < Ping.DateTime
GROUP BY Ping.ID
ORDER BY Ping.ID DESC;
+--------+--------+
| PingID | BaseID |
+--------+--------+
| 11 | 10 |
| 9 | 8 |
| 7 | 6 |
| 5 | 3 |
| 4 | 3 |
+--------+--------+
// from below I need to replace 11 by PingID above and 10 by BaseID above then the results to show up on as third column above (0 if no results, 1 if results)
Query 2:
SELECT * FROM
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "ping" AND l.ID = 11) Ping
INNER JOIN
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "base" AND l.ID = 10) Base
ON Base.Data < Ping.Data;
How can I do this? Again I'm not sure what kind of advice I will receive but please understand that the Query 2 is in reality over 200 lines and I basically can't touch it so I don't have so much flexibility as I'd like and ideally I'd like to get this working all in SQL without having to script this.
CREATE DATABASE lookback;
use lookback;
CREATE TABLE mygroup (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DateTime DateTime
) ENGINE=InnoDB;
CREATE TABLE list (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Type VARCHAR(255),
MyGroup BIGINT NOT NULL,
Data INT NOT NULL
) ENGINE=InnoDB;
CREATE TABLE sublist (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
ParentID BIGINT NOT NULL,
Data INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO mygroup (DateTime) VALUES ("2012-03-09 22:33:19"), ("2012-03-09 22:34:19"), ("2012-03-09 22:35:19"), ("2012-03-09 22:36:19"), ("2012-03-09 22:37:19"), ("2012-03-09 22:38:19"), ("2012-03-09 22:39:19"), ("2012-03-09 22:40:19"), ("2012-03-09 22:41:19"), ("2012-03-09 22:42:19"), ("2012-03-09 22:43:19");
INSERT INTO list (Type, MyGroup, Data) VALUES ("ping", 1, 4), ("base", 2, 2), ("base", 3, 4), ("ping", 4, 7), ("ping", 5, 8), ("base", 6, 7), ("ping", 7, 8), ("base", 8, 3), ("ping", 9, 10), ("base", 10, 2), ("ping", 11, 3);
INSERT INTO sublist (ParentID, Data) VALUES (1, 2), (2, 3), (3, 6), (4, 8), (5, 4), (6, 5), (7, 1), (8, 9), (9, 11), (10, 4), (11, 6);
The simplest way of dealing with this is temporary tables, described here and here. If you create an empty table to store your results (let's call it tbl_temp1) you can to this:
INSERT INTO tbl_temp1 (PingID, BaseID)
SELECT Ping.ID as PingID, Base.ID as BaseID
FROM ...
Then you can query it however you like:
SELECT PingID, BaseID from tbl_temp1 ...
Edited to add:
From the docs for CREATE TEMPORARY TABLE:
You can use the TEMPORARY keyword when creating a table. A TEMPORARY
table is visible only to the current connection, and is dropped
automatically when the connection is closed. This means that two
different connections can use the same temporary table name without
conflicting with each other or with an existing non-TEMPORARY table of
the same name. (The existing table is hidden until the temporary table
is dropped.)
If this were a more flattened query, then there would a straightforward answer.
It is certainly possible to use a derived table as the input to outer queries. A simple example would be:
select
data1,
(select data3 from howdy1 where howdy1.data1 = greetings.data1) data3_derived
from
(select data1 from hello1 where hello1.data2 < 4) as greetings;
where the derived table greetings is used in the inline query. (SQL Fiddle for this simplistic example: http://sqlfiddle.com/#!3/49425/2 )
Following this logic would lead us to assume that you could cast your first query as a derived table of query1 and then recast query2 into the select statement.
For that I constructed the following:
select query1.pingId, query1.baseId,
(SELECT ping.Data pingData FROM
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "ping" AND l.ID = query1.pingId
) Ping
INNER JOIN
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "base" AND l.ID = query1.baseId
) Base
ON Base.Data < Ping.Data)
from
(SELECT Ping.ID as PingID, Base.ID as BaseID FROM
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "ping"
ORDER BY l.ID DESC
) Ping
INNER JOIN
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "Base"
ORDER BY l.ID DESC
) Base
ON Base.DateTime < Ping.DateTime
GROUP BY Ping.ID
) query1
order by pingId desc;
where I have inserted query2 into a select clause from query1 and inserted query1.pingId and query1.baseId in place of 11 and 10, respectively. If 11 and 10 are left in place, this query works (but obviously only generates the same data for each row).
But when this is executed, I'm given an error: Unknown column 'query1.pingId'. Obviously, query1 cannot be seen inside the nested derived tables.
Since, in general, this type of query is possible, when the nesting is only 1 level deep (as per my greeting example at the top), there must be logical restrictions as to why this level of nesting isn't possible. (Time to pull out the database theory book...)
If I were faced with this, I'd rewrite and flatten the queries to get the real data that I wanted. And eliminate a couple things including that really nasty group by that is used in query1 to get the max baseId for a given pingId.
You say that's not possible, due to external constraints. So, this is, ultimately, a non-answer answer. Not very useful, but maybe it'll be worth something.
(SQL Fiddle for all this: http://sqlfiddle.com/#!2/bac74/35 )
If you cannot modify query 2 then there is nothing we can suggest. Here is a combination of your two queries with a reduced level of nesting. I suspect this would be slow with a large dataset -
SELECT tmp1.PingID, tmp1.BaseID, IF(slb.Data, 1, 0) AS third_col
FROM (
SELECT lp.ID AS PingID, MAX(lb.ID) AS BaseID
FROM MyGroup mgp
INNER JOIN MyGroup mgb
ON mgb.DateTime < mgp.DateTime
INNER JOIN list lp
ON mgp.ID = lp.MyGroup
AND lp.Type = 'ping'
INNER JOIN list lb
ON mgb.ID = lb.MyGroup
AND lb.Type = 'base'
GROUP BY lp.ID DESC
) AS tmp1
LEFT JOIN sublist slp
ON tmp1.PingID = slp.ParentID
LEFT JOIN sublist slb
ON tmp1.BaseID = slb.ParentID
AND slb.Data < slp.Data;