ShiftNote belongs_to Shift has_many ShiftNote
I want to have scope ShiftNote.by_month("2013-10")
How to implement that?
Right now I am trying:
ShiftNote.includes(:shift)
.where("shifts.starts_at <= ? AND shifts.starts_at >= ?", "2013-10-01".to_date, "2013-10-30".to_date)
But I get
ShiftNote.includes(:shift).where("shifts.starts_at <= ? AND shifts.starts_at >= ?", "2013-10-01".to_date, "2013-10-30".to_date)
DEPRECATION WARNING: It looks like you are eager loading table(s) (one
of: shift_notes, shifts) that are referenced in a string SQL
snippet. For example:
Post.includes(:comments).where("comments.title = 'foo'")
Currently, Active Record recognizes the table in the string, and knows
to JOIN the comments table to the query, rather than loading comments
in a separate query. However, doing this without writing a full-blown
SQL parser is inherently flawed. Since we don't want to write an SQL
parser, we are removing this functionality. From now on, you must
explicitly tell Active Record when you are referencing a table from a
string:
Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
If you don't rely on implicit join references you can disable the
feature entirely by setting
config.active_record.disable_implicit_join_references = true. SQL
(1.6ms) SELECT shift_notes.id AS t0_r0, shift_notes.state AS
t0_r1, shift_notes.user_id AS t0_r2, shift_days.shift_id AS
t0_r3, shift_notes.created_at AS t0_r4, shift_notes.updated_at
AS t0_r5, shifts.id AS t1_r0, shifts.starts_at AS t1_r1,
shifts.ends_at AS t1_r2, shifts.rate AS t1_r3,
shifts.limit AS t1_r4, shifts.created_at AS t1_r5,
shifts.updated_at AS t1_r6, shifts.state AS t1_r7,
shifts.shift_notes_count AS t1_r8 FROM shift_notes LEFT OUTER
JOIN shifts ON shifts.id = shift_notes.shift_id WHERE
(shifts.starts_at <= '2013-10-01' AND shifts.starts_at >=
'2013-10-30')
You can use a BETWEEN query, which is achieved using a range of dates:
class ShiftNote < ActiveRecord::Base
scope :by_month, ->(year = Date.today.year, month = Date.today.month) do
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month + 1, 1) - 1
joins(:shifts)
.where('shifts.starts_at' => first_day..last_day)
end
Related
Long time reader, first time poster. (I did some deep dive searches, and was unable to find anything similar -- Thank you in advance)
I am coding a project using ruby and active record, and I ran into a situation where I'm not able to google search the answer to why two things are happening. From what I can tell, the first thing is happening because of a known bug with rails. The second, I do not know.
Here's the mock code:
class Object1 < ActiveRecord::Base
has_many :object2s, foreign_key: :object1_id, :dependent => :delete_all
end
class Object2 < ActiveRecord::Base
belongs_to :object1, class_name: "Object1"
end
Object2 has a unique index for object1_id and date.
I have an update that is failing due to a index violation:
ActiveRecord::RecordNotUnique in Object1Controller#datechange
Update code:
Object2.joins( :object1 ).where( object1: { :id => id } )**.order( obj2_date: ascdsc )**.update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")
The index is being tripped on the update without the order (** added above), the dates are being updated in a way that causes the violation. Elsewhere in the code, I have specified the update order, and it will update them in a way that will not violate the index. With this Object, adding in the join causes the first issue:
Here's the (mock) SQL generated:
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria> **ORDER BY object2.obj2_date ASC**
) __active_record_temp
)
If I modify the SQL, I can run it in an SQL client where it will work as expected. [Note: I moved the location of the order]
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
) __active_record_temp
) **ORDER BY object2.obj2_date ASC**
Question 1:
The order is being added to the wrong place. How do I get it right, or work around it?
I believe this to be related to this bug:
https://github.com/rails/rails/issues/6769
Question 2:
Why is this happening? ...select id from (select id from table) __temp_table...
WHERE object2.id IN (
SELECT id FROM (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria> **ORDER BY object2.obj2_date ASC**
) __active_record_temp
)
Wouldn't it be better for it to be this: ...select id from table...
WHERE object2.id IN (
SELECT object2.id FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
)
Removing the need for a temp table just to get the id when it's already getting just the id?
Thanks.
I really don't get why the Order should matter so much, but simply add
LIMIT 18446744073709551615
behind your ORDER mBY
UPDATE object2 SET obj2_date = date_sub(obj2_date, INTERVAL 1 month)
WHERE object2.id IN (
SELECT DISTINCT id FROM (
SELECT object2.id,object2.obj2_date FROM object2 INNER JOIN object1 ON object1.id = object2.object1_id WHERE <criteria>
) __active_record_temp
ORDER BY obj2_date ASC LIMIT 18446744073709551615
)
The cause, why order by are ignored without a LIMIT is simple, rows are per definition unordered so teh ORDER BY is removed without a Limit,
Mysql lets the ORDER BY untouched under special circumstances
Object2.joins( :object1 ).where( object1: { :id => id } ).order( obj2_date: ascdsc ).limit(18446744073709551615).update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")
I didn't know you could have an entire active record code block inside an active record code block.
This addresses both issues. Code:
Object2.where( id: __Object1.select( :id ).where( :parent_id => id )__ ).order( obj2_date: ascdsc ).update_all(
"obj2_date = " + direction + "(obj2_date, INTERVAL " + difference.to_s + " month)")
Auto generates this SQL:
UPDATE object2 SET obj2_date = date_add(obj2_date, INTERVAL 1 month)
WHERE object2.obj1_id IN (
SELECT object1.id FROM object1 WHERE object1.parent_id = 15
) ORDER BY object2.obj2_date ASC
I'm developing a new endpoint using Rocket and am trying to return a Vec<> made of various structs.
The raw query I want to replicate in diesel is:
select location.id, location.name, w.datetime, t.temp, w.compass, w.speed, r.probability, s.height
from location
inner join rainfall r on location.id = r.location
inner join temperature t on location.id = t.location
inner join wind w on location.id = w.location
inner join swell s on location.id = s.location
where t.datetime = w.datetime
and s.datetime = t.datetime
and CAST(t.datetime as date) = CAST(r.datetime as date)
and t.datetime > now() and t.datetime < NOW() + INTERVAL 1 HOUR;
and I recognize, that in order to use the CAST function I need to use the sql_function! macro:
sql_function! {
#[sql_name="CAST"]
fn cast(x: sql_types::Nullable<sql_types::Datetime>) -> sql_types::Date;
}
which allows me to create the following query:
let summaries: Vec<(Location, Swell, Wind, Temperature, Rainfall)> = location::table
.inner_join(swell::table)
.inner_join(wind::table)
.inner_join(temperature::table)
.inner_join(rainfall::table)
.filter(temperature::datetime.eq(wind::datetime))
.filter(temperature::datetime.eq(swell::datetime))
.filter(temperature::datetime.gt(utilities::today()))
.filter(temperature::datetime.lt(utilities::future_hour(1)))
.filter(cast(temperature::datetime).eq(cast(rainfall::datetime)))
.load(&conn.0)?;
However, when I run this query I get a SQL Query error:
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \') = CAST('rainfall'.'datetime')\' at line 1"
As illustrated in the raw SQL statement it should read CAST('rainfall'.'datetime' as date).
My question is, how can I add the 'as date' component to my diesel query? Is something missing in the sql_function definition?
Thanks for your help.
I found the answer after digging a little deeper into similar questions.
Turns out you can enter a raw sql string into the .filter method after adding: use diesel::expression::sql_literal::sql;.
So the final snippet becomes:
let summaries: Vec<(Location, Swell, Wind, Temperature, Rainfall)> = location::table
.inner_join(swell::table)
.inner_join(wind::table)
.inner_join(temperature::table)
.inner_join(rainfall::table)
.filter(temperature::datetime.eq(wind::datetime))
.filter(temperature::datetime.eq(swell::datetime))
.filter(temperature::datetime.gt(utilities::today()))
.filter(temperature::datetime.lt(utilities::future_hour(1)))
.filter(sql("CAST(`temperature`.`datetime` as date) = CAST(`rainfall`.`datetime` as date)"))
.load(&conn.0)?;
I hope this helps someone else!
I'm having a heck of a time getting the intended behavior using includes() and where().
Result I want:
- All students (even if they have zero check-ins)
- All check-ins in the Library
Result I'm getting:
- Only students with check-ins in the library
- All check-ins in the library, for those students
Currently my code is based off of this:
http://edgeguides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
Which describes the behavior I want:
Article.includes(:comments).where(comments: { visible: true })
If, in the case of this includes query, there were no comments for any
articles, all the articles would still be loaded.
My code:
#students = Student.includes(:check_ins)
.where(check_ins: {location: "Library"})
.references(:check_ins)
.
class CheckIn < ApplicationRecord
belongs_to :student
end
.
class Student < ApplicationRecord
has_many :check_ins, dependent: :destroy
end
The generated SQL query:
SELECT "students"."id" AS t0_r0,"check_ins"."id" AS t1_r0, "check_ins"."location" AS t1_r1, "check_ins"."student_id" AS t1_r6 FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id" WHERE "check_ins"."location" IN ('Library')
This SQL query gives the join behavior I want:
SELECT first_name, C.id FROM students S LEFT OUTER JOIN check_ins C ON C.student_id = S.id AND location IN ('Library');
Tried a new approach using Scopes with relations, expecting to preload everything and filter it out, but was pleasantly surprised that Scopes actually give me the exact behavior I want (right down to the eager loading).
Here's the result:
This ActiveRecord Call pulls in the full list of students and eager loads the check-ins:
#students = Student.all.includes(:check_ins)
The scope of check_ins can be limited right in the has_many declaration:
Class Student < ApplicationRecord
has_many :check_ins, -> {where('location = 'Library'}, dependent: :destroy
end
Resulting in two clean, efficient queries:
Student Load (0.7ms) SELECT "students".* FROM "students"
CheckIn Load (1.2ms) SELECT "check_ins".* FROM "check_ins" WHERE location = 'Library') AND "check_ins"."student_id" IN (6, 7, 5, 3, 1, 8, 9, 4, 2)
Bingo!
p.s. you can read more about using scopes with assocations here:
http://ducktypelabs.com/using-scope-with-associations/
What you want in terms of pure SQL is:
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
However it is not possible (afaik) to get ActiveRecord to mark the association as loaded without trickery*.
class Student < ApplicationRecord
has_many :check_ins
def self.joins_check_ins
joins( <<~SQL
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
SQL
)
end
end
So if we iterate though the result it will cause a N+1 query issue:
irb(main):041:0> Student.joins_check_ins.map {|s| s.check_ins.loaded? }
Student Load (1.0ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
=> [false, false, false]
irb(main):042:0> Student.joins_check_ins.map {|s| s.check_ins.size }
Student Load (2.3ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
(1.2ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 1]]
(0.7ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 2]]
(0.6ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 3]]
To be honest, I never like preloading only a subset of association
because some parts of your application probably assume that it is
fully loaded. It might only make sense if you are getting the data to
display it.
- Robert Pankowecki, 3 ways to do eager loading (preloading) in Rails 3 & 4
So in this case you should consider preloading all the data and using something like a subquery to select the count of check_ins.
I would also advise you to create a separate table for locations.
I think this is the only way to create the query you want.
Student.joins("LEFT OUTER JOIN check_ins ON check_ins.student_id = students.id AND check_ins.location = 'Library'")
Reference : http://apidock.com/rails/ActiveRecord/QueryMethods/joins
I'm having trouble writing an Active Record query that returns the results I want. I have the following setup:
abridged User model:
class User < ActiveRecord::Base
has_many :answers
end
abridged Answer model:
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user
end
abridged Question model:
class Question < ActiveRecord::Base
has_many :answers
def self.unanswered_by(user)
where(
'id NOT IN (SELECT question_id FROM answers WHERE user_id = ?)',
user.id
)
end
def self.recently_answered
includes(:answers).order('answers.updated_at DESC')
end
end
I'm trying to get an ActiveRecord::Relation back that orders the questions by those that have been most recently answered and then filters that result so it only contains questions a current_user has yet to answer.
Ideally, I'd like to write
Question.recently_answered.unanswered_by current_user
but this doesn't appear to work and I'm struggling to understand why with my limited understanding of SQL.
This is the result I get when I run this in the Rails console:
me = User.find(8)
Question.recently_answered.unanswered_by me
=> SQL (0.5ms) SELECT `questions`.`id` AS t0_r0,
`questions`.`question_text` AS t0_r1,
`questions`.`example_answer` AS t0_r2,
`questions`.`created_at` AS t0_r3,
`questions`.`updated_at` AS t0_r4,
`answers`.`id` AS t1_r0,
`answers`.`question_id` AS t1_r1,
`answers`.`user_id` AS t1_r2,
`answers`.`answer_text` AS t1_r3,
`answers`.`created_at` AS t1_r4,
`answers`.`updated_at` AS t1_r5
FROM `questions` LEFT OUTER JOIN `answers`
ON `answers`.`question_id` = `questions`.`id`
WHERE (id NOT IN (SELECT question_id FROM answers WHERE user_id = 8))
ORDER BY answers.updated_at DESC
#<ActiveRecord::Relation:0x3fd42e362a80>
Running Question.recently_answered.unanswered_by(me).to_sql gives me this:
=> "SELECT `questions`.*
FROM `questions`
WHERE (id NOT IN (SELECT question_id
FROM answers WHERE user_id = 8))
ORDER BY answers.updated_at DESC"
I'm working around this right now by doing
Question
.recently_answered
.reject { |q| q.answers.map(&:user_id).include? current_user.id }
but this returns an Array of Question objects instead of the ActiveRecord::Relation that I'd prefer.
Could someone help me understand why I can't chain recently_answered and unanswered_by as written and how I could go about rewriting this so I can get the result I want? Thanks.
You should add the table's name in the SQL query of the unanswered_by method:
def self.unanswered_by(user)
where('questions.id NOT IN (SELECT question_id FROM answers WHERE user_id = ?)', user.id)
#^^^^^^^^^ table's name added here
end
Because if you use this combined with a joins/includes, your DB adapter will not know from which table you select the id (error message like column id is ambiguous).
Also, you should probably use scope instead for these 2 methods.
I have a transport planner written in PHP and MySQL,
To get the task rules per day, I use the following query:
SELECT planning.*,
planning_dagen.planning_id,
planning_dagen.dagen,
planning_dagen.data_import,
routenummer_wijzigingen.routenummer AS temp_routenummer
FROM planning
LEFT JOIN planning_dagen
USING (planning_id)
LEFT JOIN routenummer_wijzigingen
USING (planning_id)
WHERE :datum >= planning.datum
AND :datum <= geldig_tot
AND (frequentie = 'dagelijks' AND dayofweek(:datum) = planning_dagen.dagen
OR (frequentie = 'eenmalig' AND date(:datum) = planning.datum)
OR (frequentie = 'wekelijks' AND 0 = (abs(datediff(:datum, planning.datum)) % 7))
OR (frequentie = 'twee-wekelijks' AND 0 = (abs(datediff(:datum, planning.datum)) % 14))
OR (frequentie = 'maandelijks'
AND ceil(dayofmonth(:datum)/7) = ceil(dayofmonth(planning.datum)/7)
AND dayofweek(:datum) = dayofweek(planning.datum)))
AND dayofweek(:datum) <> '1'
AND dayofweek(:datum) <> '7'
In the planning table there is a column called routenummer (routenumber) which is used in most conditions (standard routenumber).
But as you can see I have also a routenummer_wijzigingen table which is used to give a task a different routenumber for certain day.
For example I have a task which returns every tuesday and wednesday and has routenumber 10. But on tuesday 2015-02-03 I need this task done by routenumber 9.
So I insert a rule in the routenummer_wijzigingen table which has the following columns:
routenummer_wijzigingen_id
planning_id
routenummer
datum
So when a date is selected and that date and planning_id exists in the routenummer_wijzigingen table, it has to take the routenumber from the routenummer_wijzigingen table instead of the planning table.
How can I achieve this?
You should modify join condition with routenummer_wijzigingen table (including datum). Then you should use CASE in your SELECT clause to decide which routenummer to choose.
SELECT planning.*,
planning_dagen.planning_id,
planning_dagen.dagen,
planning_dagen.data_import,
CASE
WHEN routenummer_wijzigingen.routenummer is not NULL
THEN routenummer_wijzigingen.routenummer
ELSE planning.routenummer
END AS temp_routenummer
FROM planning
...
LEFT JOIN routenummer_wijzigingen rw on
planning.planning_id=rw.planning_id and rw.datum=...