Is it possible to convert the complex SQL into rails? - ruby-on-rails

SPEC
I need to pick all rooms that don't have a single day with saleable = FALSE in the requested time period(07-09 ~ 07-19):
I have a table room with 1 row per room.
I have a table room_skus with one row per room and day (complete set for the relevant time range).
The column saleable is boolean NOT NULL and date is defined date NOT NULL
SELECT id
FROM room r
WHERE NOT EXISTS (
SELECT 1
FROM room_skus
WHERE date BETWEEN '2016-07-09' AND '2016-07-19'
AND room_id = r.id
AND NOT saleable
GROUP BY 1
);
The above SQL query is working, but I wonder how could I translate it into Rails ORM.

Let's say you have array of room_ids called room_ids:
needed_room_ids = room_ids - RoomSku.where(room_id: room_ids, date: '2016-07-09'..'2016-07-19', sealable: false).pluck(:room_id)
If your model of room_sku is called RoomSku
Updated version:
room_ids = Room.all.select { |record| record.room_skus.present? }.map(&:id)
And then:
needed_room_ids = room_ids - RoomSku.where(room_id: room_ids, date: '2016-07-09'..'2016-07-19', sealable: false).pluck(:room_id)
It won't be one query, but you avoid plain SQL like this.

I don't have any project here to test something like it, but it should work:
Room.where.not(id: RoomSku.where(date: DateTime.parse('2016-07-09').strftime("%Y-%m-%d")..DateTime.parse('2016-07-19').strftime("%Y-%m-%d"), saleable: false).pluck(:room_id))
I hope it helps!

Related

Order by a date of the first hasMany child of a model

I have the following Structure for events and there recurrences
Event: (id, name, venue)
has_many :occurences
Occurence(id, date, event_id)
belongs_to :event
I want to get events (only event data) that have a recurrence greater than today
(occurences.date>Date.Today)
Events should be ordered by the date of their next recurrence(greater than today) in chronological order
The following query gives me event data alright but it doesn't let me order
Event.joins(:occurences).where("occurences.date>?",DateTime.now).distinct#.order('occurences.date')
but I can't order it since it says
Expression #1 of ORDER BY clause is not in SELECT list, references
column 'eventdatabase.occurences.starts'
I need to use distinct to ensure I get only one event regardless of how many occurences it has
I am using mysql and rails5
I'm interpreting
Events should be ordered by the date of their next recurrence(greater than today) in chronological order
as earliest occurance.date.
Event.joins(:occurences)
.where("occurences.date>?",DateTime.now)
.group(:id)
.select('events.*, min(occurences.date) as next_recurrence')
.order('next_recurrence')
A group by is like a more powerful distinct. You will get one row (for one "group"). Technically we should group by events.*, but mysql will cheat and let us group by a primary key to do the same thing.
When doing a group by, the aggregate functions, such as min work on the group.
For this task the SQL query may look like:
SELECT E.id AS event_id, MIN(OC.date) AS next_date
FROM events E
JOIN occurences OC ON OC.event_id = E.id
WHERE OC.date > NOW()
GROUP BY E.id
ORDER BY MIN(OC.date);
Here is sandbox: http://rextester.com/HCTE57488
So I guess the Ruby code will be:
Event.joins(:occurences)
.where('occurences.date > ?', DateTime.now)
.group('events.id')
.order('min(occurences.date)')

Summary Count by Group with ActiveRecord

I have a Rails application that holds user data (in an aptly named user_data object). I want to display a summary table that shows me the count of total users and the count of users who are still active (status = 'Active'), created each month for the past 12 months.
In SQL against my Postgres database, I can get the result I want with the following query (the date I use in there is calculated by the application, so you can ignore that aspect):
SELECT total.creation_month,
total.user_count AS total_count,
active.user_count AS active_count
FROM
(SELECT date_trunc('month',"creationDate") AS creation_month,
COUNT("userId") AS user_count
FROM user_data
WHERE "creationDate" >= to_date('2015 12 21', 'YYYY MM DD')
GROUP BY creation_month) AS total
LEFT JOIN
(SELECT date_trunc('month',"creationDate") AS creation_month,
COUNT("userId") AS user_count
FROM user_data
WHERE "creationDate" >= to_date('2015 12 21', 'YYYY MM DD')
AND status = 'Active'
GROUP BY creation_month) AS active
ON total.creation_month = active.creation_month
ORDER BY creation_month ASC
How do I write this query with ActiveRecord?
I previously had just the total user count grouped by month in my display, but I am struggling with how to add in the additional column of active user counts.
My application is on Ruby 2.1.4 and Rails 4.1.6.
I gave up on trying to do this the ActiveRecord way. Instead I just constructed my query into a string and passed the string into
ActiveRecord::Base.connection.execute(sql_string)
This had the side effect that my result set came out as a array instead of a set of objects. So getting at the values went from a syntax (where user_data is the name assigned to a single record from the result set) like
user_data.total_count
to
user_data['total_count']
But that's a minor issue. Not worth the hassle.

Rails Where Created_at Equals specific number

