Where on hasmany in sequelize with count - sql

I am not the best at UML/database diagrams but the following hopefully shows my DB design (MsSQL)
I have a "Location" table with zero to many pallets assiociated with it (there are 0-many pallets IN a location). However that location can be one of two types, location x or y. This diagram simplifies it, but there are many different types of location, and very different fields for each type.
I am using Sequelize as an ORM, and trying to figure out how to do a particular query. I think I am close but quite stuck.
What I need is:
Select a single LocationTypeX where "active" is true, and where its corresponding Location has less than 10 pallets in it.
Previously I have gone and got all LocationTypeX where "active" is true. Included Location and Pallet (on location) and did it all in code to figure out which location is relavent. however that is taking forever, as there are thousands of Locations and loads of pallets spread out through them.
All I am after is to show the Location Name. That is it. But the Location name of one that matches the above condition. Hopefully someone can help?
So far I have
models.Location.findAll({
group: ['Location.id', 'Pallets.id'],
attributes: ['Location.id', 'Pallets.id', [models.sequelize.fn('COUNT',models.sequelize.col('Pallets.id')), 'PalletCount']],
include: [{
model: models.Pallet,
attributes: []
}]
}).then((ret)=> {
console.log(ret);
});
But this doesn't do the "active" check. And also doesn't do the where clause on the amount of pallets. Back to square one

I have managed to answer my own question with raw SQL that I can use in Sequelize. However I would still be interested to know how to do it in Sequelize
models.sequelize.query(
`SELECT TOP 1 Locations.id, Locations.name FROM LocationTypeX
INNER JOIN Locations ON LocationTypeX.LocationId = Locations.id
LEFT JOIN Pallets on (Locations.id = Pallets.LocationId)
WHERE LocationTypeX.active = 1
GROUP BY Locations.id, Locations.name
HAVING COUNT(Pallets.id) < 3`
, { type: models.sequelize.QueryTypes.SELECT})

Related

Update list of dates in SQL

