根据值的元组过滤 Postgres table

Filter Postgres table based on tuples of values

想象一下 Postgres 中的 table:

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

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

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

至return:

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 语句的解决方案感兴趣。

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

在其他 RDBMS 中,例如,我可以用值对填充临时 table,然后加入 table;或使用 table 值参数(在 SQL 服务器中)。 Postgres + NpgSql 中是否有等效项?

PS:我在 this question 中读到,为此使用临时 table 可能不是 Postgres

中的最佳实践

我认为传递类似于 table 值参数的东西的灵活方法是使用 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;

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

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 相比,它的额外优势在于易于调试,因为最后上传的值会为所有用户保留在数据库中。

我们找到的最佳解决方案是传入自定义类型数组,然后您可以在查询中将其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