【问题标题】:Filter Postgres table based on tuples of values根据值的元组过滤 Postgres 表
【发布时间】:2018-01-11 10:37:42
【问题描述】:

想象一下 Postgres 中的一个表:

Firstname     Surname      Age
------------------------------
Joe           Bloggs       5
Sam           Bloggs       7
Ellie         Jones        4
Mike          Smith        10

我想根据一组值对(元组)对其进行范围过滤:

{Surname=Bloggs  &&   Age>=6 },
{Surname=Smith   &&   Age>=10}

返回:

Firstname     Surname      Age
------------------------------
Sam           Bloggs       7
Mike          Smith        10

我意识到我可以通过手动滚动 SQL 语句来做到这一点:

SELECT * FROM MyTable t
WHERE (t.Surname = 'Bloggs' AND t.Age >= 6 )
OR    (t.Surname = 'Smith'  AND t.Age >= 10)

但是,我需要从 C# 调用它,并且我对避免为每个查询生成纯文本 SQL 语句的解决方案感兴趣。

是否可以使用“通用”SQL 语句来执行此操作,将某种元组/复合类型数组作为过滤器参数传递?

例如,在其他 RDBMS 中,我可以用成对的值填充一个临时表,然后加入该表;或使用表值参数(在 SQL Server 中)。 Postgres + NpgSql 中是否有等价物?

PS:我在this question 中读到,在 Postgres 中为此使用临时表可能不是最佳实践

【问题讨论】:

    标签: postgresql npgsql


    【解决方案1】:

    我认为传递类似于表值参数的东西的一种灵活方法是使用 JSON 传递用于条件的元组数组:

    select t.*
    from mytable t
      join json_array_elements('[{"surname": "Bloggs", "age": 6},
                                 {"surname": "Smith", "age": 10}]') x 
        on (x ->> 'surname') = t.surname and t.age >= (x ->> 'age')::int;
    

    在您的应用程序中,您可以将 JSON 作为字符串传递。不确定如何在 NpPgSQL 中传递参数,在以下示例中,? 是参数占位符:

    select t.*
    from mytable t
      join json_array_elements(cast(? as json)) x 
        on (x ->> 'surname') = t.surname 
       and t.age >= (x ->> 'age')::int;
    

    【讨论】:

      【解决方案2】:

      对于它的价值,我在 C# 应用程序中有完全相同的场景,并按照您在临时表解决方案中的描述进行操作,只是我使用了普通的物理表。我通过向表中添加一个用户 ID 字段来克服冲突,所以它看起来像这样:

      create table user_data.user_list (
        user_id varchar(20) not null,
        item_1 text,
        item_2 numeric
      )
      

      那么实际的 C# 实现(下面为了演示而过分简化)是:

      清除所有以前的条目:

      string user = Environment.GetEnvironmentVariable("USERNAME");
      NpgsqlCommand cmd = new NpgsqlCommand("delete from user_data.user_list " +
          "where user_id = :USER",
          conn);
      cmd.Parameters.AddWithValue("USER", user);
      cmd.ExecuteNonQuery();
      

      使用copy插入新记录:

      using (var writer = conn.BeginBinaryImport(
          "copy user_data.user_list from STDIN (FORMAT BINARY)"))
      {
          foreach (var tuple in userData)
          {
              writer.StartRow();
              writer.Write(user);
              writer.Write(tuple.Item1);
              writer.Write(tuple.Item2, NpgsqlDbType.Numeric);
          }
      }
      

      您的最终查询最终看起来像这样:

      select t.*
      from
        table1 t
        join user_data.user_list ul on
          t.surname = ul.item_1 and
          t.age >= ul.item_2 and
          ul.user_id = :USER_ID
      

      与 GTT 相比,它具有额外的优势,因为它易于调试,因为上次上传的值会为所有用户保留在数据库中。

      【讨论】:

        【解决方案3】:

        我们找到的最佳解决方案是传入自定义类型数组,然后您可以将其unnest 在查询中转换为表格形式,然后加入。

        CREATE TYPE predicate_type AS (
            Surname text,
            Age int);
        
        SELECT * FROM MyTable t
        JOIN unnest('{"(Bloggs, 6)","(Smith, 10)"}'::predicate_type[]) p(Surname, Age)
        ON t.Surname = p.Surname AND t.Age >= p.Age
        

        我在这里按字面意思定义了数组参数,但您可以将它们作为参数传递给您的查询。

        例如,您可以将等效的 C# 类型映射到 Npgsql 中的 Postgres 类型,然后只需将这些类型的 C# 数组作为参数传递到您的命令中:
        https://www.npgsql.org/doc/types/enums_and_composites.html#mapping-your-clr-types

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-08-03
          • 2023-03-28
          • 1970-01-01
          • 2014-04-22
          • 1970-01-01
          相关资源
          最近更新 更多