QueryDsl - issue with mapping query with case builder inside aggregates - sql

I have a problem constructing a query which uses cases inside aggregates.
My setup: QueryDsl 3.3.2, JPA + Hibernate 3.6.10.Final
I Have two entities (psudo code):
Car {
private String ownerNumber;
private String color;
}
Client {
private String number;
}
There is no direct relationship between those two, just for some reporting simplicity Car holds Client number reference.
The first problem:
As I found on different SO posts I cannot use join on this etities using custom properties, I need to use for example cross join. Using: from client cl, car.c where cl.number=c.owner_number - the drawback is I'm not able to get all clients (which indicates lack of a car) - just thos that have connection for those two props. This is not too good for my reports but let's say I can live with that.
The second problem:
In the same query I also need to aggregate some data - I need to count all user cars, and let say all red. So I need sth like this:
SELECT c.number, count(c.id) AS total, SUM(CASE WHEN c.color='RED' THEN 1 ELSE 0 END) as allRed FROM client cl, car c WHERE cl.number=c.ownerNumber GROUP BY c.number;
This query works as expected;
For QueryDsl I have:
QClient client = Qclient.client;
QCar car = Qcar.car;
NumberExpression<Integer> redCarExp = new CaseBuilder()
.when(car.color.eq("RED"))
.then(1)
.otherwise(0);
List<Tuple> list = new JPAQuery(em)
.from(client, car)
.where(client.number.eq(car.ownerNumber))
.groupBy(client.number)
.list(client.number, car.id.count(), redCarExp.sum());
Running this generates JPAQuery shown below and exception. Without the last sum() part all works ok. I'm not sure where is the issue here.
java.lang.ClassCastException: org.hibernate.hql.ast.tree.ParameterNode cannot be cast to org.hibernate.hql.ast.tree.SelectExpression
org.hibernate.hql.ast.tree.CaseNode.getFirstThenNode(CaseNode.java:44)
org.hibernate.hql.ast.tree.CaseNode.getDataType(CaseNode.java:40)
org.hibernate.hql.ast.util.SessionFactoryHelper.findFunctionReturnType(SessionFactoryHelper.java:402)
org.hibernate.hql.ast.tree.AggregateNode.getDataType(AggregateNode.java:82)
org.hibernate.hql.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:154)
org.hibernate.hql.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:857)
org.hibernate.hql.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:645)
org.hibernate.hql.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:685)
org.hibernate.hql.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:301)
org.hibernate.hql.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:244)
org.hibernate.hql.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:256)
org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:187)
org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:138)
org.hibernate.engine.query.HQLQueryPlan.(HQLQueryPlan.java:101)
org.hibernate.engine.query.HQLQueryPlan.(HQLQueryPlan.java:80)
org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:124)
org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:156)
org.hibernate.impl.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:135)
org.hibernate.impl.SessionImpl.createQuery(SessionImpl.java:1770)
org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:277)
sun.reflect.GeneratedMethodAccessor74.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:606)
org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:342)
com.sun.proxy.$Proxy63.createQuery(Unknown Source)
sun.reflect.GeneratedMethodAccessor74.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:606)
org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:262)
com.sun.proxy.$Proxy63.createQuery(Unknown Source)
com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:129)
com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:97)
com.mysema.query.jpa.impl.AbstractJPAQuery.list(AbstractJPAQuery.java:242)
com.mysema.query.jpa.impl.AbstractJPAQuery.list(AbstractJPAQuery.java:236)

Related

QueryDsl: Exception "argument type mismatch" with projection bean and oneToMany or manyToMany association