I have a controller to make a room which needs a JsonBody in order to add the room:
{
"roomName": "Sol",
"properties": "Geluidsdichte kamer",
"capacity": 40,
"buildingName": "16A",
"location": "Leuven",
"reservableDates": ["2022-12-03", "2022-12-04", "2022-12-05"],
"imageUrl":"www"
}
Here we find a reservableDates object which is just a list of dates when the room is available for reservation
Now the backend code to put this code into the database isn't relevant for my problem so I will not state this here.
However the output I get in my database is this...
select * from rooms inner join rooms_reservable_dates
on room_id = rooms_room_id;
Now I have another function in my backend so I can update a room (For example change its available reservable dates, but the problem is that I don't know how to write the query so I can change the reservable dates while also updating the roomName for example.
I'm using JpaRepository in SpringBoot so I have to make a custom query for this.
In Postgresql I have 2 tables Rooms (with all the properties found in the picture except for the reservable_dates) and the other table is rooms_reservable_dates (which has the roomId and the dates that the room is available.
Thank you very much

How to build an efficient select command across multiples tables in Rails4?

Trying to figure out an efficient ways to select records that have attributes across multiple tables. Here's the basic setup:
structure
Plants (fields: id, name_id, location_id, color) (1000 records)
Names (fields: id, Common_name) (50 records)
Location (fields: id, Bed_name) (125 records)
model
Plants - belongs_to Names, belongs_to Location
Names - has_many Plants
Location - has_many Plants
My goal is to output a list of every Rose in the side yard, and display the color, but I am stuck on the select command. If I get all plants (p = Plant.all) I know that I can easily create my output with a statement like <%= "#{p.name.common_name} in bed #{p.location.bed_name} has a color of #{p.color}" %>
If I do two joins I'm looking at way more records that I need and a MUCH longer search time. As an example - I have 67 roses in 16 different beds, however, I only have 3 roses in the side yard.
My gut tells me that I should be able to do something like:
select all plants with the name of Rose, then from this selection select all Roses that are in the side yard.
Can anybody help point me in the correct direction?
You can combine it all into a single query like this:
Plant.joins(:name, :location).where(names: { common_name: "rose" }, locations: { bed_name: "side" })
This results in a single SQL query like this:
SELECT "plants".* FROM "plants" INNER JOIN "names" ON "names"."id" = "plants"."name_id" INNER JOIN "locations" ON "locations"."id" = "plants"."location_id" WHERE "names"."common_name" = 'rose' AND "locations"."bed_name" = 'side'
Note that you have to use the plural table names in the where clause, but the singular association name in the joins clause.
This will run nearly instantaneously even with enormous tables, assuming your tables are properly indexed.
This is a simple example, but you can do fairly complex joins with conditions. Full details can be found in the ActiveRecord documentation.
Edit
Per #Dan's comment, you can speed this up more by using includes to pre-fetch the association data in the join:
Plant.includes(:name, :location).where(names: { common_name: "rose" }, locations: { bed_name: "side" })
This will load the related records from names and locations at the same time. includes is handy for eliminating (or at least reducing) N+1 queries. It is also smart enough to know when it can retrieve all the data in a single query, and falls back to multiple queries when that makes more sense; you don't have to think about it (although sometimes it can reduce efficiency, so keep an eye on your logs if you think it's reduces performance).
Using includes in this case is very efficient, resulting in a single SQL query which includes association data:
SELECT "plants"."id" AS t0_r0, "plants"."color" AS t0_r1, "plants"."name_id" AS t0_r2, "plants"."location_id" AS t0_r3, "plants"."created_at" AS t0_r4, "plants"."updated_at" AS t0_r5, "names"."id" AS t1_r0, "names"."common_name" AS t1_r1, "names"."created_at" AS t1_r2, "names"."updated_at" AS t1_r3, "locations"."id" AS t2_r0, "locations"."bed_name" AS t2_r1, "locations"."created_at" AS t2_r2, "locations"."updated_at" AS t2_r3 FROM "plants" LEFT OUTER JOIN "names" ON "names"."id" = "plants"."name_id" LEFT OUTER JOIN "locations" ON "locations"."id" = "plants"."location_id" WHERE "names"."common_name" = 'rose' AND "locations"."bed_name" = 'side'

Ordering a found set by number of times a user has viewed the page

I'm trying to order a list of locations based on the number of times a user has viewed them. Am using the impressionist gem for the sake of it.
The problem I'm having is that my query completely excludes those locations the user's never viewed. I need to display these at the bottom of the results and order by the created_at timestamp.
I can do this to get a list of location_ids:
#location_ids = #user.impressions.
select('count(id) as counter, impressionable_id').
group(:impressionable_id).
order('counter DESC').
#location_ids.map(&:impressionable_id)
Which gives [3,5,8,44,99] and so on..
However, that doesn't get me far so I tried this:
#user.locations.
joins(:impressions).
select("count(impressions.id) as counter, impressionable_id, locations.location_name, locations.id").
group(:impressionable_id).
order("counter desc")
Which is better but it omits those locations with zero views.
How should I do this to get all the locations?
By default, Rails uses an inner join when you use .joins. That's why you don't see the locations with no associated impressions. You need to tell it to use a left join instead, probably like so:
#user.locations.
joins("left join impressions on impressions.impressionable_id = locations.id and impressions.impressionable_type = 'Location'").
select("count(impressions.id) as counter, impressionable_id, locations.location_name, locations.id").
group('locations.id').
order("counter desc")

nHibernate collections and alias criteria

I have a simple test object model in which there are schools, and a school has a collection of students.
I would like to retrieve a school and all its students who are above a certain age.
I carry out the following query, which obtains a given school and the children which are above a certain age:
public School GetSchoolAndStudentsWithDOBAbove(int schoolid, DateTime dob)
{
var school = this.Session.CreateCriteria(typeof(School))
.CreateAlias("Students", "students")
.Add(Expression.And(Expression.Eq("SchoolId", schoolid), Expression.Gt("students.DOB", dob)))
.UniqueResult<School>();
return school;
}
This all works fine and I can see the query going to the database and returning the expected number of rows.
However, when I carry out either of the following, it gives me the total number of students in the given school (regardless of the preceding request) by running another query:
foreach (Student st in s.Students)
{
Console.WriteLine(st.FirstName);
}
Assert.AreEqual(s.Students.Count, 3);
Can anyone explain why?
You made your query on the School class and you restricted your results on it, not on the mapped related objects.
Now there are many ways to do this.
You can make a static filter as IanL said, however its not really flexible.
You can just iterate the collection like mxmissile but that is ugly and slow (especially considering lazy loading considerations)
I would provide 2 different solutions:
In the first you maintain the query you have and you fire a dynamic filter on the collection (maintaining a lazy-loaded collection) and doing a round-trip to the database:
var school = GetSchoolAndStudentsWithDOBAbove(5, dob);
IQuery qDob = nhSession.CreateFilter(school.Students, "where DOB > :dob").SetDateTime("dob", dob);
IList<Student> dobedSchoolStudents = qDob.List<Student>();
In the second solution just fetch both the school and the students in one shot:
object result = nhSession.CreateQuery(
"select ss, st from School ss, Student st
where ss.Id = st.School.Id and ss.Id = :schId and st.DOB > :dob")
.SetInt32("schId", 5).SetDateTime("dob", dob).List();
ss is a School object and st is a Student collection.
And this can definitely be done using the criteria query you use now (using Projections)
Unfortunately s.Students will not contain your "queried" results. You will have to create a separate query for Students to reach your goal.
foreach(var st in s.Students.Where(x => x.DOB > dob))
Console.WriteLine(st.FirstName);
Warning: That will still make second trip to the db depending on your mapping, and it will still retrieve all students.
I'm not sure but you could possibly use Projections to do all this in one query, but I am by no means an expert on that.
You do have the option of filtering data. If it there is a single instance of the query mxmissle option would be the better choice.
Nhibernate Filter Documentation
Filters do have there uses, but depending on the version you are using there can be issues where filtered collections are not cached correctly.

Rails (or maybe SQL): Finding and deleting duplicate AR objects

ActiveRecord objects of the class 'Location' (representing the db-table Locations) have the attributes 'url', 'lat' (latitude) and 'lng' (longitude).
Lat-lng-combinations on this model should be unique. The problem is, that there are a lot of Location-objects in the database having duplicate lat-lng-combinations.
I need help in doing the following
Find objects that share the same
lat-lng-combination.
If the 'url' attribute of the object
isn't empty, keep this object and delete the
other duplicates. Otherwise just choose the
oldest object (by checking the attribute
'created_at') and delete the other duplicates.
As this is a one-time-operation, solutions in SQL (MySQL 5.1 compatible) are welcome too.
If it's a one time thing then I'd just do it in Ruby and not worry too much about efficiency. I haven't tested this thoroughly, check the sorting and such to make sure it'll do exactly what you want before running this on your db :)
keep = []
locations = Location.find(:all)
locations.each do |loc|
# get all Locations's with the same coords as this one
same_coords = locations.select { |l| l.lat == loc.lat and \
l.lng == loc.lng }
with_urls = same_coords.select { |l| !l.url.empty? }
# decide which list to use depending if there were any urls
same_coords = with_urls.any? ? with_urls : same_coords
# pick the best one
keep << same_coords.sort { |a,b| b.created_at <=> a.created_at }.first.id
end
# only keep unique ids
keep.uniq!
# now we just delete all the rows we didn't decide to keep
locations.each do |loc|
loc.destroy unless keep.include?( loc.id )
end
Now like I said, this is definitely poor, poor code. But sometimes just hacking out the thing that works is worth the time saved in thinking up something 'better', especially if it's just a one-off.
If you have 2 MySQL columns, you can use the CONCAT function.
SELECT * FROM table1 GROUP BY CONCAT(column_lat, column_lng)
If you need to know the total
SELECT COUNT(*) AS total FROM table1 GROUP BY CONCAT(column_lat, column_lng)
Or, you can combine both
SELECT COUNT(*) AS total, table1.* FROM table1
GROUP BY CONCAT(column_lat, column_lng)
But if you can explain more on your question, perhaps we can have more relevant answers.