如何在 Diesel 中对 Postgres 数据库执行删除子查询?

Posted

技术标签:

【中文标题】如何在 Diesel 中对 Postgres 数据库执行删除子查询?【英文标题】:How do I perform a delete with sub-query in Diesel against a Postgres database? 【发布时间】:2020-07-05 05:50:12 【问题描述】:

我在 Postgres 数据库中有以下架构:

Table A 
  ID
  Name


Table B 
  ID FOREIGN KEY (A.ID)

我正在尝试在 Diesel 中编写以下查询:

DELETE FROM B
WHERE B.ID in (SELECT ID from A WHERE A.Name = $VAR)

$VAR 是我的应用程序传递的变量。

我的第一次尝试是这样写的:

fn deleteB(conn: &PgConnection, n: &str) 
    use schema::A::dsl::*;
    use schema::A;
    use schema::B;

    let res = A::table
        .filter(Name.eq(n))
        .select(ID);
        .load(conn)
        .unwrap();
    assert!(res.len() < 2);
    let b_id: i32 = *res.iter().nth(1).unwrap_or(&0);

    let _rows = diesel::delete(
        B::table
        .filter(ID.eq(n_id))
    )
    .execute(conn)
    .unwrap();

这可以编译但它不起作用:ID 的SELECT 语句始终返回 0。它与 A 中的任何插入记录都不匹配,即使我手动检查它们是否存在。我确定比赛的进行方式有误(&amp;str vs &amp;String 也许?),但我决定尝试不同的解决方案,因为我不喜欢这个解决方案,因为它必须执行针对数据库的两个单独的语句。

我的第二次尝试是这样的:

fn deleteB(conn: &PgConnection, n: &str) 
    use schema::A::dsl::*;
    use schema::A;
    use schema::B;

    let source = B::table.inner_join(A::table)
            .filter(Name.eq(n));
    let s = delete(source)
            .execute(conn)
            .unwrap();

这对我来说看起来更像 Diesel 惯用的(剧透警告,我几乎不知道框架)但果然,它无法编译:

    |
410 |     let s = delete(source)
    |                    ^^^^^^ the trait `diesel::query_builder::IntoUpdateTarget` is not implemented for `diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<mobility2::schema::B::table...` (very long error)

当我意识到这似乎是一个微不足道的操作(删除本身)时,我正在研究上述特征,我最好寻求帮助。

如何正确编写惯用的 Diesel 代码来执行我要执行的删除语句?

【问题讨论】:

很难回答您的问题,因为它不包含minimal reproducible example。我们无法分辨代码中存在哪些 crate(及其版本)、类型、特征、字段等。如果您尝试在一个全新的 Cargo 项目中重现您的错误,我们会更轻松地为您提供帮助,然后 edit 您的问题将包含附加信息。您可以使用 Rust 和 Diesel 特定的 MRE 提示来减少您在此处发布的原始代码。谢谢! 顺便说一句,惯用的 Rust 使用 snake_case 表示变量、方法、宏、字段和模块; UpperCamelCase 用于类型和枚举变体; SCREAMING_SNAKE_CASE 用于静态和常量。 我从根本上误解了一些知识渊博的人对框架的理解——这是可能的,但是你想在这个知识渊博的人试图做出决定时浪费他们的时间吗?你究竟处于什么状态,你需要什么?相反,我们可以提前提供完整的上下文。 SO 也是关于长期存在的问题,一个格式良好的问题比最初的提问者更有价值。 指出而不复制 - 也许,但许多答案(至少是好的答案)在提交之前测试他们的更改。减少这个障碍可以更容易地做出答案。 另见Deleting from an associated table with a subquery using Diesel from a postgres database 作为一个具体的例子,我在 12 小时前有相同的解决方案as the posted answer。但是,我没有正确定义您的mod schema,因此出现了不相关的错误。如果您花时间生成minimal reproducible example,您本可以更快地获得答案 【参考方案1】:

由于 Diesel 语句从字面上映射到 SQL,因此您编写的查询将导致:

DELETE FROM TABLE B INNER JOIN TABLE A ON … WHERE name = $1

这不是有效的 SQL,因此会导致编译时错误。

要获得您要编写的查询,您需要执行以下操作:

#[macro_use]
extern crate diesel;

use diesel::delete;
use diesel::prelude::*;

mod schema 
    table! 
        A(ID) 
            ID -> Integer,
            name -> Text,
        
    

    table! 
        B(ID) 
            ID -> Integer,
        
    

    allow_tables_to_appear_in_same_query!(A, B);


fn deleteB(conn: &PgConnection, n: &str) 
    use schema::A;
    use schema::B;

    let s = delete(B::table)
        .filter(B::ID.eq_any(A::table.filter(A::name.eq(n)).select(A::ID)))
        .execute(conn)
        .unwrap();

【讨论】:

我已经修复了代码,所以它现在可以编译了(假设一个正确生成的schema.rs文件) The docs say 在处理 Postgres 时使用 any — 这重要吗? 并非如此。 any 如果在此处绑定值数组,则性能会更好一些。对于子查询,这无关紧要。我什至不确定any 是否会接受子查询。 漂亮,这正是我想要的!具体来说,eq_any 运算符为我清除了整个事情。谢谢!

以上是关于如何在 Diesel 中对 Postgres 数据库执行删除子查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Postgres 9.4 中对 JSONB 类型的列执行更新操作

`DbConnection` 没有实现特征`diesel::Connection`

如何在 postgres 中对 jsonb 值求和?

在 Diesel 中关联三个表(多对多关系)的标准模式是啥?

如何在postgres中对几个月的日期记录分组后填补时间间隔

如何在 postgres 中对相同的 CTE 表达式执行 UNION ALL?