PostgreSQL - 根据其他表中的值删除表中的行

Asa*_*kov 5 postgresql sql-delete postgresql-9.6

SQLFiddle附加到这个问题。

我有两个表,我想删除inventoryfruitsforuser带有fruitsForPrize表的表

它看起来像这样:

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
Run Code Online (Sandbox Code Playgroud)

现在,用户 ID 1 想要获得奖品 1,因此需要从中删除奖品 ID 1 的水果 inventoryfruitsforuser

所以它会像

(inventoryfruitsforuser where userID = 1) - (fruitsForPrize where prizeID = 1)

有了这个,我会得到一个看起来像这样的表:

inventoryfruitsforuser      
userID |fruitID
-------+-------
1      |1
1      |3
1      |4
1      |4
1      |5
Run Code Online (Sandbox Code Playgroud)

我做了一个查询,检查我是否有足够的水果来获得奖品

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;
Run Code Online (Sandbox Code Playgroud)

我剩下要做的就是从删除行inventoryfruitsforuser where "userID" = 1fruitsForPrize where "prizeID" = 1那么我会得到第三个表了上面。

感谢您的帮助!

col*_*ole 5

同意@gwaigh,您的架构可以使用一些工作。在任何情况下,您在这里遇到的困难之一是每个记录上没有主键/唯一键,因此删除可能会很困难。这可以很容易地修复(或者您可以按照@gwaigh 描述的方式完成更改模型的工作 - 添加一个“number_of_pieces”列):

ALTER TABLE fruitsForPrize ADD COLUMN id serial;
ALTER TABLE inventoryfruitsforuser ADD COLUMN id serial;
Run Code Online (Sandbox Code Playgroud)

一旦到位,删除变得更加简单。我相信你是“从库存中取出碎片并兑换它们以获得奖品”,对吗?

(我也认为你的一些列名可以使用一些更改。大写在 SQL 领域变得艰难)

我解决的方法是枚举你的水果,然后从库存中删除相应的元素。

我不确定如何更新小提琴,所以这里是删除查询(显然可以添加到您之前的 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
Run Code Online (Sandbox Code Playgroud)

(注意 - 不解决多个用户或多个奖品的可能性)

编辑: @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;
Run Code Online (Sandbox Code Playgroud)

更新查询在这里变得更容易一些。要记住的一件事是我没有解决多个奖品(即它会为多个奖品计算相同的水果,因为在您选择奖品之前库存不会减少。这可以通过循环奖品来解决,明确声明奖,或将查询更改为更复杂一些)。不过,希望这说明了这一点:

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;
Run Code Online (Sandbox Code Playgroud)

如果您要关注特定的用户/奖品,我可能会将整个内容包装在一个存储过程中,并使用用户/奖品作为参数......类似于:

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);
Run Code Online (Sandbox Code Playgroud)