根据值的元组过滤Postgres表

时间:2018-01-11 10:37:42

标签: postgresql npgsql

想象一下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语句。

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

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

PS:我在this question中读到,使用临时表可能不是Postgres中的最佳做法

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

对于它的价值,我在C#应用程序中拥有完全相同的场景,并执行您在临时表解决方案中描述的内容,只使用普通的物理表。我通过在表中添加一个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相比,它具有额外的优势,因为它可以轻松调试,因为上次上传的值会在数据库中保留给所有用户。

答案 2 :(得分:0)

我们发现最好的解决方案是传递自定义类型的数组,然后您可以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