How does Room use sql statement keywords as query parameters? - android-room

#Query(
"""
SELECT * FROM t_article ORDER BY id :order
"""
)
fun pagingSource(order: String): PagingSource<Int, Article>
I want order set ASC or DESC, but the compiler prompts
'(', '.', ASC, BETWEEN, COLLATE, DESC, IN, LIMIT, comma or semicolon expected, got ':order'
How to solve this problem?

The issue is that you can only bind (replace ? with a value) literal values, not KEYWORDS or component names. In short if bound then the values are suitably enclosed so binding the string ASC would result in 'ASC'
You basically have the following options.
a) Multiple #Querys with selection of the appropriate Query (ascending or descending) e.g.
#Query("SELECT * FROM t_article ORDER BY id ASC")
fun pagingSourceAsc(): PagingSource<Int, Article>
#Query("SELECT * FROM t_article ORDER BY id DESC")
fun pagingSourceDesc(): PagingSource<Int, Article>
fun pagingSource(order: String): PagingSource<Int, Article> {
if(order.equals
}
b) Manipulation according to a value passed to the query where the keywords are selected according to the selection criteria e.g.
#Query("SELECT * FROM t_articale ORDER BY WHEN :order = 'ASC' THEN id ASC ELSE id DESC END")
c) By using a #RawQuery, which is very flexible BUT not validated at compile time e.g.
#RawQuery
fun pagingSourceRawQuery(rawQuery: SupportSQLiteQuery): PagingSource<Int, Article>
fun pagingSource(order: String): PagingSource<Int,Article> {
return pagingSourceRawQuery(SimpleSQLiteQuery("SELECT * FROM t_artical ORDER BY $order"))
}
Noting that Room will not validate the SQL at compile time and also that as the order is not bound then there is the potential for SQL injection.
this option can eliminate complex SQL but for the sake of the complexity being moved to the programming code (and thus often more understandable).

Related

Spring JPA - Issue while sorting on non entity column

