PostgreSQL - 根据其他 table 中的值删除 table 中的行
PostgreSQL - Delete rows in table based on values in other table
附加 SQLFiddle 这个问题。
我有两个 table,我想删除 inventoryfruitsforuser
table 和 fruitsForPrize
table
看起来像这样:
select * from fruitsForPrize where "prizeID" = 1
prizeID|fruitID
-------+-------
1 |1
1 |1
1 |1
1 |1
1 |2
1 |2
1 |3
1 |3
1 |4
1 |4
1 |4
1 |5
1 |5
1 |5
select * from inventoryfruitsforuser where "userID" = 1
userID |fruitID
-------+-------
1 |1
1 |1
1 |1
1 |2
1 |2
1 |3
1 |4
1 |5
1 |5
现在userID 1想要获得奖品1所以需要删除prizeID 1的水果inventoryfruitsforuser
所以它会像
(inventoryfruitsforuser where userID = 1) - (fruitsForPrize where prizeID = 1)
我会得到一个看起来像这样的 table:
inventoryfruitsforuser
userID |fruitID
-------+-------
1 |1
1 |3
1 |4
1 |4
1 |5
我做了一个查询,检查我是否有足够的水果来获得奖品
with myfruits as (
select "fruitID", count("fruitID") from inventoryFruitsForUser where "userID" = 1 group by "fruitID" order by "fruitID"
),fruitsRequired as (
select "fruitID", count("fruitID") from fruitsForPrize where "prizeID" = 1 group by "fruitID" order by "fruitID"
),checkShouldDelete as (
select fruitsRequired."fruitID" as "fruitsRequired.fruitID", fruitsRequired.count as "fruitsRequired.count", myfruits."fruitID" as "myfruits.fruitID", myfruits.count as "myfruits.count",
(myfruits.count is not null and myfruits.count >= fruitsRequired.count) or (myfruits.count is null and fruitsRequired.count = 0) as "isEnough"
from fruitsRequired
left join myfruits
on (fruitsRequired."fruitID" = myfruits."fruitID")
) SELECT bool_and("isEnough") "toDelete" FROM checkShouldDelete;
我剩下要做的是使用 fruitsForPrize where "prizeID" = 1
从 inventoryfruitsforuser where "userID" = 1
中删除行,这样我就可以得到上面的第三个 table。
感谢帮助!
同意@gwaigh 的观点,您的模式需要做一些工作。无论如何,您在这里遇到的困难之一是每条记录都没有主键/唯一键,因此删除可能会很困难。这可以很容易地修复(或者您可以按照@gwaigh 描述的方式完成更改模型的工作 - 添加 "number_of_pieces" 列):
ALTER TABLE fruitsForPrize ADD COLUMN id serial;
ALTER TABLE inventoryfruitsforuser ADD COLUMN id serial;
一旦到位,删除就变得简单多了。我相信你是 "removing the pieces from inventory and redeeming them for the prize",对吗?
(我还认为您的一些专栏名称可以使用一些更改。资本在 SQL 土地变得艰难)
我解决的方法是枚举你的水果,然后从库存中删除相应的元素。
我不确定如何更新 fiddle,所以这里是删除查询(显然可以将其添加到您之前的 CTE 字符串中):
with prize_enum as (
-- enumerate fruit for prize
select "prizeID","fruitID"
, id as prizefruitid
, row_number() over (partition by "prizeID","fruitID" order by id) as fruitnum
from fruitsForPrize
)
, inv_enum as (
-- enumerate the fruit in inventory
select "userID","fruitID",id as invid
, row_number() over (partition by "userID","fruitID" order by id) as fruitnum
from inventoryfruitsforuser
)
DELETE FROM inventoryfruitsforuser z
USING
(
-- get the fruits we are redeeming for prize
select *
from prize_enum p
join inv_enum i on i."fruitID" = p."fruitID"
and i.fruitnum = p.fruitnum
) a
where a.invid = z.id
returning *;
select * from inventoryfruitsforuser;
-- returns the table you expect
(注意 - 未解决多个用户或多个奖品的可能性)
编辑: @gwaigh 建议的方法。我相应地更改了 DDL:
CREATE TABLE inventoryfruitsforuser ("userID" INTEGER NOT NULL, "fruitID" INTEGER NOT NULL, number_of_pieces integer NOT NULL DEFAULT 1);
CREATE TABLE fruitsForPrize ("prizeID" INTEGER NOT NULL, "fruitID" INTEGER NOT NULL, number_of_pieces integer NOT NULL DEFAULT 1);
INSERT INTO inventoryfruitsforuser ("userID", "fruitID",number_of_pieces)
VALUES (1,1,4),
(1,2,2),
(1,3,2),
(1,4,3),
(1,5,3);
INSERT INTO fruitsForPrize ("prizeID", "fruitID",number_of_pieces)
VALUES (1,1,3),
(1,2,2),
(1,3,1),
(1,4,1),
(1,5,2);
ALTER TABLE fruitsForPrize ADD COLUMN id serial;
ALTER TABLE inventoryfruitsforuser ADD COLUMN id serial;
在这里,更新查询变得更容易一些。要记住的一件事是我没有解决多个奖品(即它会计算多个奖品的相同水果,因为在您选择奖品之前库存不会减少。这可以通过循环奖品来解决,明确声明奖,或将查询更改为更复杂一点)。不过,希望这能说明这一点:
with check_prize as (
select p.*, u."userID", u.number_of_pieces >= p.number_of_pieces and u.number_of_pieces is not null as enough
from fruitsForPrize p
left join inventoryfruitsforuser u on u."fruitID" = p."fruitID"
) , enough_for_prize as (
select "userID", "prizeID", true = ALL(array_agg(enough)) as enough
from check_prize
group by 1,2
)
update inventoryfruitsforuser u
set number_of_pieces = u.number_of_pieces - p.number_of_pieces
from fruitsForPrize p
join enough_for_prize e on e."prizeID" = p."prizeID" and e.enough
where e."userID" = u."userID" and p."fruitID" = u."fruitID"
returning *
;
select * from inventoryfruitsforuser;
如果您要专注于特定的用户/奖品,我可能会将整个过程包装在一个存储过程中,并将用户/奖品用作参数...类似于:
create or replace function user_redeem_prize(_user integer, _prize integer)
RETURNS BOOLEAN
AS $BODY$
BEGIN
with check_prize as (
select p.*, u."userID", u.number_of_pieces >= p.number_of_pieces and u.number_of_pieces is not null as enough
from fruitsForPrize p
left join inventoryfruitsforuser u on u."fruitID" = p."fruitID"
where p."prizeID" = _prize and u."userID" = _user
) , enough_for_prize as (
select "userID", "prizeID", true = ALL(array_agg(enough)) as enough
from check_prize
group by 1,2
)
update inventoryfruitsforuser u
set number_of_pieces = u.number_of_pieces - p.number_of_pieces
from fruitsForPrize p
join enough_for_prize e on e."prizeID" = p."prizeID" and e.enough
where e."userID" = u."userID" and p."fruitID" = u."fruitID"
and e."userID" = _user
and p."prizeID" = _prize
;
RETURN TRUE;
END;
$BODY$
LANGUAGE PLPGSQL;
select user_redeem_prize(1,1);
附加 SQLFiddle 这个问题。
我有两个 table,我想删除 inventoryfruitsforuser
table 和 fruitsForPrize
table
看起来像这样:
select * from fruitsForPrize where "prizeID" = 1
prizeID|fruitID
-------+-------
1 |1
1 |1
1 |1
1 |1
1 |2
1 |2
1 |3
1 |3
1 |4
1 |4
1 |4
1 |5
1 |5
1 |5
select * from inventoryfruitsforuser where "userID" = 1
userID |fruitID
-------+-------
1 |1
1 |1
1 |1
1 |2
1 |2
1 |3
1 |4
1 |5
1 |5
现在userID 1想要获得奖品1所以需要删除prizeID 1的水果inventoryfruitsforuser
所以它会像
(inventoryfruitsforuser where userID = 1) - (fruitsForPrize where prizeID = 1)
我会得到一个看起来像这样的 table:
inventoryfruitsforuser
userID |fruitID
-------+-------
1 |1
1 |3
1 |4
1 |4
1 |5
我做了一个查询,检查我是否有足够的水果来获得奖品
with myfruits as (
select "fruitID", count("fruitID") from inventoryFruitsForUser where "userID" = 1 group by "fruitID" order by "fruitID"
),fruitsRequired as (
select "fruitID", count("fruitID") from fruitsForPrize where "prizeID" = 1 group by "fruitID" order by "fruitID"
),checkShouldDelete as (
select fruitsRequired."fruitID" as "fruitsRequired.fruitID", fruitsRequired.count as "fruitsRequired.count", myfruits."fruitID" as "myfruits.fruitID", myfruits.count as "myfruits.count",
(myfruits.count is not null and myfruits.count >= fruitsRequired.count) or (myfruits.count is null and fruitsRequired.count = 0) as "isEnough"
from fruitsRequired
left join myfruits
on (fruitsRequired."fruitID" = myfruits."fruitID")
) SELECT bool_and("isEnough") "toDelete" FROM checkShouldDelete;
我剩下要做的是使用 fruitsForPrize where "prizeID" = 1
从 inventoryfruitsforuser where "userID" = 1
中删除行,这样我就可以得到上面的第三个 table。
感谢帮助!
同意@gwaigh 的观点,您的模式需要做一些工作。无论如何,您在这里遇到的困难之一是每条记录都没有主键/唯一键,因此删除可能会很困难。这可以很容易地修复(或者您可以按照@gwaigh 描述的方式完成更改模型的工作 - 添加 "number_of_pieces" 列):
ALTER TABLE fruitsForPrize ADD COLUMN id serial;
ALTER TABLE inventoryfruitsforuser ADD COLUMN id serial;
一旦到位,删除就变得简单多了。我相信你是 "removing the pieces from inventory and redeeming them for the prize",对吗?
(我还认为您的一些专栏名称可以使用一些更改。资本在 SQL 土地变得艰难)
我解决的方法是枚举你的水果,然后从库存中删除相应的元素。
我不确定如何更新 fiddle,所以这里是删除查询(显然可以将其添加到您之前的 CTE 字符串中):
with prize_enum as (
-- enumerate fruit for prize
select "prizeID","fruitID"
, id as prizefruitid
, row_number() over (partition by "prizeID","fruitID" order by id) as fruitnum
from fruitsForPrize
)
, inv_enum as (
-- enumerate the fruit in inventory
select "userID","fruitID",id as invid
, row_number() over (partition by "userID","fruitID" order by id) as fruitnum
from inventoryfruitsforuser
)
DELETE FROM inventoryfruitsforuser z
USING
(
-- get the fruits we are redeeming for prize
select *
from prize_enum p
join inv_enum i on i."fruitID" = p."fruitID"
and i.fruitnum = p.fruitnum
) a
where a.invid = z.id
returning *;
select * from inventoryfruitsforuser;
-- returns the table you expect
(注意 - 未解决多个用户或多个奖品的可能性)
编辑: @gwaigh 建议的方法。我相应地更改了 DDL:
CREATE TABLE inventoryfruitsforuser ("userID" INTEGER NOT NULL, "fruitID" INTEGER NOT NULL, number_of_pieces integer NOT NULL DEFAULT 1);
CREATE TABLE fruitsForPrize ("prizeID" INTEGER NOT NULL, "fruitID" INTEGER NOT NULL, number_of_pieces integer NOT NULL DEFAULT 1);
INSERT INTO inventoryfruitsforuser ("userID", "fruitID",number_of_pieces)
VALUES (1,1,4),
(1,2,2),
(1,3,2),
(1,4,3),
(1,5,3);
INSERT INTO fruitsForPrize ("prizeID", "fruitID",number_of_pieces)
VALUES (1,1,3),
(1,2,2),
(1,3,1),
(1,4,1),
(1,5,2);
ALTER TABLE fruitsForPrize ADD COLUMN id serial;
ALTER TABLE inventoryfruitsforuser ADD COLUMN id serial;
在这里,更新查询变得更容易一些。要记住的一件事是我没有解决多个奖品(即它会计算多个奖品的相同水果,因为在您选择奖品之前库存不会减少。这可以通过循环奖品来解决,明确声明奖,或将查询更改为更复杂一点)。不过,希望这能说明这一点:
with check_prize as (
select p.*, u."userID", u.number_of_pieces >= p.number_of_pieces and u.number_of_pieces is not null as enough
from fruitsForPrize p
left join inventoryfruitsforuser u on u."fruitID" = p."fruitID"
) , enough_for_prize as (
select "userID", "prizeID", true = ALL(array_agg(enough)) as enough
from check_prize
group by 1,2
)
update inventoryfruitsforuser u
set number_of_pieces = u.number_of_pieces - p.number_of_pieces
from fruitsForPrize p
join enough_for_prize e on e."prizeID" = p."prizeID" and e.enough
where e."userID" = u."userID" and p."fruitID" = u."fruitID"
returning *
;
select * from inventoryfruitsforuser;
如果您要专注于特定的用户/奖品,我可能会将整个过程包装在一个存储过程中,并将用户/奖品用作参数...类似于:
create or replace function user_redeem_prize(_user integer, _prize integer)
RETURNS BOOLEAN
AS $BODY$
BEGIN
with check_prize as (
select p.*, u."userID", u.number_of_pieces >= p.number_of_pieces and u.number_of_pieces is not null as enough
from fruitsForPrize p
left join inventoryfruitsforuser u on u."fruitID" = p."fruitID"
where p."prizeID" = _prize and u."userID" = _user
) , enough_for_prize as (
select "userID", "prizeID", true = ALL(array_agg(enough)) as enough
from check_prize
group by 1,2
)
update inventoryfruitsforuser u
set number_of_pieces = u.number_of_pieces - p.number_of_pieces
from fruitsForPrize p
join enough_for_prize e on e."prizeID" = p."prizeID" and e.enough
where e."userID" = u."userID" and p."fruitID" = u."fruitID"
and e."userID" = _user
and p."prizeID" = _prize
;
RETURN TRUE;
END;
$BODY$
LANGUAGE PLPGSQL;
select user_redeem_prize(1,1);