Fusion of two queries in active record rails - sql

How could these two queries be merged in Rails 5?
Event
.where(starts_at: date.beginning_of_day..date.end_of_day)
.where(kind: "opening")
Event.where("cast(strftime('%w', starts_at) as int) = ?", date.wday)
.where(kind: "opening")
.where(weekly_recurring: true)
I need to take all these events in one query for performance.
Thanks for the help

If you want to combine results then
Event.where(starts_at: date.beginning_of_day..date.end_of_day).where(kind: "opening").where("cast(strftime('%w', starts_at) as int) = ?", date.wday).where(kind: "opening").where(weekly_recurring: true)
If you want two separate results in one query, i dont think active records has such method.

I have always easier to write complex queries such as the one you need directly in SQL; It will be faster for you to write than trying to shoehorn it into ActiveRecord.
In your case, I would do something like this:
query = "starts_at between #{date.beginning_of_day} and #{date.end_of_day}
and kind = 'opening'
and weekly_recurring = true
and cast(strftime('%w', starts_at) as int) = #{date.wday}"
Event.where(query)
I find this approach easier and more maintanable.

Related

Combine 2 results in one in SQL

Update merge
Cama::PostType.first.posts.joins(:custom_field_values)
.where("cama_custom_fields_relationships.custom_field_slug = ? AND
cama_custom_fields_relationships.value LIKE ?","localization",
"%Paris%").merge(Cama::PostType.first.posts.joins(:custom_field_values)
.where("cama_custom_fields_relationships.custom_field_slug = ? AND
cama_custom_fields_relationships.value = ?","type-localization", "2"))
This also doesnt work. When executed seperatelty, it returns same AssociationRelation. I guess it only works for ActiveRecord:Relation
Update
I think Im looking for INTERSECT but don't know how to use it with where
There is another topic that I created and still can't find answer how to optimize it.
It goes likes this
I need to find "posts" by "other_model" values. Other model has relationship with posts throught another table but lets keep it simple. When i do
Foo.joins(:other_model).where("other_model.value = ? AND other_model.value = ?", "one", "two")
This of course won't find me any result because it contradicts itself.
When I do with OR instead of AND
Foo.joins(:other_model).where("other_model.value = ? OR other_model.value = ?", "one", "two")
It finds posts for me but... either it has one value or either has second value and...
I want to find posts based on other_model.value = one and other_model.value = two
Which means it looks for 2 seperate results and then I need to just return ids that covers each other... Does it make sense ?
I think you are looking for a query like:
Foo
.joins(:other_model)
.where('other_model.value = ? OR other_model.value = ?', 'one', 'two')
.group('foos.id')
.having('COUNT(other_models.id) >= 2')

Django Queries: related subquery

I have 3 Models: Offer, Request and Assignment. Assignment makes a connection between Request and Offer. Now I want to do this:
select *
from offer as a
where places > (
select count(*)
from assignment
where offer_id = a.id and
to_date > "2014-07-07");
I am not quiet sure how to achieve this with a django QuerySet... Any tips?
Edit: The query above is just an example, how the query in general should look like. The django model looks like this:
class Offer(models.Model):
...
places = models.IntegerField()
...
class Request(models.Model):
...
class Assignment(models.Model):
from_date = models.DateField()
to_data = models.DateField()
request = models.ForeignKey("Request",related_name="assignments")
offer = models.ForeignKey("Offer",related_name="assignments")
People now can create a offer with a given amount of places or a request. The admin then will connect a request with an offer for a given time. This is saved as an assignment. The query above should give me a list of offers, which have still places left. Therefore I want to count the number of valid assignments for a given offer to compare it with its number of places. This list should be used to find a possible offer for a given request to create a new assignment.
I hope this describes the problem better.
Unfortunately related subqueries aren't directly supported by ORM operations. Usage of .extra(where=...) should be possible in this case.
To get the same results without using a subquery something like the following should work:
Offer.objects.filter(
assignment__to_date__gt=thedate
).annotate(
assignment_cnt=Count('assignment')
).filter(
assignment_cnt__lte=F('places')
)
The exact query depends on the model definitions.
query = '''select *
from yourapp_offer as a
where places > (
select count(*)
from yourapp_assignment
where offer_id = a.id and
to_date > "2014-07-07");'''
offers = Offer.objects.raw(query):
https://docs.djangoproject.com/en/1.6/topics/db/sql/

ActiveRecord condition with count less than for association

I have a User that has_many messages.
I need a create a query that will
'Get me all users who's (message.opened == false) count < 3'
Right now, I am using User.all, iterating through all users, and counting manually. I understand that this isn't very efficient and it can be all done in one query, but I am new to SQL/ActiveRecord so need some help here.
Thanks
Assuming Rails 3 syntax. You can do something like:
User.joins(:messages).where(:messages => {:opened => false}).group(:user_id).having("COUNT(messages.id) < 3)
This should work:
User.includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
This will create two queries, one for the grouped query, and one for eager loading the resulting users and messages with a join.
Here is solution to your problem
User.includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
but what else you can do is to create a scope for this
scope :not_opened_less_three_count, includes(:messages).group("users.id").where("messages.opened = 0").having("count(messages.id) < 3")
And then you can use it anywhere you needed as follow
User.not_opened_less_three_count
Try this
User.includes(:messages).group('users.id').having('SUM(IFNULL(messages.opened = 0, 1)) < 3')
It works at least on MySQL, AND assuming your boolean true are 1 in database.
EDIT I had reversed the condition
PS IFNULL is there to handle if messages.opened can be NULL

Complex subqueries in activerecord

I'm doing a rails app. I have to do a comparison engine a bit complex. I'm currently trying to do a prototype. My query can vary widely so i have to work with a lot of scopes, but that's not my problem.
My query have to compare candidates. These candidates have answered some tests. These tests belongs to category. Theses tests have different max value, and i have to be able to compare candidates by categories.
So i have to calculate a % of good answers. I have to be able to compare candidates in all possible use cases in one category. So, i have to be able to compare the average good answer rate for all this category.
In a nutshell : I have to be able to use subqueries in order to compare some candidates. I have to be able to compare them for a test or a category. My problem is using a subquery able to return a good answer rate for all tests a candidats may have passed in a category.
And I have to be able to use this subquery in an order_by or having clause.
How can I construct this subquery ? I have no problem to handle complex conditional queries with some scopes. This has to be a real subquery, because I am working with 6 or 7 models here.
I ask for an active record way, cause this must work with whatever database supported by rails.
Excuse my poor English.
Edit :
An example is worth 1000 words so how could do something like this :
Sessiontest.find(Candidat.where(:firstname => 'toto'))
This example is stupid, ok. So, is it possible to do something like this ?
Edit2 :
I saw some posts about AREL. I wish to know if it is possible to do this without a third party plugin.
Is it possible to do some sub queries in subqueries with arel? Because for example, my number of points per test, is the sum of the points of all his questions. (Sad, but I have to keep it). And I need this, so my subquery can calculate my good answers %.
So you got the idea. That's something, which has to be really powerful, so I need something powerful, and not too much error prone.
Edit3 : I made some progress, but I can't for a while post an answer.
It seem possible to get this work without any plugin. I have some success in buildings some subqueries like this :
toto = Candidat.where(:lastname => Candidat.select(:lastname).where(:lastname => "ulysse").limit(1))
The request :
Candidat Load (1.0ms)[0m SELECT "candidats".* FROM "candidats" WHERE "candidats"."lastname" IN (SELECT "candidats"."id" FROM "candidats" WHERE "candidats"."lastname" = 'ulysse' LIMIT 1
This works and create a real subquery. I will try some more advanced experiences, in order to get the level I actually need.
Just tried sub-subquery works wonder too.
Edit 5 :
I am trying some more advanced things, and there is a lot of things, i still don't understand.
- toto = Candidat.where("id = ? / ? ", Sessiontest.select(:id).where(:id => 6), Sessiontest.select(:id).where(:id => 2))
This is just a stupid example in order to get an object with an id of 3. This code works, but not as i expected.
See, the sql :
1m[35m (1.0ms)[0m SELECT COUNT("sessiontests"."id") FROM "sessiontests" WHERE "sessiontests"."id" = 6
[1m[36mSessiontest Load (0.0ms)[0m [1mSELECT id FROM "sessiontests" WHERE "sessiontests"."id" = 6[0m
[1m[35m (1.0ms)[0m SELECT COUNT("sessiontests"."id") FROM "sessiontests" WHERE "sessiontests"."id" = 2
[1m[36mSessiontest Load (1.0ms)[0m [1mSELECT id FROM "sessiontests" WHERE "sessiontests"."id" = 2[0m
[1m[35mCandidat Load (1.0ms)[0m SELECT "candidats".* FROM "candidats" WHERE (id = 6 / 2)
So, it does not use a subqueries. I tried with .to_sql. But it introduce my sql this way :
1m[36mCandidat Load (0.0ms)[0m [1mSELECT "candidats".* FROM "candidats" WHERE (id = 'SELECT id FROM "sessiontests" WHERE "sessiontests"."id" = 6' / 2 )[0m
So active record quoted the subreust for security purpose. this is closer to my wish, but not really what i want.
This does not work
Candidat.where("id = (?) / ? ", Sessiontest.select(:id).where(:id => 6).to_sql, Sessiontest.select(:id).where(:id => 2))
Quotes prevents the subquery to work.
But this work :
Candidat.where("id = (" + Sessiontest.select(:id).where(:id => 6).to_sql + ") / (" + Sessiontest.select(:id).where(:id => 2).to_sql + ") ")
[1m[36mCandidat Load (1.0ms)[0m [1mSELECT "candidats".* FROM "candidats" WHERE (id = (SELECT id FROM "sessiontests" WHERE "sessiontests"."id" = 6) / (SELECT id FROM "sessiontests" WHERE "sessiontests"."id" = 2) )[0m
But I find this ugly. I will try to get these subqueries working in a more dynamic way. I mean replace the integer values by columns name.
I don't have anymore the exact answer to this question, because i do not work in the same enterprise anymore. But the solution to this problem, was to use a group_by clause. So the request became really easy.
With a group_by, i was able to manipulate, category or a technology with ease.

Limit models to select

I have a database table called Event which in CakePHP has its relationships coded to like so:
var $belongsTo = array('Sport');
var $hasOne = array('Result', 'Point', 'Writeup', 'Timetable', 'Photo');
Now am doing a query and only want to pull out Sport, Point, and Timetable
Which would result in me retrieving Sports, Events, Points, and Timetable.
Reason for not pulling everything is due the results having 17000+ rows.
Is there a way to only select those tables using:
$this->Event->find('all');
I have had a look at the API but can't see how its done.
You should set recursive to -1 in your app_model and only pull the things you require. never use recursive of 2 and http://book.cakephp.org/view/1323/Containable is awesome.
just $this->Event->find('all', array('contain' => array()));
if you do the trick of recursive as -1 in app_model, this is not needed, if would just be find('all') like you have