How to retrieve items with hibernate clustered by to_days + COUNT(*) - mysql

I am pretty new to hibernate again, so this might be a noobish question ;).
Without to_days, but clustered by timestamp it works like this:
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<Session> sessionRoot = query.from(Session.class);
query.multiselect(
sessionRoot.get("time").alias("time"),
criteriaBuilder.count(sessionRoot).alias("count")
);
query.groupBy(sessionRoot.get("time"));
List<Tuple> results = this.executeQuery(query);
So I recieve:
time|count
13721938721|1
13721938722|2
13721938723|3
13721938724|4
13721938725|2
13721938726|1
13721938727|4
But this are all sessioncounts for each millisecond, but I need those clustered by day and not by timestamp: thus I use to_days in plain mysql.
In mysql I perform this query:
SELECT TO_DAYS(`time`) AS `days`, COUNT(*) as `count` FROM sessions WHERE 1 GROUP BY `days`
This gives me:
days|count
777594|123
777595|60
777596|61
777597|74
But I have no idea, yet: how to achieve the same thing with javax.persistence.criteria.CriteriaBuilder and CriteriaQuery in hibernate?

I dont know how to do it with criteriaBuilder, but i do know how in Hibernate 4 criteria api:
query.setProjection(
Projections.sqlProjection(
"TO_DAYS(time) as days",
new String[]{"days"},
new Type[]{StandardBasicTypes.INTEGER}
)
);
sqlProjection allows you to cast or convert data types, but careful, using a projection will only retrieve the fileds you specify in it, and the resulting list will come up like this:
List<Object[]> results = this.executeQuery(query);
But you can make hibernate do a alias match with the properties using a result transformer:
query.setResultTransformer(new AliasToBeanResultTransformer(Session.class));
and the list comes out like it normally does:
List<Session.class> results = this.executeQuery(query);
Sorry i could not provide a criteriaBuilder solution, but i hope this gets you in the right track.

After some investigation, it turned out, that HQL does not support TO_DAYS. Since I want to make it possible for MySQL and other databases, this is my final solution:
Query q = entityManager.createQuery("SELECT concat(day(e.time), '-', month(e.time), '-', year(e.time)) AS days, COUNT(*) FROM Event e GROUP BY concat(day(e.time), '-', month(e.time), '-', year(e.time))");
The result is:
3-5-2012|980
4-5-2012|200
10-6-2012|123
12-6-2012|144
13-11-2012|500
So afterwards I convert all ugly date strings into proper milliseconds in java and have the data, which I need.

Related

Linq to EF not returning all data

I have following query against EF whereby mysql was used:
var query = from r in context.myContext
where r.clmn1.CompareTo("2015-11-19 00:00:00") > 0)
orderby r.someColumn
select r;
return query;
The number of returned rows is as expected. however some values of the property r.clmn2 repeat itself in the result of the query. For example I could not find clmn2 == 220011 because it was "overwritten" by the value 220033 (The value 220033 is correct and expected but should not "overwrite" other values). Strangely enough, when I add this condition to the query I get it in the result (of course then only and only this value) which means that the first condition is also valid for clmn2:
var query = from r in context.myContext
where r.clmn1.CompareTo("2015-11-19 00:00:00") > 0) && r.clmn2.Equals("220011")
orderby r.someColumn
select r;
return query;
The same query (the first one) works at DB-level and returns all values (will not be overwritten)
SELECT * FROM myContext.myTable
WHERE r.clmn1 > ("2015-11-19 00:00:00")
ORDER BY r.someColumn
It should be a problem of EF. I hope someone could help me!
Thanks in Advance.
I have prefixed the column/property clmn2 with [key] atribute in the generated entity class so that it is now a part of the multiple key, i.e., with other columns/properties. It works and i get all values from DB. Maybe cus this property comes from a DB-view, Visual Studio could not recognize it as a primary key as done by other properties.

JPA2/Hibernate Criteria or CriteriaQuery for Postgres JSON type