I'm trying to figure out how to do a query where created_at.year == a given year, and created_at.month equals a given month.
However I can't figure out what I'm doing wrong.
Model.where("'created_at.month' = ? AND 'created_at.year' = ?", 7,2013)
results in nothing being shown.
However when I try Model.first.created_at.month ==7 and
Model.first.created_at.year ==2013 I get true for both.
Therefore theoretically my query should be at least be returning my first record.
Anyone know what I'm doing wrong or any alternative way to find records created on specific months?
Note that in my views the month / year will be parameters but for the purposes of this example I used actual values.
using ruby 1.9.3
rails 3.2.13
You can use the extract SQL function, that will extract the month and year of the timestamp:
Model.where('extract(year from created_at) = ? and extract(month from created_at) = ?', '2013','7')
This query should give you the desired result.
created_at is a timestamp; it is not a set of discrete fields in the database. created_at.year and such don't exist in your DB; it's simply a single timestamp field. When you call #model.created_at.year, Rails is loading the created_at field from the database, and creating a Time object from it, which has a #year method you can call.
What you want is to query on a range of dates:
Model.where("created_at >= ? and created_at < ?", Time.mktime(2013, 7), Time.mktime(2013, 8))
This will find any Model with a created_at timestamp in July 2013.

RubyOnRails - search in the database by date

I have in my db a attribute where i save the record creation date.
The saved date has this format:
2013/06/18 19:03:24
I need to search only for date and i am not interested the time
I have try in this mode but it's doesn't work:
Report.find_all_by_created_at("2013-06-18").each do |r| %>
[...]
end
Given that created_at is a Rails generated column you can't really and just for created at date. Reason for that is in database created_at is saved as timestamp (or Datetime) so records created on 18th of June may have values from 2013-06-18 00:00:00 to 2013-06-18 23:59:59.
So, if you'd like to select all objects created on specific day you should do something like
Report.where('created_at >= ? AND created_at <= ?, Date.new(2013,6,18).beginning_of_day, Date.new(2013,6,18).end_of_day)
You can pass a range to where:
date = Date.new(2013,6,18)
Report.where(created_at: date.beginning_of_day..date.end_of_day)
This creates a SQL statement like:
SELECT `reports`.* FROM `reports` WHERE `reports`.`created_at` BETWEEN '2013-06-18 00:00:00' AND '2013-06-18 23:59:59'
Probably not the best solution but you could do:
Report.where("created_at >= ? and created_at < ?","2013-06-18","2013,06-19").each do |r|
[...]
end
It looks like you are collecting it as a DateTime and you would probably be able to use the to_date() method to change it to a Date object which would be searchable.
http://api.rubyonrails.org/classes/DateTime.html#method-i-to_date
you can use like matcher in sql query
Report.where("created_at LIKE '2013-06-18%'")
or look at the Squeel gem it can call SQL functions in ruby like syntax then you could use something thati will generate query with
WHERE cast(created_at as date) = '2013-06-18'

Rails to always save a changed state what method would you override

Ok.. so I have boss that's a bit of a nut when it comes to using the date as an indicator of change. He doesn't trust it.
What I want to do is have something work the same way as the date update that comes native with active record, but instead base it on an ever increasing number..
I know... the number of seconds since 1973 is constantly getting bigger Well unless you count daylight savings and things.
I'm wondering if there are any thoughts, on how to do this gracefully..
Note I have 20 tables that need this and I am a big fan of DRY.
Have a look at http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html, I think this is exactly what you want.
Optimistic locking within ActiveRecord means that if a lock_version column is present on a specific table then it will be updated (+1) every time you change that record (via ActiveRecord, of course).
I ended up using a mass trigger inside the database.
The function creates a record (or updates it) in a new table called data_changed.
def create_trigger_function(schema)
puts "DAVE: creating trigger function for tenant|schema #{schema.to_s}"
sql = "CREATE OR REPLACE FUNCTION \""+schema+"\".insert_into_delta_table() RETURNS TRIGGER AS 'BEGIN
UPDATE \""+schema+"\".data_changes SET status = 1, created_at = now() where table_name = TG_TABLE_NAME and record_id = NEW.id;
INSERT INTO \""+schema+"\".data_changes (status, table_name, market_id, record_id, created_at)
( select m.* from \""+schema+"\".data_changes as ds right outer join
(select 1, CAST (TG_TABLE_NAME AS text ) as name , markets.id, NEW.id as record_id, now() from \""+schema+"\".markets) as m
on
ds.record_id = m.record_id
and ds.market_id = m.id
and table_name = name
where ds.id is null );
RETURN NULL;
END;' LANGUAGE plpgsql;"
connection.execute(sql);
end
Now all I have to do to find all the changed "products" is
update data_changes set status = 2 where status = 1 and table_name = 'products'
select * from products where id in (select record_id from data_changes where status = 2 and table_name = 'products')
update data_changes set status = 3 where status = 2 and table_name = 'products'
If a product gets updated after I do my first update, but before I do the select, then it won't show up in my select, because it's id will be reset to 1.
If a product gets updated after I do my select, but before I do the last update,then again it will not be affected, by the last update.
The contents of my select, will be out of date, but there's no real way of avoiding that.

Resources