Get rows based on maximum value for subsets of a table - java

I want to filter a table based on the values of one column, then get the maximum value for each of these values.
e.g.
id | value
-----------
0 | 10
0 | 22
0 | 50
1 | 33
1 | 4
2 | 5
2 | 23
2 | 33
3 | 22
3 | 50
Filter by rows with IDs 2 and 3, then get the maximum of each id
id | value
-----------
2 | 33
3 | 50
How do I use that using hibernate?
This is my attempt:
List<int> ids = ... // Retreived from elsewhere
Disjunction disjunction = Restrictions.disjunction();
for(int id: ids){
disjunction.add(Restrictions.eq("id", id)); // Specify which IDs
}
#SuppressWarnings("unchecked")
List<Item> items= (List<Item>) sessionFactory.getCurrentSession()
.createCriteria(Item.class)
.add(disjunction)
.setProjection(
Projections.projectionList()
.add(Projections.max("value"))
.add(Projections.groupProperty("id")
)
)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
This is just giving me the 'id' with the highest value (e.g. 3, not the entire row)
I am trying to do this in a spring mvc app.
Thanks in advance

select MAX(id),max(value) from ABCD where id in (110,56001) group by id
Try this Query to execute in the form to get the expected output.

You can specify a WHERE clause in your CriteriaQuery, and then do a multiselect to do the GROUP BY:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<Item> item = query.from(Item.class);
query.where(cb.equal(item.get("id"), 2));
query.where(cb.equal(item.get("id"), 3));
query.multiselect(item.get("id"), item.max("value")).groupBy(item.get("id"));
List<Object[]> results = em.createQuery(query).getResultList();
System.out.println("id | value\n-----------");
for(Object[] object : results){
System.out.println(object[0] + " | " + object[1]);
}

Related

Groovy createCriteria issue with joined table