I have a PostgreSQL database with JSON fields. I would like to construct a query which restricts results by JSON expressions. I can formulate this query in psql without problem:
select * from mytable where relation_id=100 AND CAST(jsonField->'key' AS float) >= 10.0;
This query combines a normal column and a JSON column.
I have no idea how to start this in Hibernate using Criteria or Criteria query. I could, in theory, use HSQL language, but I am almost certain that will fail when it comes to the JSON column.
Does anyone have an idea how to tackle this?
The only way to do that is to write a custom SQLFunction for the CAST(jsonField->'key' AS float) expression and then use that in JPQL.
public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException {
return "CAST(" + args.get(0).toString() + "->'" + args.get(1).toString() + "' AS float)";
}
Register in the dialect with registerFunction("json_float", new JsonFloatFunction()) and use it in the query like
SELECT o FROM MyTable o WHERE o.relation.id = 100 AND JSON_FLOAT(o.jsonField, 'key') >= 10.0

zend framework automatically alter queries

My database (mysql) tables use TIMESTAMP columns, and whenever I want them returned in a query, I want them to be queried as "UNIX_TIMESTAMP(columnname)".
How do you easily modify queries in zend framework to achieve this?
For example, the current code is:
select = $this->select();
$select->where('user_id = ?',$user_id);
return $this->fetchAll($select);
This eventually becomes:
select * from tablename where user_id = 42;
I want something that automatically finds the TIMESTAMP column and changes the resulting query to:
select user_id,name,unix_timestamp(created) where user_id = 42;
I know I can use a MySQL view to achieve this, but I'd rather avoid that.
Thanks.
RR
You should be able to specify the fields you want in the select using the $select->from() object.
Zend_Db_Select
You should end up with something like this.
$select = $this->select();
$select->from(
array('t' => 'tablename'),
array('user_id', 'name', 'UNIX_TIMESTAMP(created)')
);
$select->where('user_id = ?',$user_id);
return $this->fetchAll($select);
If you wanted to run an expression that doesn't have parenthese in the function, Use the Zend_Db_Expr() method to escape the query properly.

ActiveRecord + SQLite 3 behaves strangely

Using activerecord I made this query
AdImage.select("ad_images.id, ad_images.locale_id, ad_campaigns.click_url,
ad_campaigns.default_ad_image_id").joins("left outer join ad_campaigns on
ad_campaigns.id = ad_images.ad_campaign_id").where("ad_images.ad_campaign_id" => 1)
which generates the following sql query:
SELECT ad_images.id, ad_images.locale_id, ad_campaigns.click_url,
ad_campaigns.default_ad_image_id FROM "ad_images" left outer join ad_campaigns on
ad_campaigns.id = ad_images.ad_campaign_id WHERE "ad_images"."ad_campaign_id" = 1
and the result is the following:
=> [#<AdImage id: 22, click_url: "market://details?id=com.mobiata.flighttrack",
locale_id: 2>]
which is wrong.
So I used ActiveRecord::Base.connection.execute method to directly run the sql query:
ActiveRecord::Base.connection.execute("SELECT ad_campaigns.click_url, ad_images.id,
ad_images.locale_id, ad_campaigns.default_ad_image_id FROM ad_campaigns inner join
ad_images on ad_campaigns.id = ad_images.ad_campaign_id WHERE ad_images.ad_campaign_id = 1")
which returns the following:
[{"click_url"=>"market://details?id=com.mobiata.flighttrack", "id"=>22, "locale_id"=>2,
"default_ad_image_id"=>22, 0=>"market://details?id=com.mobiata.flighttrack", 1=>22,
2=>2, 3=>22}]
which has the strange repetition in it.
The only difference between the first and the second is "ad_images" vs ad_images in the table names.
My questions are:
1) I don't understand what makes this difference.
2) Why does the second query returns the garbage in SQLite3 while it doesn't happen in MySQL server?
I ended up with using "ActiveRecord::Base.connection.execute" instead of using Rails' ActiveRecord helpers. There doesn't seem to be other solutions to it.
It turns out that you should use the index values instead of double quoted column names when you call values. Otherwise you will bump into Type errors when used in production with MySQL.

Could not format node 'Value' for execution as SQL

