Use array of ints parameter in FromSQL query and Where In clause - entity-framework-core

I have a list of ints:
var ids = new List { 10, 20 };
And I need to find Users with that ids:
context.Users.FromSqlInterpolated($#"
select Users.*
where Users.Id in ({String.Join(',', ids)})"
But I get the following error:
'Conversion failed when converting the nvarchar value '10, 20' to data type int.'
How can I use such a parameter?

Using Interpolated method is not appropriate here, because {String.Join(',', ids)} defines single string placeholder, hence EF Core binds single nvarchar parameter with value '10,20', so the actual SQL is like this
select Users.*
where Users.Id in ('10,20')
which is invalid, hence the exception.
You should use Raw method instead. Either
var query = context.Users.FromSqlRaw($#"
select Users.*
where Users.Id in ({String.Join(',', ids)})");
which will embed literal values
select Users.*
where Users.Id in (10,20)
or if you want to parameterize it, generate parameter placeholders like {0}, {1} etc. inside the SQL and pass values separately:
var placeholders = string.Join(",", Enumerable.Range(0, ids.Count)
.Select(i => "{" + i + "}"));
var values = ids.Cast<object>().ToArray();
var query = context.Users.FromSqlRaw($#"
select Users.*
where Users.Id in ({placeholders})", values);
which would generate SQL like this
select Users.*
where Users.Id in (#p0,#p1)

If you need to combine .FromSql() and SQL WHERE x IN () then perform a second operation on the output IQueryable<T> of the .FromSql() call.
var ids = new List { 10, 20 };
var query = context.Users.FromSqlInterpolated(
$#"select *
from Users");
if (ids?.Count > 0)
{
query = query.Where(user => ids.Contains(user.Id));
}
Resultant SQL approximates to:
select * from (
select *
from Users
) u
where u.Id in (10,20)
No invalid SQL if ids is empty
No empty result if ids is empty string (I was working with a string data type)

Related

Cast top-level JSONB field as UUID

Consider a table like so:
CREATE TABLE items (
e uuid,
v jsonb
)
I insert the following values:
INSERT INTO items (e, v) VALUES
('9a70439e-33c0-4b34-91f5-efac20b58301', '"92cb730c-8b4f-46ef-9925-4fab953694c6"'),
('92cb730c-8b4f-46ef-9925-4fab953694c6', '"Bob"'),
('92cb730c-8b4f-46ef-9925-4fab953694c6', '52');
Note how for v, I have actually stringified text and numbers -- not an object.
Now, what if I wanted to write a query like so:
WITH match AS (
SELECT * FROM items WHERE e = '9a70439e-33c0-4b34-91f5-efac20b58301'
) SELECT * FROM items JOIN match ON match.v = items.e;
I would get:
Query Error: error: operator does not exist: jsonb = uuid
Is there a way I could tell postgres to "parse" the jsonb value, and see if it is a uuid?
I know about Postgres cast to UUID from JSON , where the ->> operator was able to do the trick. But I can't do that in this case, because our json value is a strong not an object.
You can use ->> 0 to extract the value as text:
SELECT * FROM items
WHERE e = '9a70439e-33c0-4b34-91f5-efac20b58301'
AND e::text = v ->> 0;

Filter Postgres Json array is subset of array using JsonPath

I fail to find any information on how to do in / subsetof / contains queries using JsonPath in Postgres.
e.g.
Assuming the following data in a jsonb column named data
{
"name": "foo",
"somearray" : [1,2,3,4,5]
}
Then I want to query this using something like
SELECT *
FROM mytable
where jsonb_path_exists(data, '($.somearray ??????? [2,4,6,8] ');
This does work:
SELECT *
FROM mytable
where jsonb_path_exists(data, '($ ? (#.somearray[*] == 2 || #.somearray[*] == 4 /*etc*/) ');
But I am hoping that there is some shorter syntax to do a proper subset test
Unfortunately no jsonpath array operators, but you can still use the array operators :
SELECT *
FROM mytable
where (data->>'somearray') :: integer[] #> [2,4,6,8] ;

Flask SQLAlchemy Filter On A Postgres JSON List Object Based on a Single String

I have a model called Testing. The field called alias is a JSON field (a list really) and has values such as ["a", "b"] or ["d", "e"] and so on.
class Testing(db.Model):
__tablename__ = 'testing'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(25))
alias = db.Column(JSON)
def __init__(self, name, alias):
self.name = name
self.alias = alias
In my flask view I grab a url parameter that I want to use to filter Testing to get all Testing objects in which the parameter value is in the alias json list. So for example if url_param_value="a" I want all the Testing objects where "a" is in alias. So the alias value of ["a", "b"] would be a hit in this example.
Here is my approach but its not working and I assume it has to do with seralization.
Testing.query.filter(Testing.alias.contains(url_param_val)).all()
I am getting the below error
NotImplementedError: Operator 'contains' is not supported on this expression
The name field is a JSON type, not an array type. JSON columns don't have a contains method, even if you happen to be storing array data (how would the database know?)
In Postgres, you can use json_array_elements to expand a JSON array to a set of JSON values; this will return one row per element:
select id, json_array_elements(alias) as val from testing;
id | val
---------+--------------------
1 | "a"
2 | "b"
You can use that as a subquery to select records that contain a matching value:
select t.id, t.name, t.alias, cast(q.val as varchar)
from testing t, (
select id, json_array_elements(alias) as val
from testing
) q
where q.id=t.id and cast(q.val as varchar) = '"a"';
In SQLAlchemy syntax:
subq = session.query(
Testing.id,
func.json_array_elements(Testing.alias).label("val")
).subquery()
q = session.query(Testing).filter(
cast(subq.c.val, sa.Unicode) == '"a"',
subq.c.id == Testing.id)
Warning: this is going to be very inefficient for large tables; you're probably better off fixing the types to match your data, and then creating appropriate indexes.

Criteriabuilder like, how to do it for Long?

i try use "like" method from Criteriabuilder for get all record based on pattern " 10% ".
I want get record where ID is - 101, 10002, 1003,1000 etc...
I've use this code:
Predicate p = cb.like(r.<String>get("ID").as(String.class), "10%")
but i got Exception where i see what postgres can't execute query like this:
SELECT ID, NAME, SOMETHING FROM TABLE WHERE ID LIKE 10%
That is JPA (Glassfish 4.x) generate wrond query.
Right query must like that :
SELECT ID, NAME, SOMETHING FROM TABLE WHERE CAST (ID as TEXT) LIKE '10%'
How to build query via Criteria API that i got a right query for postgres ?
Updated:
I try write a CAST function :
Expression<String> postgresqlCastFunction = cb.function("CAST", String.class, r.<String>get("ID").as(String.class));
Predicate p = cb.like(postgresqlCastFunction, "10%");
but got a query like this :
FROM TABLE WHERE (CAST(ID) LIKE ?)
, so, how to add need expression in function for this right result -
FROM TABLE WHERE (CAST(ID as TEXT) LIKE ?) ..
An example implementation using PostgreSQL native TO_CHAR function may look as follows:
JPQL
SELECT r FROM Records r
WHERE FUNCTION('TO_CHAR', r.ID, 'FM9999999999') LIKE :pattern
Criteria API
Path<String> id = r.get("ID");
Expression<String> format = cb.literal("FM9999999999");
Expression<String> function= cb.function("TO_CHAR", String.class, id, format);
ParameterExpression<String> pattern = cb.parameter(String.class, "pattern");
Predicate like = cb.like(function, pattern);
cq.where(like);
also you can build the query as an one-liner:
cq.where(cb.like(cb.function("TO_CHAR", String.class, r.get("ID"), cb.literal("FM9999999999")), cb.parameter(String.class, "pattern")));
Execute the above query:
Query q = em.createQuery(cq).setParameter("pattern", "10%");

How to ORDER BY non-column field?

I am trying to create an Entity SQL that is a union of two sub-queries.
(SELECT VALUE DISTINCT ROW(e.ColumnA, e.ColumnB, 1 AS Rank) FROM Context.Entity AS E WHERE ...)
UNION ALL
(SELECT VALUE DISTINCT ROW(e.ColumnA, e.ColumnB, 2 AS Rank) FROM Context.Entity AS E WHERE ...)
ORDER BY *??* LIMIT 50
I have tried:
ORDER BY Rank
and
ORDER BY e.Rank
but I keep getting:
System.Data.EntitySqlException: The query syntax is not valid. Near keyword 'ORDER'
Edit:
This is Entity Framework. In C#, the query is executed using:
var esql = "...";
ObjectParameter parameter0 = new ObjectParameter("p0", value1);
ObjectParameter parameter1 = new ObjectParameter("p1", value2);
ObjectQuery<DbDataRecord> query = context.CreateQuery<DbDataRecord>(esql, parameter0, parameter1);
var queryResults = query.Execute(MergeOption.NoTracking);
There is only a small portion of my application where I have to resort to using Entity SQL. Generally, the main use case is when I need to do: "WHERE Column LIKE 'A % value % with % multiple % wildcards'".
I do not think it is a problem with the Rank column. I do think it is how I am trying to apply an order by to two different esql statements joined by union all. Could someone suggest:
How to apply a ORDER BY to this kind of UNION/UNION ALL statment
How to order by the non-entity column expression.
Thanks.