Update query on jsonb column only inserts first value - postgresql

Why is it, that if you use jsonb_set as new values in an UPDATE query, that it only updates the first row of the result set?
See this example here: http://sqlfiddle.com/#!17/0bdd8/5
There are two entries in reactions for the same post, yet when I try to assign a random value keyed to the username, it only inserts it for the first value not for the second one:
UPDATE posts
SET a_to_b = jsonb_set(posts.a_to_b, array[username::text], to_jsonb(random()))
FROM reactions
WHERE posts.id = reactions.post_id;

There are more than one row in the FROM clause for a single row to be modified. The documentation explains it clearly:
When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.
You can execute a single update by aggregating the expected value in a subquery. Use the aggregate function jsonb_object_agg():
update posts p
set a_to_b = agg
from (
select p.id, jsonb_object_agg(username, random()) as agg
from posts p
join reactions r on p.id = r.post_id
group by p.id
) s
where s.id = p.id;
SqlFiddle.
Alternatively, you can use an anonymous code block to repeatedly update a single row, e.g.:
do $$
declare rec record;
begin
for rec in
select *
from posts p
join reactions r on p.id = r.post_id
loop
update posts
set a_to_b = jsonb_set(a_to_b, array[rec.username], to_jsonb(random()))
where posts.id = rec.post_id;
end loop;
end $$;
The second solution may turn out to be suboptimal, especially for a large number of aggregated values.

Related

Return closest timestamp from Table B based on timestamp from Table A with matching Product IDs