I have an association manyToMany between User and Role entities (User >---< Role)
I wanted to perform this query:
createQuery()
.from(qUser)
.leftJoin(qUser.roles, qRole)
.where(qUser.login.eq(login))
.singleResult(
Projections.bean(User.class,
qUser.id,
qUser.login,
qUser.password,
GroupBy.set(Projections.bean(Role.class,
qRole.id,
qRole.code
)).as(qUser.roles)
)
);
The generated query looks like this, for me it's perfect:
SELECT user0_.ID AS col_0_0_,
user0_.LOGIN AS col_1_0_,
user0_.PASSWORD AS col_2_0_,
role2_.ID AS col_4_0_,
role2_.CODE AS col_5_0_
FROM public.USER user0_
LEFT OUTER JOIN public.USER_ROLE roles1_ ON user0_.ID=roles1_.USER_ID
LEFT OUTER JOIN public.ROLE role2_ ON roles1_.ROLE_ID=role2_.ID
WHERE user0_.LOGIN=? LIMIT ?
But I have a java.lang.IllegalArgumentException: argument type mismatch.
I debugged and I found out that data from database id loaded without problem. This is when QueryDsl/Hibernate did some introspection to create and initialise my entities that the exception is throwed.
The problem is that the User.setRoles(Set<Role>) method has called with a long parameter: The ID of the first Role entity list of the User. Instead of create a Set of Role an then associate these roles to the User.
Is there a problem with the query? Or is it not supported by QueryDsl?
I am using the QueryDsl 3.6.6 (I tested with 3.7.4: same result)
I guess that the java.lang.IllegalArgumentException: argument type mismatch are thrown not by the JOIN comparing and you can verify that by verifing the ID type of the 3 tables UTILISATEUR, ROLE and USER_ROLE.
If there is a difference on type between
UTILISATEUR.ID=USER_ROLE.USER_ID
or
USER_ROLE..ROLE_ID=ROLE.ID
so that's the probleme.
If there is not a problem , the exception is thrown by the test of equality of the login.
But, I suspect that the USER_ROLE join table did not connect with the correct table. You may have two tables USER and UTILISATEUR . Unless you renamed the join table.
I have had same error as you,
I try different ways and it took a lot of time.Eventually I founded this way,
My classes are:
Lesson
LessonScores for saving user scores
LessonScoresModel for returning data and
LessonScoresModel.ScoresModel is a static nested class
I need some information about user scores in a class.
I use below query for returning data.
I use exact related models for retuning data but it's gives the beatiful "argument type mismatch" error.
So I developed a static class for returning data.
JPAQuery query = new JPAQuery(em);
//select from which model you need
query.from(QLessonScores.lessonScores);
//use your condition
query.where(predicate);
//use query and returning data
Map<Lesson,List<LessonScoresModel.ScoresModel>> map = (Map<Lesson,List<LessonScoresModel.ScoresModel>>)
//use tranform for making group by
query.transform(GroupBy.groupBy(QLessonScores.lessonScores.lesson).as(
//return list of data which column we need
GroupBy.list(Projections.fields(LessonScoresModel.ScoresModel.class,
QLessonScores.lessonScores.score.as("score"),
QLessonScores.lessonScores.scoresType.as("scoresType"),
QLessonScores.lessonScores.success.as("success")
))
));
you will find more information in these links,
codata
programcreek

QueryDsl column case sensitivity bug on identifier

