【问题标题】:Delete last N entries from the database by X field using trigger使用触发器通过 X 字段从数据库中删除最后 N 个条目
【发布时间】:2020-02-07 15:44:40
【问题描述】:

我在 SQL Server 中有一个表,用于保存用户的密码历史记录,以防止用户重复使用以前使用的密码。

CREATE TABLE user_password_histories 
(
    id BIGINT IDENTITY CONSTRAINT user_password_histories_pk PRIMARY KEY NONCLUSTERED
    ,password VARCHAR(128) NOT NULL
    ,user_id BIGINT NOT NULL
    ,created_at DATETIME DEFAULT GETUTCDATE() NOT NULL
    ,updated_at DATETIME
)

但是,我只想为用户维护一个包含 10 个密码历史记录的列表。因此,我正在考虑在插入期间使用触发器为每个用户保留 10 个历史记录。

我想出的部分解决方案是这样的

CREATE TRIGGER trg_user_password_histories  
ON user_password_histories
AFTER INSERT
AS
    DELETE FROM user_password_histories
    WHERE user_password_histories.user_id = Inserted.user_id // This will not work
      AND user_password_histories.id NOT IN (SELECT t2.id
                                             FROM user_password_histories t2
                                             WHERE t2.id = user_password_histories.id
                                             ORDER BY t2.created_at DESC 
                                                 OFFSET 10 ROWS)
GO

但这里的问题是Inserted 可以有多个条目。因此,我不确定如何解决此问题或解决此问题的最佳方法。

【问题讨论】:

  • 老实说,我闻到了更大的问题;看起来您将纯文本密码存储在数据库中,而不是哈希和盐。我可能是错的,因为您很可能会重复使用 [User] 表中的盐,而 password 是加盐的;但如果它被散列,我可以预期该列是nvarchar 而不是varchar。在我看来,这是一个更大的问题,你应该首先解决这个问题。
  • 只是想知道是什么让您认为我在这里使用纯密码只是通过查看数据库模式?我没有存储普通密码..它们都是加密的。
  • 当您发布该评论时,我刚刚将我的推理添加到我的原始评论中。但是,就像我说的,它更像是一种“气味”。我可能错了,但是表格的 DDL 让它看起来确实有这个缺陷。
  • 触发器永远不应该是第一选择; INSERT 触发 DELETE 似乎特别邪恶。除非您确实需要以某种方式为多个用户大量导入密码(?),否则为这个逻辑设置一个存储过程会更易于维护。
  • 尽管我完全同意这看起来像纯文本密码并且触发器是错误的工作,因为失败的原因是因为在您的删除语句中您没有插入的表查询。您需要将 where 子句更改为连接,以便插入的表成为查询的一部分。如果是我,我会让这个删除逻辑非常不同。这将是我插入存储过程的一部分。

标签: sql sql-server database database-trigger


【解决方案1】:

好吧,我想我终于找到了解决方案。我用批量插入和单次插入对此进行了测试,它可以工作!!!我不得不使用cursor 来解决这个问题。我在这里发布我的解决方案,以便其他人可以从中受益。

CREATE TRIGGER trg_user_password_histories_insert ON user_password_histories
    AFTER INSERT
    AS
    DELETE uph FROM user_password_histories uph
    WHERE uph.user_id IN (SELECT user_id FROM Inserted)
    AND uph.id NOT IN (
        SELECT id FROM (
            SELECT user_password_histories.id, ROW_NUMBER() OVER(PARTITION BY user_password_histories.user_id ORDER BY user_password_histories.created_at DESC) AS rn
            FROM user_password_histories
            WHERE user_password_histories.user_id IN (SELECT user_id FROM Inserted)
        ) t
        WHERE t.rn <= 10
    )
go

此解决方案可能不适用于所有场景,但在这种情况下可以完美运行。

【讨论】:

  • 只有一个提示:创建一个子查询,为每个用户选择最后 10 个密码。然后与主表进行外连接,删除所有不在子查询中的记录。
  • 顺便说一句:我怀疑保留密码是“大量数据”,所以我会保留它们。
  • @SQLPolice 这是个好建议,我没想过,谢谢。我做了修改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 2014-09-11
  • 1970-01-01
  • 2014-05-15
相关资源
最近更新 更多