Goal: Create a query to pull the closest cycle count event (Table C) for a product ID based on the inventory adjustments results sourced from another table (Table A).
All records from Table A will be used, but is not guaranteed to have a match in Table C.
The ID column will be present in both tables, but is not unique in either, so that pair of IDs and Timestamps together are needed for each table.
Current simplified SQL
SELECT
A.WHENOCCURRED,
A.LPID,
A.ITEM,
A.ADJQTY,
C.WHENOCCURRED,
C.LPID,
C.LOCATION,
C.ITEM,
C.QUANTITY,
C.ENTQUANTITY
FROM
A
LEFT JOIN
C
ON A.LPID = C.LPID
WHERE
A.facility = 'FACID'
AND A.WHENOCCURRED > '23-DEC-22'
AND A.ADJREASONABBREV = 'CYCLE COUNTS'
ORDER BY A.WHENOCCURRED DESC
;
This is currently pulling the first hit on C.WHENOCCURRED on the LPID matches. Want to see if there is a simpler JOIN solution before going in a direction that creates 2 temp tables based on WHENOCCURRED.
I have a functioning INDEX(MATCH(MIN()) solution in Excel but that requires exporting a couple system reports first and is extremely slow with X,XXX row tables.
If you are using Oracle 12 or later, you can use a LATERAL join and FETCH FIRST ROW ONLY:
SELECT A.WHENOCCURRED,
A.LPID,
A.ITEM,
A.ADJQTY,
C.WHENOCCURRED,
C.LPID,
C.LOCATION,
C.ITEM,
C.QUANTITY,
C.ENTQUANTITY
FROM A
LEFT OUTER JOIN LATERAL (
SELECT *
FROM C
WHERE A.LPID = C.LPID
AND A.whenoccurred <= c.whenoccurred
ORDER BY c.whenoccurred
FETCH FIRST ROW ONLY
) C
ON (1 = 1) -- The join condition is inside the lateral join
WHERE A.facility = 'FACID'
AND A.WHENOCCURRED > DATE '2022-12-23'
AND A.ADJREASONABBREV = 'CYCLE COUNTS'
ORDER BY A.WHENOCCURRED DESC;

Single Value Expression in When Then Aggregate Function TSQL

I am trying to map a certain value of a column based on its count on another table. If the count of [Location] i.e a column of IMPORT.DATA_SCRAP table in each row. For now for location static value i.e Utah and Kathmandu is supplied for test purpose only is equal to 1, then only i need to get the result in the select statement i.e only single value expression must be returned but here n rows of table with value is returned.
For. eg. In the below query,total rows of IMPORT.DATA_SCRAP gets returned, i only need the single first row value in my case.
I came to know whether cursor or CTE will acheive my result but i am unable to figure it out.
Here,
select
case
when
((SELECT COUNT(stateName) FROM Location.tblState where stateName = 'Utah')=1)
then (select stateName, CountryName from Location.tblState where stateName= 'Utah')
end as nameof
from IMPORT.DATA_SCRAP
The relation between country, state, city is as below:
select
case
when
((SELECT COUNT(cityName) FROM Location.tblCity where cityName = 'Kathmandu')=1)
then (select ct.countryName from Location.tblCity c
inner join Location.tblState s
on c.stateID = s.StateID
inner join Location.tblCountry ct
on ct.countryId = s.CountryId
where c.cityName = 'Kathmandu'
)
end as nameof
from IMPORT.DATA_SCRAP
How can i return only a single value expresion despite of multiple nmax rows of IMPORT.DATA_SCRAP row in the result.
If i comment out the -- from IMPORT.DATA_SCRAP in the above query i would get the desired single result expression in my case, but unable how can i acheive it in other ways or suggest me the appropriate way to do these types of situation.

comprare aggregate sum function to number in postgres

I have the next query which does not work:
UPDATE item
SET popularity= (CASE
WHEN (select SUM(io.quantity) from item i NATURAL JOIN itemorder io GROUP BY io.item_id) > 3 THEN TRUE
ELSE FALSE
END);
Here I want to compare each line of inner SELECT SUM value with 3 and update popularity. But SQL gives error:
ERROR: more than one row returned by a subquery used as an expression
I understand that inner SELECT returns many values, but can smb help me in how to compare each line. In other words make loop.
When using a subquery you need to get a single row back, so you're effectively doing a query for each record in the item table.
UPDATE item i
SET popularity = (SELECT SUM(io.quantity) FROM itemorder io
WHERE io.item_id = i.item_id) > 3;
An alternative (which is a postgresql extension) is to use a derived table in a FROM clause.
UPDATE item i2
SET popularity = x.orders > 3
FROM (select i.item_id, SUM(io.quantity) as orders
from item i NATURAL JOIN itemorder io GROUP BY io.item_id)
as x(item_id,orders)
WHERE i2.item_id = x.item_id
Here you're doing a single group clause as you had, and we're joining the table to be updated with the results of the group.

Four triggers: It does not work

Hello everybody,
excuse me for my bad english
It's been more than 4 days I am trying to solve my problem:
each trigger works well but when I combine them there is an error:
the subquery returns more than 1 value.
I tried to follow all the tips in this website and others, I could not make it works, though.
the concerned tables are: PIECES, COMPOSITIONSGAMMES, nomenclatures and SITUATIONS.
What I want the triggers to do is :
When the user inserts a new row on "SITUATIONS" and if 'nomstrategie'= "DST" (It's a name of a strategy but this detail does not really matter, I mean for people who will help me), I need other rows to be inserted with the same reference (referencepiece), the same strategy(nomstrategie). Only 'ancienposte' and 'nouveauposte' have to change. Indeed, the first one's value(s) has to be all 'Numeroposte' from the table "Compositionsgammes". The second one's value has to be '???'.
I need, when I insert a new row and 'nomstrategie'='DST', other rows to be inserted with all 'piecesfilles' in the table "Nomenclatures"
of the reference 'referencepiece' in the row inserted by the user. And in 'ancienposte', there should be 'numeroposte' in the table "compositionsgammes".
I need, when the user inserts a new row and 'nomstrategie'= 'delestage, another row to be inserted as below, for example :
inserted row: Ref A ancienposte : P01 Nouveauposte :P02 Nomstrategie :Delestage…………
row to be inserted: Ref A ancienposte : P02 Nouveauposte :NULL Nomstrategie :Delestage…………
I need, for every row in the table "situations", calculate a value called 'charge' in the table situations charge=(TS/Taillelot)+Tu
here are the triggers I've done:
create trigger [dbo].[ALLDST]
ON [dbo].[SITUATIONS]
AFTER INSERT /*pas d'update*/
as
begin
set nocount on
insert into SITUATIONS(ReferencePiece,nomstrategie,AncienPoste,nouveauposte,DateStrategie)
select distinct i.referencepiece, i.nomstrategie,COMPOSITIONSGAMMES.NumeroPoste,'???',i.DateStrategie
from inserted i, PIECES, compositionsgammes, SITUATIONS s
where i.ReferencePiece is not null
and i.NomStrategie='DST'
and i.ReferencePiece=pieces.ReferencePiece and pieces.CodeGamme=COMPOSITIONSGAMMES.CodeGamme
and i.AncienPoste<>COMPOSITIONSGAMMES.NumeroPoste
and i.DateStrategie=s.DateStrategie
end
create trigger [dbo].[Calcul_Charge]
on [charges].[dbo].[SITUATIONS]
after insert
as
begin
update situations
set charge= (select (cg.TS/pieces.TailleLot)+cg.tu from situations s
inner join COMPOSITIONSGAMMES cg on cg.NumeroPoste=SITUATIONS.AncienPoste
inner join pieces on SITUATIONS.ReferencePiece=pieces.ReferencePiece
inner join inserted i on s.DateStrategie=i.DateStrategie
where cg.CodeGamme=pieces.CodeGamme and NumeroPoste=situations.AncienPoste
)
end
create trigger [dbo].[Duplicate_SITUATIONS]
ON [dbo].[SITUATIONS]
AFTER INSERT
as
begin
set nocount on
declare #ref varchar(50)
declare #strategie varchar(50)
declare #ancienposte varchar(50)
declare #datestrategie date
declare #pourcentage decimal(18,3)
declare #coeff decimal(18,3)
declare #charge decimal(18,3)
/*while (select referencepiece from situations where ReferencePiece) is not null*/
select #ref=referencepiece, #strategie=nomstrategie,#ancienposte=NouveauPoste,
#datestrategie=datestrategie, #pourcentage=PourcentageStrategie,#coeff=coeffameliorationposte,#charge=charge
from inserted,POSTESDECHARGE
where ReferencePiece is not null
and POSTESDECHARGE.NumeroPoste = inserted.AncienPoste
if #strategie = 'delestage' and #ancienposte is not null
/*if GETDATE()>= (select datestrategie from SITUATIONS)*/
begin
insert into SITUATIONS(ReferencePiece, nomstrategie,AncienPoste,DateStrategie,
StatutStrategie,DateModification,PourcentageStrategie,charge)
values
(#ref, #strategie, #ancienposte, #datestrategie,1,getdate(),#pourcentage,#charge*#coeff)
end
end
I'm mostly familiar with T-SQL (MS SQL), not sure if this will work for your case.. but I usually avoid updates using a sub query and rewrite your update:
update situations
set charge= (select (cg.TS/pieces.TailleLot)+cg.tu from situations s
inner join COMPOSITIONSGAMMES cg on cg.NumeroPoste=SITUATIONS.AncienPoste
inner join pieces on SITUATIONS.ReferencePiece=pieces.ReferencePiece
inner join inserted i on s.DateStrategie=i.DateStrategie
where cg.CodeGamme=pieces.CodeGamme and NumeroPoste=situations.AncienPoste
)
as follows
update s set
charge= (cg.TS/pieces.TailleLot)+cg.tu
from situations s
inner join COMPOSITIONSGAMMES cg on cg.NumeroPoste=SITUATIONS.AncienPoste
inner join pieces on SITUATIONS.ReferencePiece=pieces.ReferencePiece
inner join inserted i on s.DateStrategie=i.DateStrategie
where cg.CodeGamme=pieces.CodeGamme and NumeroPoste=situations.AncienPoste

Stored procedure is not concurrent

I want this stored procedure to work concurrently, but as I see in PgAdmin it doesn't. One process is blocked by another one (waits him to finish). Seems to me, update query has no problems with being running concurrently. So this is all about INSERT, am I right? I tried to set ROW SHARE EXCLUSIVE LOCK on update query, but I've reached deadlock.
UPDATE product p SET category_id = m.internal_category_id, category_from=m.cat_prior FROM main_table m
WHERE p.mpn=m.mpn AND
p.vendor_id = m.internal_vendor_id AND
m.new_prod=false and
m.internal_category_id IS NOT NULL AND
(category_from is null or category_from<=m.cat_prior);
INSERT INTO product(vendor_id, category_id, mpn, name, category_from)
SELECT DISTINCT ON(m.mpn, m.internal_vendor_id) m.internal_vendor_id, m.internal_category_id, m.mpn, m.prod_name, m.cat_prior
FROM main_table m
LEFT OUTER JOIN product ON product.mpn=m.mpn AND product.vendor_id=m.internal_vendor_id
WHERE m.new_prod='1' AND m.internal_vendor_id IS NOT NULL
AND product.mpn IS NULL;
UPDATE main_table m SET internal_product_id = p.id
FROM product p
WHERE p.mpn=m.mpn AND p.vendor_id = m.internal_vendor_id ;
UPDATE vendor SET has_products = true FROM (SELECT DISTINCT v.id as id FROM vendor v INNER JOIN product p ON p.vendor_id = v.id) AS r WHERE vendor.id = r.id;
UPDATE vendor SET has_products = false FROM (SELECT DISTINCT v.id as id FROM vendor v LEFT JOIN product p ON p.vendor_id = v.id WHERE p.vendor_id IS NULL) AS r WHERE vendor.id = r.id;
Seems to me, update query has no problems with being running
concurrently. So this is all about INSERT, am I right?
Both can be problematic but UPDATE tends to be more problematic with regard to interlocking.
When an UPDATE modifies a row and another UPDATE tries to modify the same row, the second is put to wait until the first transaction commits or rolls back.
On the other hand, when two INSERTs hit the same table, the second one is made to wait only if there's a unique index on the table and both statements are trying to insert the same indexed value. Which is unusual because that would lead to an index violation error unless the first transaction aborts.
Just by looking at a piece of code, it's hard to know whether concurrent calls will hit the same rows or not, it depends on the data. However in your case it's a bit suspicious that your first UPDATE doesn't seem to take any parameter.