I've stumbled upon a very strange LINQ to SQL behaviour / bug, that I just can't understand.
Let's take the following tables as an example: Customers -> Orders -> Details.
Each table is a subtable of the previous table, with a regular Primary-Foreign key relationship (1 to many).
If I execute the follow query:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Then I get an exception: Could not format node 'Value' for execution as SQL.
But the following queries do not throw an exception:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).OrderDateTime;
var q = from c in context.Customers
select (new Order()).Details.Count();
If I change my primary query as follows, I don't get an exception:
var q = from r in context.Customers.ToList()
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Now I could understand that the last query works, because of the following logic:
Since there is no mapping of "new Order()" to SQL (I'm guessing here), I need to work on a local list instead.
But what I can't understand is why do the other two queries work?!?
I could potentially accept working with the "local" version of context.Customers.ToList(), but how to speed up the query?
For instance in the last query example, I'm pretty sure that each select will cause a new SQL query to be executed to retrieve the Orders. Now I could avoid lazy loading by using DataLoadOptions, but then I would be retrieving thousands of Order rows for no reason what so ever (I only need the first row)...
If I could execute the entire query in one SQL statement as I would like (my first query example), then the SQL engine itself would be smart enough to only retrieve one Order row for each Customer...
Is there perhaps a way to rewrite my original query in such a way that it will work as intended and be executed in one swoop by the SQL server?
EDIT:
(longer answer for Arturo)
The queries I provided are purely for example purposes. I know they are pointless in their own right, I just wanted to show a simplistic example.
The reason your example works is because you have avoided using "new Order()" all together. If I slightly modify your query to still use it, then I still get an exception:
var results = from e in (from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, Count = (e.FirstOrder != null ? e.FirstOrder : new Order()).Details().Count() }
Although this time the exception is slightly different - Could not format node 'ClientQuery' for execution as SQL.
If I use the ?? syntax instead of (x ? y : z) in that query, I get the same exception as I originaly got.
In my real-life query I don't need Count(), I need to select a couple of properties from the last table (which in my previous examples would be Details). Essentially I need to merge values of all the rows in each table. Inorder to give a more hefty example I'll first have to restate my tabels:
Models -> ModelCategoryVariations <- CategoryVariations -> CategoryVariationItems -> ModelModuleCategoryVariationItemAmounts -> ModelModuleCategoryVariationItemAmountValueChanges
The -> sign represents a 1 -> many relationship. Do notice that there is one sign that is the other way round...
My real query would go something like this:
var q = from m in context.Models
from mcv in m.ModelCategoryVariations
... // select some more tables
select new
{
ModelId = m.Id,
ModelName = m.Name,
CategoryVariationName = mcv.CategoryVariation.Name,
..., // values from other tables
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia = cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId) ?? new ModelModuleCategoryVariationItemAmount()
select new
{
cvi.Id,
Amount = (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.FirstOrDefault() ?? new ModelModuleCategoryVariationItemAmountValueChange()).Amount
... // select some more properties
}
}
This query blows up at the line let mmcvia =.
If I recall correctly, by using let mmcvia = new ModelModuleCategoryVariationItemAmount(), the query would blow up at the next ?? operand, which is at Amount =.
If I start the query with from m in context.Models.ToList() then everything works...
Why are you looking into only the individual count without selecting anything related to the customer.
You can do the following.
var results = from e in
(from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, DetailCount = e.FirstOrder != null ? e.FirstOrder.Details.Count() : 0 };
EDIT:
OK, I think you are over complicating your query.
The problem is that you are using the new WhateverObject() in your query, T-SQL doesnt know anyting about that; T-SQL knows about records in your hard drive, your are throwing something that doesn't exist. Only C# knows about that. DON'T USE new IN YOUR QUERIES OTHER THAN IN THE OUTER MOST SELECT STATEMENT because that is what C# will receive, and C# knows about creating new instances of objects.
Of course is going to work if you use ToList() method, but performance is affected because now you have your application host and sql server working together to give you the results and it might take many calls to your database instead of one.
Try this instead:
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia =
cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(
mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId)
select new
{
cvi.Id,
Amount = mmcvia != null ?
(mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.Select(
x => x.Amount).FirstOrDefault() : 0
... // select some more properties
}
Using the Select() method allows you to get the first Amount or its default value. I used "0" as an example only, I dont know what is your default value for Amount.