select empty object in jsonb_each in postgres - sql

How to select a empty object Record with jsonb_each function.
because I select some extra field with jsonb_each key and value.
but when a all record jsonb column in empty result is empty.
create table newtest (id SERIAL PRIMARY KEY,foo jsonb);
insert into newtest (foo) values ('{"a":1, "c":2}'), ('{"b":1}'), ('{}');
select * from newtest
ID | foo
-----+----------------
1 | "{"a": 1, "c": 2}"
2 | "{"b": 1}"
3 | "{}"
select id,(jsonb_each(foo)).key AS KEY, (jsonb_each(foo)).value AS value from newtest
Result
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
I need a result like
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
3 |null | null

A lateral left outer join should be the right thing:
SELECT newtest.id, item.key, item.value
FROM newtest
LEFT JOIN LATERAL jsonb_each(newtest.foo) item ON TRUE;
id | key | value
----+-----+-------
1 | a | 1
1 | c | 2
2 | b | 1
3 | |
(4 rows)
This will supply a NULL for missing entries on the right side.

Related

getting concatenated desc from a table of 3 different columns of another table

I am trying to get a single description column from a reference table in PostgreSQL using 3 id columns as a concatenated value.
I have a id Table as below:
+-----+-----+-----+
| id1 | id2 | id3 |
+-----+-----+-----+
| 1 | 2 | 3 |
| 4 | 6 | 5 |
+-----+-----+-----+
and Reference Table;
+----+----------+
| id | desc |
+----+----------+
| 1 | apple |
| 2 | boy |
| 3 | cat |
| 4 | dog |
| 5 | elephant |
| 6 | Flight |
+----+----------+
The Desired expected output is as below
I just have to concat a "/M" in the end additionally.
I don't have to add /M if id2 and id3 both are null
+-----------------------+
| desc |
+-----------------------+
| apple+boy+cat/M |
| dog+Flight+Elephant/M |
+-----------------------+
You can use string_agg() to concatenate all rows with a single expression. Something like:
select (select string_agg(r.descr, '+' order by r.id)||
case when count(r.descr) > 1 then '/M' else '' end
from ref r
where id in (i.id1, i.id2, id3)) as descr
from id_table i;
Online example: https://rextester.com/KVCGLD44632
The above sorts the descriptions by the ID value. If you need to preserve the order of the columns in the "id table", you could use something like this:
select (select string_agg(r.descr, '+' order by t.idx)||
case when count(r.descr) > 1 then '/M' else '' end
from ref r
join (values (i.id1, 1), (i.id2, 2), (i.id3, 3)) as t(id, idx)
on t.id = r.id
) as descr
from id_table i;
Note that desc is a reserved keyword, you should not use it as a column name. That's why I used descr in my example.

how to "deepcopy" rows