I have requirement where I need to get the records based in join of three table with pagination(addition requirement are also there). So I have written a nativeQuery to get the records. Below is the sample query
#Query(value = "SELECT "
+ "a.GROUP_REF_ID as refId "
+ "count(case when c.STAT_CD in :userStatus then (c.grp_user_id) end) as numberOfUsers, "
+ "count(case when b.STAT_CD in :itemStatus then (b.grp_item_id) end) as numberOfItems "
+ "from grp a left join grp_item b on a.grp_id=b.grp_id left join grp_user c on a.grp_id=c.grp_id "
+ "where a.stat_cd in :status and a.co_id in :cids "
+ "group by a.GROUP_REF_ID,a.grp_nam,a.GRP_DESC,a.co_id,a.co_nam,a.CRTE_BY, "
+ "a.CRTE_DT,a.UPDT_BY,a.UPDT_DT ", countQuery = "select count(*) from grp where stat_cd in :status and co_id in :cids ", nativeQuery = true)
public Page<Object> findByStatusAndCompanyIdIn(#Param("status") String status, #Param("cids") List<Long> companyIds,
#Param("userStatus") List<GroupUserStatus> userStatus,
#Param("itemStatus") List<GroupItemStatus> itemStatus, Pageable pageable);
Now the requirement is also that these records are to be sorted on any of the column in select part. So, if user passes numberOfItems, the records are to be sorted on it. But I am facing an issue here because if I pass Sort parameter as numberOfItems, spring prepends an a. before numberOfItems which results in error that not able to find a.numberOfItems.
Is there a way I can stop spring from prepending table alias while creating a query with Sort, or should I write my logic in a different approach
Making my comment an answer so the question can be marked as answered:
Wrap the whole select in another one: select * from (<your current select>) x
I have solved the issue by creating a projection. (Kotlin was used but you’ll get the gist.)
class ShowRepository : JpaRepository<Int, Show> {
#Query("SELECT s AS show, (CASE WHEN (s.status = 'scheduled') THEN s.scheduledStartTime ELSE s.startTime END) AS time FROM Show s")
fun findShows(pageable: Pageable): Page<ShowWithTime>
}
interface ShowWithTime {
val show: Show,
val time: Date?
}
This allows Spring-Data to work its full magic, and using Sort.by(Order.desc("time")) works like a charm.
I’ve written it up with a little bit more detail here: Sorting by a Non-Entity Field.

Is it possible to put a subquery inside the FROM statement in my HQL Querys?

Here is my query.
SELECT letter
FROM
Letter AS letter,
(evaluateDisplayName) AS displayName
WHERE
letter.id =: someID
AND displayName =: someDisplayName
// AND etc etc...
The Subquery in this part:
(Do some subquery here) AS displayName
I don't know how to form. But the logic is something like this:
private String evaluateDisplayName(Letter letter) {
def username = letter?.sender?.username
def lastName = letter?.sender?.lastName
def emailAddress = letter?.sender?.emailAddress
return username ?: lastName ?: emailAddress
}
How to turn this into a subquery?
You don't need a subquery, the logic of evaluateDisplayName seems to be the same of the coalesce function: return the first not null value. Try with this:
SELECT letter
FROM
Letter AS letter LEFT JOIN letter.sender AS sender
WHERE
letter.id = :someID
AND COALESCE(sender.username, sender.lastName, sender.emailAddress) = :someDisplayName
// AND etc etc...

SQL Group by and having in coherence

I would like to execute the following query in Coherence
Select AVG(Salary)
, Count(*)
from Employees
group by Company
Having AVG(Salary)> 1000
I was trying to use GroupAggregators but I have problem with refering to an avarage salary as it is not defined in the Employee Class but in Aggregator:
GroupAggregator high_salary = GroupAggregator.createInstance("getDepartment"
, new DoubleAverage("getSalary")
, new GreaterFilter("DoubleAverage", 1000));
How can I do this?
You should use IdentityExtractor.INSTANCE instead of "DoubleAverage" as a value extractor in GreaterFilter, because value passed to this filter is the avarage salary itself, not some object with "DoubleAverage" property.
But if you would like to get avarage salary as well as count (that would be consistent with your SQL query), you will have to use CompositeAggregator. In that case the value passed to the filter will not be a number anymore, but a List and you will have to use ValueExtractor that will extract avarage salary from this list. In Coherence 12.2.1 it would look like this:
DoubleAverage<Employee> avgSalary = new DoubleAverage<>(Employee::getSalary);
Count<String, Employee> count = new Count<>();
CompositeAggregator<String, Country> compositeAggregator = CompositeAggregator
.createInstance(new InvocableMap.EntryAggregator[] { avgSalary, count });
ValueExtractor<Object, ? extends Double> salaryExtractor =
list -> ((List<Number>) list).get(0).doubleValue();
Filter having = Filters.greater(salaryExtractor, 1000.0);
GroupAggregator<String, Country, Employee, String, List> groupAggregator =
GroupAggregator.createInstance(Employee::getDepartment, compositeAggregator, having);
It can be done also in older versions, but it would require some more work to implement salaryExtractor without using lambda expression.

Help required to optimize LINQ query

I am looking to optimize my LINQ query because although it works right, the SQL it generates is convoluted and inefficient...
Basically, I am looking to select customers (as CustomerDisplay objects) who ordered the required product (reqdProdId), and are registered with a credit card number (stored as a row in RegisteredCustomer table with a foreign key CustId)
var q = from cust in db.Customers
join regCust in db.RegisteredCustomers on cust.ID equals regCust.CustId
where cust.CustomerProducts.Any(co => co.ProductID == reqdProdId)
where regCust.CreditCardNumber != null && regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.DisplayName,
RegNumber = cust.RegNumber
};
As an overview, a Customer has a corresponding Person which has the Name; PersonID is a foreign key in Customer table.
If I look at the SQL generated, I see all columns being selected from the Person table. Fyi, DisplayName is an extension method which uses Customer.FirstName and LastName. Any ideas how I can limit the columns from Person?
Secondly, I want to get rid of the Any clause (and use a sub-query) to select all other CustomerIds who have the required ProductID, because it (understandably) generates an Exists clause.
As you may know, LINQ has a known issue with junction tables, so I cannot just do a cust.CustomerProducts.Products.
How can I select all Customers in the junction table with the required ProductID?
Any help/advice is appreciated.
The first step is to start your query from CustomerProducts (as Alex Said):
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
join regCust in db.RegisteredCustomers
on custProd.Customer.ID equals regCust.CustId
where
custProd.ProductID == reqProdId
&& regCust.CreditCardNumber != null
&& regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
This will simplify your syntax and hopefully result in a better execution plan.
Next, you should consider creating a foreign key relationship between Customers and RegisteredCustomers. This would result in a query that looked like this:
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
where
custProd.ProductID == reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
Finally, for optimum speed, have LINQ compile your query at compile time, rather than run time by using a compiled query:
Func<MyDataContext, SearchParameters, IQueryable<CustomerDisplay>>
GetCustWithProd =
System.Data.Linq.CompiledQuery.Compile(
(MyDataContext db, SearchParameters myParams) =>
from custProd in db.CustomerProducts
where
custProd.ProductID == myParams.reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
);
You can call the compiled query like this:
IQueryable<CustomerDisplay> myCustDisplay = GetCustWithProd(db, myParams);
I'd suggest starting your query from the product in question, e.g. something like:
from cp in db.CustomerProducts
join .....
where cp.ProductID == reqdProdID
As you have found, using a property defined as an extension function or in a partial class will require that the entire object is hydrated first and then the select projection is done on the client side because the server has no knowledge of these additional properties. Be glad that your code ran at all. If you were to use the non-mapped value elsewhere in your query (other than in the projection), you would likely see a run-time exception. You can see this if you try to use the Customer.Person.DisplayName property in a Where clause. As you have found, the fix is to do the string concatenation in the projection clause directly.
Lame Duck, I think there is a bug in your code as the cust variable used in your select clause isn't declared elsewhere as a source local variable (in the from clauses).

Linq Joining IEnumerable(Of Struct) to IEnumerable(of object)

With class RoleRecord (Guid RoleId, string RoleName,...) I am trying to get a new list of Name where the RoleId matches a list of Guid
IEnumerable<RoleRecord> roles;
IEnumerable<Guid> roleIds;
I was thinking about avoiding the nested for loops, and along the lines of :
var query =
from rowA in roles
join rowB in roleIds
on rowA.RoleId equals rowB.????
select { rowA.RoleName };
I tried to wrap the guid in a class too, but cant even build it because the syntax is wrong.
Any ideas?
Thanks
I would personally not use Jeremy's answer if you've got a significant number of Guids. If a join is what you really want to express - you just need:
var query = from rowA in roles
join rowB in roleIds on rowA.RoleId equals rowB
select rowA.RoleName;
Alternatively, create a set of role IDs first:
HashSet<Guid> validRoleIds = new HashSet<Guid>(roleIds);
var query = from rowA in roles
where validRoleIds.Contains(rowA.RoleId)
select rowA.RoleName;
The advantage is that then you don't need to do a linear search through every valid role ID for every role. That's not an issue if you know you don't have many roles or role IDs, but generally a hashing approach will be more effective.
Note that the join will use a hash as well internally.
Give this a try:
var query =
from rowA in roles
where roleIds.Contains(rowA.RoleId)
select rowA.RoleName;

Resources