I have seen many post on column name case sensitivity for in memory database like hsqldb and h2. We are using sql server camel case column names. However, I am testing using HyperSql which is column name case sensitive. I don't see any settings to handle column name sensitivity in hypersql except when creating the table quote the column names which will make them what ever case are inside the quotes, for example
Insert Into AddressType ("AddressTypeName", "CreateUser")
Values ('Mailing', 'User')
This will create tables in hsqldb with column name AddressTypeName and CreateUser
sql server is not willing to make theirs all upper case. As a result, when creating the columns in hsqldb I quote them which creates them in camel case. Works good except when using Querydsl their identiifers does not quote them which then results in the lookup with upper case column names and the database now has them as camel case.
The whole problem started because QueryDsl Q types which were generated off the sql server columns has the metadata with quote columns and since sql server had them as camel case it looks them up as camel case. But now when doing a Q type query it uses the identifier un quoted which results in a upper case lookup which fails. I don't see any work around except to let the database make them upper case and change all the Q types to be upper case for the meta data column name look ups.
I assume this is a bug in querydsl.
Example QAddressType addMetadata. Notice the ColumnMetadata.named("AddressTypeName") By these being quoted the database must have them in camel case.
public void addMetadata() {
addMetadata(addressTypeID, ColumnMetadata.named("AddressTypeID").withIndex(1).ofType(Types.BIGINT).withSize(19).notNull());
addMetadata(addressTypeName, ColumnMetadata.named("AddressTypeName").withIndex(2).ofType(Types.VARCHAR).withSize(50).notNull());
ColumnMetadata.named("CreateUser").withIndex(3).ofType(Types.VARCHAR).withSize(100).notNull());
}
But if I switch the database to be camel case then the queryDslTemplate call fails because it does not quote the identifier. So in the below code .where(qAddressType.addressTypeID.eq(id)); does this in sql a.addressTypeID = 1 where a is the alias for QAddressType. It needs to be a."addressTypeID" = 1 or else hsqldb looks it up as a.ADDDRESTYPEID = 1
private static QAddressType qAddressType = new QAddressType("a");
#Override
public AddressType getById(Long id) {
AddressType addressType = null;
try {
SQLQuery sqlQuery = queryDslJdbcTemplate.newSqlQuery()
.from(qAddressType)
.where(qAddressType.addressTypeID.eq(id));
addressType = queryDslJdbcTemplate.queryForObject(sqlQuery, new AddressTypeProjection(qAddressType));
//the API was not throwing the Exception so let's explicitly throw it
return addressType;
}

Mondrian/Pivot4j error java.lang.IndexOutOfBoundsException with closure table

This is the scenario:
Pentaho 5.4.0 CE
Pivot4j Plugin
If in Mondrian XML Schema I insert a dimension with a hierarchy type parent-child - defined using a closure table - with the following options, it works well (that is, I can see the result table and drill down to the parent-child hierarchy elements):
attribute "nameColumn" defined (reference a fact table field)
attribute "captionColumn" empty (no field assigned)
If I try to change the two attributes definition as
attribute "nameColumn" defined (reference a fact table field)
attribute "captionColumn" defined (reference another fact table field)
I get the error from Pivot4J java.lang.IndexOutOfBoundsException: Index: 2, Size: 2. It happen only with dimension defined using closure table, in standard dimension I can set the attributes at the same time with no error.
Any idea about it, how I can solve this? It's a problem because I need to use the captionColumn attribute that contains label value for the end-user, nameColumn instead contains a nickname. I've the same problem in Pentaho 6.0.
This is what I have in the pentaho log:
... Caused by: java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
at java.util.ArrayList.rangeCheck(ArrayList.java:635)
at java.util.ArrayList.get(ArrayList.java:411)
at mondrian.rolap.SqlMemberSource.makeMember(SqlMemberSource.java:1072)
at mondrian.rolap.SqlMemberSource.getMemberChildren2(SqlMemberSource.java:1004)
at mondrian.rolap.SqlMemberSource.getMemberChildren(SqlMemberSource.java:881)
at mondrian.rolap.SqlMemberSource.getMemberChildren(SqlMemberSource.java:854)
at mondrian.rolap.SmartMemberReader.readMemberChildren(SmartMemberReader.java:249)
at mondrian.rolap.SmartMemberReader.getMemberChildren(SmartMemberReader.java:211)
at mondrian.rolap.RolapCubeHierarchy$CacheRolapCubeHierarchyMemberReader.readMemberChildren(RolapCubeHierarchy.java:600)
at mondrian.rolap.RolapCubeHierarchy$CacheRolapCubeHierarchyMemberReader.getMemberChildren(RolapCubeHierarchy.java:696)
at mondrian.rolap.SmartMemberReader.getMemberChildren(SmartMemberReader.java:177)
at mondrian.rolap.RestrictedMemberReader.getMemberChildren(RestrictedMemberReader.java:101)
at mondrian.rolap.SmartRestrictedMemberReader.getMemberChildren(SmartRestrictedMemberReader.java:85)
at mondrian.rolap.RolapSchemaReader.internalGetMemberChildren(RolapSchemaReader.java:186)
at mondrian.rolap.RolapSchemaReader.getMemberChildren(RolapSchemaReader.java:168)
at mondrian.rolap.RolapSchemaReader.getMemberChildren(RolapSchemaReader.java:162)
at mondrian.olap4j.MondrianOlap4jMember$3.execute(MondrianOlap4jMember.java:111)
at mondrian.olap4j.MondrianOlap4jMember$3.execute(MondrianOlap4jMember.java:110)
at mondrian.server.Locus.execute(Locus.java:86)
at mondrian.server.Locus.execute(Locus.java:71)
at mondrian.olap4j.MondrianOlap4jMember.getChildMemberCount(MondrianOlap4jMember.java:105)
at org.pivot4j.impl.QueryAdapter.canExpand(QueryAdapter.java:838)
at org.pivot4j.transform.impl.DrillExpandPositionImpl.canExpand(DrillExpandPositionImpl.java:44)
at org.pivot4j.ui.command.DrillExpandPositionCommand.canExecute(DrillExpandPositionCommand.java:69)
at org.pivot4j.ui.AbstractPivotRenderer.getCommands(AbstractPivotRenderer.java:146)
at org.pivot4j.ui.table.TableRenderer.access$100(TableRenderer.java:60)
at org.pivot4j.ui.table.TableRenderer$3.handleTreeNode(TableRenderer.java:649)
at org.pivot4j.ui.table.TableHeaderNode.walkChildrenAtColIndex(TableHeaderNode.java:915)
at org.pivot4j.ui.table.TableHeaderNode.walkChildrenAtColIndex(TableHeaderNode.java:931)
at org.pivot4j.ui.table.TableRenderer.renderBody(TableRenderer.java:611)
at org.pivot4j.ui.table.TableRenderer.render(TableRenderer.java:483)
at org.pivot4j.analytics.ui.ViewHandler.render(ViewHandler.java:597)
at org.pivot4j.analytics.ui.ViewHandler.structureChanged(ViewHandler.java:963)
at org.pivot4j.impl.PivotModelImpl.fireStructureChanged(PivotModelImpl.java:833)
at org.pivot4j.impl.PivotModelImpl$1.queryChanged(PivotModelImpl.java:111)
at org.pivot4j.impl.QueryAdapter.fireQueryChanged(QueryAdapter.java:197)
at org.pivot4j.impl.QueryAdapter.fireQueryChanged(QueryAdapter.java:182)
at org.pivot4j.impl.QueryAdapter.onQuaxChanged(QueryAdapter.java:1109)
at org.pivot4j.impl.QueryAdapter$1.quaxChanged(QueryAdapter.java:79)
at org.pivot4j.impl.Quax.fireQuaxChanged(Quax.java:163)
at org.pivot4j.impl.Quax.regeneratePosTree(Quax.java:648)
at org.pivot4j.transform.impl.PlaceHierarchiesOnAxesImpl.placeHierarchies(PlaceHierarchiesOnAxesImpl.java:88)
at org.pivot4j.transform.impl.PlaceHierarchiesOnAxesImpl.addHierarchy(PlaceHierarchiesOnAxesImpl.java:119)
at org.pivot4j.analytics.ui.NavigatorHandler.addHierarhy(NavigatorHandler.java:548)
at org.pivot4j.analytics.ui.NavigatorHandler.addHierarhy(NavigatorHandler.java:516)
at org.pivot4j.analytics.ui.NavigatorHandler.onDropOnAxis(NavigatorHandler.java:392)
at org.pivot4j.analytics.ui.NavigatorHandler.onDropOnAxis(NavigatorHandler.java:365)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.el.parser.AstValue.invoke(AstValue.java:191)
... 77 more

How to use LINQ to Entities to make a left join using a static value

I've got a few tables, Deployment, Deployment_Report and Workflow. In the event that the deployment is being reviewed they join together so you can see all details in the report. If a revision is going out, the new workflow doesn't exist yet new workflow is going into place so I'd like the values to return null as the revision doesn't exist yet.
Complications aside, this is a sample of the SQL that I'd like to have run:
DECLARE #WorkflowID int
SET #WorkflowID = 399 -- Set to -1 if new
SELECT *
FROM Deployment d
LEFT JOIN Deployment_Report r
ON d.FSJ_Deployment_ID = r.FSJ_Deployment_ID
AND r.Workflow_ID = #WorkflowID
WHERE d.FSJ_Deployment_ID = 339
The above in SQL works great and returns the full record if viewing an active workflow, or the left side of the record with empty fields for revision details which haven't been supplied in the event that a new report is being generated.
Using various samples around S.O. I've produced some Entity to SQL based on a few multiple on statements but I feel like I'm missing something fundamental to make this work:
int Workflow_ID = 399 // or -1 if new, just like the above example
from d in context.Deployments
join r in context.Deployment_Reports.DefaultIfEmpty()
on
new { d.Deployment_ID, Workflow_ID }
equals
new { r.Deployment_ID, r.Workflow_ID }
where d.FSJ_Deployment_ID == fsj_deployment_id
select new
{
...
}
Is the SQL query above possible to create using LINQ to Entities without employing Entity SQL? This is the first time I've needed to create such a join since it's very confusing to look at but in the report it's the only way to do it right since it should only return one record at all times.
The workflow ID is a value passed in to the call to retrieve the data source so in the outgoing query it would be considered a static value (for lack of better terminology on my part)
First of all don't kill yourself on learning the intricacies of EF as there are a LOT of things to learn about it. Unfortunately our deadlines don't like the learning curve!
Here's examples to learn over time:
http://msdn.microsoft.com/en-us/library/bb397895.aspx
In the mean time I've found this very nice workaround using EF for this kind of thing:
var query = "SELECT * Deployment d JOIN Deployment_Report r d.FSJ_Deployment_ID = r.Workflow_ID = #WorkflowID d.FSJ_Deployment_ID = 339"
var parm = new SqlParameter(parameterName="WorkFlowID" value = myvalue);
using (var db = new MyEntities()){
db.Database.SqlQuery<MyReturnType>(query, parm.ToArray());
}
All you have to do is create a model for what you want SQL to return and it will fill in all the values you want. The values you are after are all the fields that are returned by the "Select *"...
There's even a really cool way to get EF to help you. First find the table with the most fields, and get EF to generated the model for you. Then you can write another class that inherits from that class adding in the other fields you want. SQL is able to find all fields added regardless of class hierarchy. It makes your job simple.
Warning, make sure your filed names in the class are exactly the same (case sensitive) as those in the database. The goal is to make a super class model that contains all the fields of all the join activity. SQL just knows how to put them into that resultant class giving you strong typing ability and even more important use-ability with LINQ
You can even use dataannotations in the Super Class Model for displaying other names you prefer to the User, this is a super nice way to keep the table field names but show the user something more user friendly.

Entity Framework Dynamic Lambda to Perform Search

I have the following entities in Entity Framwork 5 (C#):
OrderLine - Id, OrderId, ProductName, Price, Deleted
Order - Id, CustomerId, OrderNo, Date
Customer - Id, CustomerName
On the order search screen the user can enter the following search values:
ProductName, OrderNo, CustomerName
For Example they might enter:
Product Search Field: 'Car van bike'
Order Search Field: '100 101 102'
Customer Search Field: 'Joe Jack James'
This should do a OR search (ideally using linq to entities) for each entered word, this example would output the following where sql.
(ProductName like 'Car' Or ProductName like 'van' Or ProductName like 'bike') AND
(OrderNo like '100' Or OrderNo like '101' Or OrderNo like '102') AND
(CustomerName like 'Joe' Or CustomerName like 'Jack' Or CustomerName like 'James')
I want to do this using linq to entities, i am guessing this would need to be some sort of dynamic lambda builder as we don't know how many words the user might enter into each field.
How would i go about doing this, i have had a quick browse but cant see anything simple.
You can build a lambda expression using Expression Trees . What you need to do is split the value and build the expression . Then you can convert in in to a lambda expression like this,
var lambda = Expression.Lambda<Func<object>>(expression);
Here is an example
There are 2 basic approaches to Dynamic Expressions and Queries in LINQ.
3 if you count using Json as the approach to get a lambda expression. => Akash Kava post
a) String Dynamic Lambda
System.Linq.Dynamic can be found at following links
http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
http://www.scottgu.com/blogposts/dynquery/dynamiclinqcsharp.zip
b) Build Expression trees
More powerful but harder to master...
Build expressions trees with code found here:
http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
an alternate approach is predicate builder but it isnt really that dynamic.
but can deal with the OR type scenario you give as example.
http://www.albahari.com/nutshell/predicatebuilder.aspx
I would recomend to go slightly different way from answers above and use EntitySQL as it is trivial to build SQL-like string with dynamic conditions.
http://msdn.microsoft.com/en-us/library/bb738683.aspx
Disclaimer: I am author of Entity REST SDK.
You can look at Entity REST SDK at http://entityrestsdk.codeplex.com
You can query using JSON syntax as shown below,
/app/entity/account/query?query={AccountID:2}&orderBy=AccountName
&fields={AccountID:'',AcccountName:''}
You can use certain extensions provided to convert JSON to lambda.
And here is details of how JSON is translated to Linq. http://entityrestsdk.codeplex.com/wikipage?title=JSON%20Query%20Language&referringTitle=Home
Current Limitations of OData v3
Additionally, this JSON based query is not same as OData, OData does not yet support correct way to search using navigation properties. OData lets you search navigation property inside a selected entity for example Customer(1)/Addresses?filter=..
But here we support both Any and Parent Property Comparison as shown below.
Example, if you want to search for List of Customers who have purchased specific item, following will be query
{ 'Orders:Any': { 'Product.ProductID:==': 2 } }
This gets translated to
Customers.Where( x=> x.Orders.Any( y=> y.Product.ProductID == 2))
There is no way to do this OData as of now.
Advantages of JSON
When you are using any JavaScript frameworks, creating query based on English syntax is little difficult, and composing query is difficult. But following method helps you in composing query easily as shown.
function query(name,phone,email){
var q = {};
if(name){
q["Name:StartsWith"] = name;
}
if(phone){
q["Phone:=="] = phone;
}
if(email){
q["Email:=="] = email;
}
return JSON.stringify(q);
}
Above method will compose query and "AND" everything if specified. Creating composable query is great advantage with JSON based query syntax.