I have a domain class Coach which has a has many relationship to another domain class CoachProperty.
Hibernate/Grails is creating a third joined table in the database.
In the example below I am trying to fetch the coaches which both have foo AND bar for their text value. I have tried different solutions with 'or' and 'and' in Grails which either returns an empty list or a list with BOTH foo and bar.
Coach:
class Coach {
static hasMany = [ coachProperties : CoachProperty ]
CoachProperty:
class CoachProperty {
String text
boolean active = true
static constraints = {
text(unique: true, nullable: false, blank: false)
}
}
Joined table which is being auto-created and I populated with some data, in this example I am trying to fetch coach 372 since that coach has both 1 and 2 i.e foo and bar:
+---------------------------+-------------------+
| coach_coach_properties_id | coach_property_id |
+---------------------------+-------------------+
| 150 | 2 |
| 372 | 1 |
| 372 | 2 |
| 40 | 3 |
+---------------------------+-------------------+
Inside Coach.createCriteria().list() among with other filters. This should return coach 372 but return empty:
def tempList = ["foo", "bar"]
coachProperties{
for(String temp: tempList){
and {
log.info "temp = " + temp
ilike("text",temp)
}
}
}
I seem to remember this error. Was something about not being able to use both nullable & blank at the same time.Try with just 'nullable:true'
I had to create a workaround with executeQuery where ids is the list containing the id's of the coachproperties i was trying to fetch.
def coaches = Coach.executeQuery '''
select coach from Coach as coach
join coach.coachProperties as props
where props.id in :ids
group by coach
having count(coach) = :count''', [ids: ids.collect { it.toLong()
}, count: ids.size().toLong()]
or{
coaches.each{
eq("id", it.id)
}
}

Spark and non-denormalized tables

I know Spark works much better with denormalized tables, where all the needed data is in one line. I wondering, if it is not the case, it would have a way to retrieve data from previous, or next, rows.
Example:
Formula:
value = (value from 2 year ago) + (current year value) / (value from 2 year ahead)
Table
+-------+-----+
| YEAR|VALUE|
+-------+-----+
| 2015| 100 |
| 2016| 34 |
| 2017| 32 |
| 2018| 22 |
| 2019| 14 |
| 2020| 42 |
| 2021| 88 |
+-------+-----+
Dataset<Row> dataset ...
Dataset<Results> results = dataset.map(row -> {
int currentValue = Integer.valueOf(row.getAs("VALUE")); // 2019
// non sense code just to exemplify
int twoYearsBackValue = Integer.valueOf(row[???].getAs("VALUE")); // 2016
int twoYearsAheadValue = Integer.valueOf(row[???].getAs("VALUE")); // 2021
double resultValue = twoYearsBackValue + currentValue / twoYearsAheadValue;
return new Result(2019, resultValue);
});
Results[] results = results.collect();
Is it possible to grab these values (that belongs to other rows) without changing the table format (no denormalization, no pivots ...) and also without collecting the data, or does it go totally against Spark/BigData principles?

SQL delete leaving the ID not deleted

When I use deleteRow method it deletes all the values of a row(imagine row 2 of a total 0f 5 rows) but when I use my getLastID method it still returns the lastID 5 as if there still are 5 numbers. Here are my methods
Method to delete all the values in the row whit the id given
public Integer deleteRow(String id){
return database.delete(TABLE_NAME,"ID=?",new String[]{id});
}
Method to getLastID
public int getLastId() {
String query = "SELECT MAX(id) AS max_id FROM " + TABLE_NAME;
Cursor cursor = database.rawQuery(query, null);
int id = 0;
if (cursor.moveToFirst())
{
do
{
id = cursor.getInt(0);
} while(cursor.moveToNext());
}
cursor.close();
return id;
}
Your delete just eliminates one row with the ID given, but that does not mean that the last row is deleted, imagine you have this table :
|---------------------|------------------|
| ID | other |
|---------------------|------------------|
| 1 | 34 |
|---------------------|------------------|
| 2 | 34 |
|---------------------|------------------|
| 3 | 34 |
|---------------------|------------------|
if you do max(id) it will return 3, if you delete the ID 2, you end up with the table like this :
|---------------------|------------------|
| ID | other |
|---------------------|------------------|
| 1 | 34 |
|---------------------|------------------|
| 3 | 34 |
|---------------------|------------------|
And your max(id) will keep returning 3 because that function just returns the higher value on the column.
The database is doing exactly what it is designed to do. It keeps the last used ID and increments it by one each time a new ID is assigned.
It does not keep a pool of the all the ID's assigned and attempt to reuse ones where the rows are deleted. That what be very complicated and slow.

MySQL query to fetch list of data using logical operations

The following are the list of different kinds of books that customers read in a library. The values are stored with the power of 2 in a column called bookType.
I need to fetch list of books with the combinations of persons who read
only Novel Or only Fairytale Or only BedTime Or both Novel + Fairytale
from the database with logical operational query.
Fetch list for the following combinations :
person who reads only novel(Stored in DB as 1)
person who reads both novel and fairy tale(Stored in DB as 1+2 = 3)
person who reads all the three i.e {novel + fairy tale + bed time} (stored in DB as 1+2+4 = 7)
The count of these are stored in the database in a column called BookType(marked with red in fig.)
How can I fetch the above list using MySQL query
From the example, I need to fetch users like novel readers (1,3,5,7).
The heart of this question is conversion of decimal to binary and mysql has a function to do just - CONV(num , from_base , to_base );
In this case from_base would be 10 and to_base would be 2.
I would wrap this in a UDF
So given
MariaDB [sandbox]> select id,username
-> from users
-> where id < 8;
+----+----------+
| id | username |
+----+----------+
| 1 | John |
| 2 | Jane |
| 3 | Ali |
| 6 | Bruce |
| 7 | Martha |
+----+----------+
5 rows in set (0.00 sec)
MariaDB [sandbox]> select * from t;
+------+------------+
| id | type |
+------+------------+
| 1 | novel |
| 2 | fairy Tale |
| 3 | bedtime |
+------+------------+
3 rows in set (0.00 sec)
This UDF
drop function if exists book_type;
delimiter //
CREATE DEFINER=`root`#`localhost` FUNCTION `book_type`(
`indec` int
)
RETURNS varchar(255) CHARSET latin1
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
begin
declare tempstring varchar(100);
declare outstring varchar(100);
declare book_types varchar(100);
declare bin_position int;
declare str_length int;
declare checkit int;
set tempstring = reverse(lpad(conv(indec,10,2),4,0));
set str_length = length(tempstring);
set checkit = 0;
set bin_position = 0;
set book_types = '';
looper: while bin_position < str_length do
set bin_position = bin_position + 1;
set outstring = substr(tempstring,bin_position,1);
if outstring = 1 then
set book_types = concat(book_types,(select trim(type) from t where id = bin_position),',');
end if;
end while;
set outstring = book_types;
return outstring;
end //
delimiter ;
Results in
+----+----------+---------------------------+
| id | username | book_type(id) |
+----+----------+---------------------------+
| 1 | John | novel, |
| 2 | Jane | fairy Tale, |
| 3 | Ali | novel,fairy Tale, |
| 6 | Bruce | fairy Tale,bedtime, |
| 7 | Martha | novel,fairy Tale,bedtime, |
+----+----------+---------------------------+
5 rows in set (0.00 sec)
Note the loop in the UDF to walk through the binary string and that the position of the 1's relate to the ids in the look up table;
I leave it to you to code for errors and tidy up.

