确保在使用 CTE 插入之前删除行

问题描述 投票:0回答:1

我正在使用 CTE 检查数据库行,通过过滤删除行,最后将行插入数据库。

下面是插入行的 go 函数:

func (vm VoteModel) Insert(vote *Vote) error {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    // begin atomic transaction
    tx, err := vm.DB.BeginTx(ctx, nil)
    if err != nil {
        return err
    }

    // Use a CTE to check if the poll_choice_id belongs to the poll_id and to delete any existing vote
    insertVoteQuery := `
        WITH valid_choice AS (
            SELECT 1
            FROM poll_choices
            WHERE id = $1 AND poll_id = $2
            LIMIT 1
        ),
        delete_existing_vote AS (
            DELETE FROM votes
            WHERE user_id = $3 AND poll_id = $2
            RETURNING 1
        )
        INSERT INTO votes (user_id, poll_id, poll_choice_id)
        SELECT $3, $2, $1
        FROM valid_choice
        RETURNING id, added_at, version
    `
    insertArgs := []interface{}{vote.PollChoiceID, vote.PollID, vote.UserID}

    err = tx.QueryRowContext(ctx, insertVoteQuery, insertArgs...).Scan(
        &vote.ID,
        &vote.AddedAt,
        &vote.Version,
    )
    if err != nil {
        if rbErr := tx.Rollback(); rbErr != nil {
            return rbErr
        }
        switch {
        case errors.Is(err, sql.ErrNoRows):
            return ErrRecordNotFound
        default:
            return err
        }
    }

    // commit
    err = tx.Commit()
    if err != nil {
        if rbErr := tx.Rollback(); rbErr != nil {
            return rbErr
        }
        return err
    }

    return nil
}

假设我有这些行的投票表:

 id | user_id | poll_id | poll_choice_id |         added_at          | version 
----+---------+---------+----------------+---------------------------+---------
 10 |      11 |      12 |              5 | 2024-05-21 13:39:58+05:30 |       1
 16 |       3 |      18 |             34 | 2024-05-22 21:57:44+05:30 |       1 <-- existing user vote
 24 |       9 |      18 |             35 | 2024-05-24 10:54:47+05:30 |       1
(3 rows)

当我尝试对一项民意调查进行投票时,我已经使用另一个民意调查选项进行了投票,例如,我的用户 ID 是

3
:

{
    "poll_id": 18,
    "poll_choice_id": 31
}

我收到错误:

votes_user_id_poll_id_key :“违反唯一约束”

但我想我删除了所有用户并使用模型 go

Insert()
函数在这一行中进行投票:

delete_existing_vote AS(从投票中删除 user_id = $3 且 poll_id = $2 返回 1 )

我错过了什么?我可以分离查询事务并进行额外的数据库调用,但我想最大程度地减少数据库调用的数量。

database postgresql go
1个回答
0
投票

答案来自手册

WITH中的子语句是并发执行的 以及主要查询。因此,在使用数据修改时 WITH 中的语句,指定的实际更新顺序 发生的事情是不可预测的 [...] 尝试在单个语句中更新同一行两次是不行的 支持的。仅发生其中一项修改,但并非如此 很容易(有时不可能)可靠地预测是哪一个。这 也适用于删除已在同一行中更新的行 声明:仅执行更新。因此你应该 通常避免尝试在单个行中修改同一行两次 陈述。特别是避免编写可能会导致以下情况的WITH子语句: 影响主语句或同级语句更改的相同行 子陈述。此类声明的影响不会 可以预见的。

© www.soinside.com 2019 - 2024. All rights reserved.