My question is similar to this one but more involved. Suppose I have a table A with id idA, and another table B with idB and foreign key idA. I would like to duplicate all entries of A, including corresponding entries in B. For example, if I have the following tables at the start:
A
|---|
|idA|
|---|
| 1 |
| 2 |
| 3 |
|---|
B
|---|---|
|idB|idA|
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
|---|---|
Then the result should be:
A
|---|
|idA|
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
|---|
B
|---|---|
|idB|idA|
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 4 |
| 5 | 4 |
| 6 | 5 |
|---|---|
This is quite tricky. You need to insert the ids into the a -- but then be able to match them back to the existing ids to insert the right values into b.
A generic solution looks like this:
with i as (
insert into a
select . . . -- the other columns you want
from a
order by idA
returning *
),
a_mapping (
select a.idA, i.idA as new_idA
from (select a.*, row_number() over (order by idA) as seqnum
from a
) a join
(select i.*, row_number() over (order by idA) as seqnum
from i
) i
on a.seqnum = i.seqnum
)
insert into b (idA) (
select am.new_idA
from b join
a_mapping am
on b.idA = am.idA;
Note: If you have another unique column or columns in the row, then the mapping is a little easier to generate. Of course, if you are copying all the columns, then nothing else is unique, so you do need the row_number().
Of course, for your very simple example, you don't need a mapping table. You can just use:
with i as (
insert into a
select . . . -- the other columns you want
from a
order by idA
returning *
)
insert into b (idA) (
select i.idA
from i
I have an approach which may be equivalent to what Gordon Linoff suggests, I would be grateful if you could point out any flaws!
Let's set up the tables:
CREATE TABLE A(
idA SERIAL PRIMARY KEY,
txt varchar);
INSERT INTO A(txt)
VALUES ('A1'), ('A2'),('A3');
CREATE TABLE B(
idB SERIAL PRIMARY KEY,
idA int REFERENCES A(idA),
txt varchar);
INSERT INTO B(idA, txt)
VALUES (1, 'A1.B1'), (1, 'A1.B2'), (2, 'A2.B1');
so the initial data looks as follows:
SELECT * FROM (A LEFT JOIN B ON A.idA=B.idA) ORDER BY A.idA, B.idB;
ida | txt | idb | ida | txt
-----+-----+-----+-----+-------
1 | A1 | 1 | 1 | A1.B1
1 | A1 | 2 | 1 | A1.B2
2 | A2 | 3 | 2 | A2.B1
3 | A3 | | |
(4 rows)
Now, we can use the NEXTVAL function to generate the mappings directly:
CREATE TEMP TABLE tmp_A_new AS (
SELECT *, NEXTVAL('A_idA_seq') as newidA
FROM A ORDER BY idA -- order probably not needed
);
INSERT INTO A(idA, txt) (SELECT newidA, txt FROM tmp_A_new);
CREATE TEMP TABLE tmp_B_new AS (
SELECT B.idB, newidA, B.txt, NEXTVAL('B_idB_seq') as newidB
FROM B, tmp_A_new WHERE B.idA=tmp_A_new.idA ORDER BY idB
);
INSERT INTO B(idB, idA, txt) (SELECT newidB, newidA, txt FROM tmp_B_new);
The results look correct:
SELECT * FROM (A LEFT JOIN B ON A.idA=B.idA) ORDER BY A.idA, B.idB;
ida | txt | idb | ida | txt
-----+-----+-----+-----+-------
1 | A1 | 1 | 1 | A1.B1
1 | A1 | 2 | 1 | A1.B2
2 | A2 | 3 | 2 | A2.B1
3 | A3 | | |
4 | A1 | 4 | 4 | A1.B1
4 | A1 | 5 | 4 | A1.B2
5 | A2 | 6 | 5 | A2.B1
6 | A3 | | |
(8 rows)
Note that this could be continued further down to C, D, etc.
I would be glad for any comments :)

Check if data for update is same as before in SQL Server

I have a table Table1:
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 0 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
I have a result set in temp table Temp1 with same structure and have update Table1 only if for refID answer and points have changed, otherwise there should be deletion for this record.
I tried:
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid
where table1.answer != temp1.answer or table1.points != temp1.points
Here is a fiddle http://sqlfiddle.com/#!18/60424/1/1
However this is not working and don't know how to add the delete condition.
Desired result should be if tables not the same ex. (second row answer 2 points3):
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 3 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
if they are same all records with refID are deleted.
Explanation when temp1 has this data
ID | RefID | Answer | Points |
----+-------+---------+--------+
12 | 1 | 1 | 5 |
13 | 1 | 2 | 0 |
14 | 1 | 3 | 3 |
EDIT: adding another id column questionid solved the update by adding this also in join.
Table structure is now:
ID | RefID | Qid |Answer | Points |
----+-------+------+-------+--------+
1 | 1 | 10 | 1 | 5 |
2 | 1 | 11 | 2 | 0 |
3 | 1 | 12 | 3 | 3 |
4 | 2 | 11 | 1 | 4 |
SQL for update is: (fiddle http://sqlfiddle.com/#!18/00f87/1/1) :
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid and table1.qid = temp1.qid
where table1.answer != temp1.answer or table1.points != temp1.points;
SELECT ID, refid, answer, points
FROM table1
How can I make the deletion case, if data is same ?
You need to add one more condition in the join to exactly match the column.Try this one.
update table1
set table1.answer=temp1.answer,
table1.points=temp1.points
from
table1 join temp1 on table1.refid=temp1.refid and **table1.ID=temp1.ID**
where table1.answer!=temp1.answer or table1.points!=temp1.points
I would first do the delete, and only then the update.
The reason for this is that once you've deleted all the records where the three columns are the same, your update statement becomes simpler - you only need the join, and no where clause:
DELETE t1
FROM table1 AS t1
JOIN temp1 ON t1.refid = temp1.refid
AND t1.qid = temp1.qid
AND t1.answer=temp1.answer
AND t1.points=temp1.points
UPDATE t1
SET answer = temp1.answer,
points = temp1.points
FROM table1 AS t1
JOIN temp1 ON t1.refid=temp1.refid
AND t1.qid = temp1.qid
I think from what i understood that you need to use id instead of refid or both if id is unique

find set of row, duplicate list, before insert

I have table (it's a list of struct with 4 integers, first id is list id)
id | idL | idA(null) | idB(null) | idC
1 | 1 | 2 | null | 1
2 | 1 | 4 | null | 1
3 | 1 | null | 1 | 1
4 | 2 | 2 | null | 1
5 | 2 | 4 | null | 1
6 | 3 | 6 | null | 1
7 | 3 | null | 4 | 1
Now I need to insert 4th list to this table
idA | idB | idC
2 | null | 1
4 | null | 1
null | 1 | 1
but, it's already exist (list id = 1)
idA | idB | idC
2 | null | 1
4 | null | 1
alse exist (idL = 2)
idA | idB | idC
2 | null | 1
4 | null | 1
null | 7 | 1
does not exist.
How to find duplicate before insert it to table
It appears to be just a matter of insert from (select not in).
Try this example:
SQLFiddle
Disclaimer: In the example data you provided rows 2 and 4 got a identical idA,idB,idC set.
If that columns cannot form a unique and you already got that tuple in copy table and you need one row in copy table for each row in original table that ill be a lot harder because for a such row in copy there's no way to tell the row in original it's related.
if values is in table temp and you know the list id.
you can use "Except"
eg:
insert into list (idL, idA, idB, idC)
select #list_id, t.idA, t.idB, t.idC
from
(
select idA, idB, idC
from #new_values
except
select idA, idB, idC
from list
) t

SQL Server : getting all leafs from tree

I have datatable which contains:
|Parent Key| Component Key|
I need to get all leafs(parent key) for a chosen component key.
For example:
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
| 6 | 4 |
| 7 | 6 |
| 8 | 11 |
| 9 | 4 |
| 10 | 12 |
for component key = 4 I want to receive
| 1 |
| 7 |
| 9 |
If selected component key is already a leaf (there is no row where component key == selected component key) I want to return only the selected component key.
Can it be done by select only ?
How to do it in the most efficient way ?
Try something like that:
DECLARE #selected INT = 4;
WITH cte
AS
(
SELECT ParentKey, ComponentKey
FROM Table1
WHERE ComponentKey = #selected
UNION ALL
SELECT Table1.ParentKey, Table1.ComponentKey
FROM Table1
INNER JOIN cte ON Table1.ComponentKey = cte.ParentKey
)
SELECT ParentKey FROM cte
WHERE
ParentKey NOT IN (SELECT ComponentKey FROM Table1)
UNION
SELECT ParentKey FROM Table1
WHERE
ParentKey = #selected
AND ParentKey NOT IN (SELECT ComponentKey FROM Table1)
Replace Table1 with your table name.