Mongodb select all fields group by one field and sort by another field

We have collection 'message' with following fields
_id | messageId | chainId | createOn
1 | 1 | A | 155
2 | 2 | A | 185
3 | 3 | A | 225
4 | 4 | B | 226
5 | 5 | C | 228
6 | 6 | B | 300
We want to select all fields of document with following criteria
distict by field 'chainId'
order(sort) by 'createdOn' in desc order
so, the expected result is
_id | messageId | chainId | createOn
3 | 3 | A | 225
5 | 5 | C | 228
6 | 6 | B | 300
We are using spring-data in our java application. I tried to go with different approaches, nothing helped me so far.
Is it possible to achieve above with single query?
What you want is something that can be achieved with the aggregation framework. The basic form of ( which is useful to others ) is:
db.collection.aggregate([
// Group by the grouping key, but keep the valid values
{ "$group": {
"_id": "$chainId",
"docId": { "$first": "$_id" },
"messageId": { "$first": "$messageId" },
"createOn": { "$first": "$createdOn" }
}},
// Then sort
{ "$sort": { "createOn": -1 } }
])
So that "groups" on the distinct values of "messageId" while taking the $first boundary values for each of the other fields. Alternately if you want the largest then use $last instead, but for either smallest or largest by row it probably makes sense to $sort first, otherwise just use $min and $max if the whole row is not important.
See the MongoDB aggregate() documentation for more information on usage, as well as the driver JavaDocs and SpringData Mongo connector documentation for more usage of the aggregate method and possible helpers.
here is the solution using MongoDB Java Driver
final MongoClient mongoClient = new MongoClient();
final DB db = mongoClient.getDB("mstreettest");
final DBCollection collection = db.getCollection("message");
final BasicDBObject groupFields = new BasicDBObject("_id", "$chainId");
groupFields.put("docId", new BasicDBObject("$first", "$_id"));
groupFields.put("messageId", new BasicDBObject("$first", "$messageId"));
groupFields.put("createOn", new BasicDBObject("$first", "$createdOn"));
final DBObject group = new BasicDBObject("$group", groupFields);
final DBObject sortFields = new BasicDBObject("createOn", -1);
final DBObject sort = new BasicDBObject("$sort", sortFields);
final DBObject projectFields = new BasicDBObject("_id", 0);
projectFields.put("_id", "$docId");
projectFields.put("messageId", "$messageId");
projectFields.put("chainId", "$_id");
projectFields.put("createOn", "$createOn");
final DBObject project = new BasicDBObject("$project", projectFields);
final AggregationOutput aggregate = collection.aggregate(group, sort, project);
and the result will be:
{ "_id" : 5 , "messageId" : 5 , "createOn" : { "$date" : "2014-04-23T04:45:45.173Z"} , "chainId" : "C"}
{ "_id" : 4 , "messageId" : 4 , "createOn" : { "$date" : "2014-04-23T04:12:25.173Z"} , "chainId" : "B"}
{ "_id" : 1 , "messageId" : 1 , "createOn" : { "$date" : "2014-04-22T08:29:05.173Z"} , "chainId" : "A"}
I tried it with SpringData Mongo and it didn't work when I group it by chainId(java.lang.NumberFormatException: For input string: "C") was the exception
Replace this line:
final DBObject group = new BasicDBObject("$group", groupFields);
with this one:
final DBObject group = new BasicDBObject("_id", groupFields);
here is the solution using springframework.data.mongodb:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.group("chainId"),
Aggregation.sort(new Sort(Sort.Direction.ASC, "createdOn"))
);
AggregationResults<XxxBean> results = mongoTemplate.aggregate(aggregation, "collection_name", XxxBean.class);

Categories