根据值的元组过滤 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
想象一下 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