ruby multiple where operator with jsonb column and non jsonb columns - ruby-on-rails

I am trying to make a query that includes a jsonb column and 2 non jsonb columns.
Multiple attempts to combine them have failed but 1 nearly worked when I only used 1 other non jsonb column. I have a channel model with an 'options' store and several attributes within.
If i separate the queries they work just fine but combined they retrieve an empty array. I have made sure that if the queries did work, there is definitely data for them to return.
Non jsonb columns - works
Channel.where("platform_id = ? AND updated_at < ?",2,7.days.ago)
jsonb column - works
Channel.where("options #> ?", {valid_account: true}.to_json)
combined where operator - returns empty []
Channel.where("platform_id = ? AND updated_at < ?",2,7.days.ago).where("options #> ?", {valid_account: true}.to_json)
1 where operator with combined query - again, returns empty []
Channel.where("options = ? AND platform_id = ? AND updated_at < ?", {"valid_account" => true}.to_json, 2, 7.days.ago)
Am at a loss now and not sure how to get this all into one query... or if it's even possible.
Again... there are definitely channels that should return with the given queries above
TIA
UPDATE
Managed to get the query to work. tried nearly every combination but missed one critical one.
Channel.where("options #> ? AND platform_id = ? AND updated_at > ?", {valid_account: true}.to_json, 2, 7.days.ago)
this worked. For some reason I had used the '>#' in a separate method but not combined it for this. All working now. Thanks for the support

Answer was to include '>#' in the query for the jsonb column
Channel.where("options #> ? AND platform_id = ? AND updated_at > ?", {valid_account: true}.to_json, 2, 7.days.ago

Related

How to get the latest created object in ruby on rails [duplicate]

I was wondering if there is a way to find the newest record in a table in rails3?
Given a Post model, you could do #post = Post.order("created_at").last
(The reason I didn't just do a #post = Post.last is because that always defaults to sort by your primary key (usually id). Most of the time this is fine, but I'm sure there's a scenario where that could cause problems (e.g. setting custom IDs on records, database changes which affect the primary key sequencing/autonumbering, etc.). Sorting by the created_at timestamp ensures you are really getting the most recent record).
While dmarkow's answer is technically correct, you'll need to make an index on created_at or risk an increasingly slow query as your database grows.
If you know that your "id" column is an auto-increment primary key (which it likely is), then just use it since it is an index by definition.
Also, unless AREL is optimized to select only one record in a find(:last), you run the risk of making it select ALL records, then return you just the last one by using the "last()" method. More efficient is to limit the results to one:
MyModel.last(:order => "id asc", :limit => 1)
or
MyModel.first(:order => "id desc", :limit => 1)
you may run into ambiguity issues using created_at on a sufficiently high-traffic table.
eg. try:
INSERT INTO table (created_at) VALUES ( NOW() );
INSERT INTO table (created_at) VALUES ( NOW() );
..has the potential to have the same created_at, which only has 1 second of resolution. a sort would return them in no particular order.
you may be better off storing a microtime value and sorting on that.
Yes, you can use the method .last
So if your model is called Post then:
>> Post.last
=> #<Post ...>
Try, for a model named ModelName:
record = ModelName.last

How to make Rails/ActiveRecord return unique objects using join table's boolean column

I have a Rails 4 app using ActiveRecord and Postgresql with two tables: stores and open_hours. a store has many open_hours:
stores:
Column |
--------------------+
id |
name |
open_hours:
Column |
-----------------+
id |
open_time |
close_time |
store_id |
The open_time and close_time columns represent the number of seconds since midnight of Sunday (i.e. beginning of the week).
I would like to get list of store objects ordered by whether the store is open or not, so stores that are open will be ranked ahead of the stores that are closed. This is my query in Rails:
Store.joins(:open_hours).order("#{current_time} > open_time AND #{current_time} < close_time desc")
Notes that current_time is in number of seconds since midnight on the previous Sunday.
This gives me a list of stores with the currently open stores ranked ahead of the closed ones. However, I'm getting a lot of duplicates in the result.
I tried using the distinct, uniq and group methods, but none of them work:
Store.joins(:open_hours).group("stores.id").group("open_hours.open_time").group("open_hours.close_time").order("#{current_time} > open_time AND #{current_time} < close_time desc")
I've read a lot of the questions/answers already on Stackoverflow but most of them don't address the order method. This question seems to be the most relevant one but the MAX aggregate function does not work on booleans.
Would appreciate any help! Thanks.
Here is what I did to solve the issue:
In Rails:
is_open = "bool_or(#{current_time} > open_time AND #{current_time} < close_time)"
Store.select("stores.*, CASE WHEN #{is_open} THEN 1 WHEN #{is_open} IS NULL THEN 2 ELSE 3 END AS open").group("stores.id").joins("LEFT JOIN open_hours ON open_hours.store_id = stores.id").uniq.order("open asc")
Explanation:
The is_open variable is just there to shorten the select statement.
The bool_or aggregate function is needed here to group the open_hours records. Otherwise there likely will be two results for each store (one open and one closed), which is why using the uniq method alone doesn't eliminate the duplicate issues
LEFT JOIN is used instead of INNER JOIN so we can include the stores that don't have any open_hours objects
The store can be open (i.e. true), closed (i.e. false) or not determined (i.e. nil), so the CASE WHEN statement is needed here: if a store is open, then it's 1, 2 if not determined and 3 if closed
Ordering the results ASC will show open stores first, then the not determined ones, then the closed stores.
This solution works but doesn't feel very elegant. Please post your answer if you have a better solution. Thanks a lot!
Have you tried uniq method, just append it at the end
Store.joins(:open_hours).order("#{current_time} > open_time AND #{current_time} < close_time desc").uniq

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.

Rails: Order with nulls last

In my Rails app I've run into an issue a couple times that I'd like to know how other people solve:
I have certain records where a value is optional, so some records have a value and some are null for that column.
If I order by that column on some databases the nulls sort first and on some databases the nulls sort last.
For instance, I have Photos which may or may not belong to a Collection, ie there are some Photos where collection_id=nil and some where collection_id=1 etc.
If I do Photo.order('collection_id desc) then on SQLite I get the nulls last but on PostgreSQL I get the nulls first.
Is there a nice, standard Rails way to handle this and get consistent performance across any database?
I'm no expert at SQL, but why not just sort by if something is null first then sort by how you wanted to sort it.
Photo.order('collection_id IS NULL, collection_id DESC') # Null's last
Photo.order('collection_id IS NOT NULL, collection_id DESC') # Null's first
If you are only using PostgreSQL, you can also do this
Photo.order('collection_id DESC NULLS LAST') #Null's Last
Photo.order('collection_id DESC NULLS FIRST') #Null's First
If you want something universal (like you're using the same query across several databases, you can use (courtesy of #philT)
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
Even though it's 2017 now, there is still yet to be a consensus on whether NULLs should take precedence. Without you being explicit about it, your results are going to vary depending on the DBMS.
The standard doesn't specify how NULLs should be ordered in comparison with non-NULL values, except that any two NULLs are to be considered equally ordered, and that NULLs should sort either above or below all non-NULL values.
source, comparison of most DBMSs
To illustrate the problem, I compiled a list of a few most popular cases when it comes to Rails development:
PostgreSQL
NULLs have the highest value.
By default, null values sort as if larger than any non-null value.
source: PostgreSQL documentation
MySQL
NULLs have the lowest value.
When doing an ORDER BY, NULL values are presented first if you do ORDER BY ... ASC and last if you do ORDER BY ... DESC.
source: MySQL documentation
SQLite
NULLs have the lowest value.
A row with a NULL value is higher than rows with regular values in ascending order, and it is reversed for descending order.
source
Solution
Unfortunately, Rails itself doesn't provide a solution for it yet.
PostgreSQL specific
For PostgreSQL you could quite intuitively use:
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
MySQL specific
For MySQL, you could put the minus sign upfront, yet this feature seems to be undocumented. Appears to work not only with numerical values, but with dates as well.
Photo.order('-collection_id DESC') # NULLs come last
PostgreSQL and MySQL specific
To cover both of them, this appears to work:
Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last
Still, this one does not work in SQLite.
Universal solution
To provide cross-support for all DBMSs you'd have to write a query using CASE, already suggested by #PhilIT:
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
which translates to first sorting each of the records first by CASE results (by default ascending order, which means NULL values will be the last ones), second by calculation_id.
Photo.order('collection_id DESC NULLS LAST')
I know this is an old one but I just found this snippet and it works for me.
Put minus sign in front of column_name and reverse the order direction. It works on mysql. More details
Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
Bit late to the show but there is a generic SQL way to do it. As usual, CASE to the rescue.
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
The easiest way is to use:
.order('name nulls first')
For posterity's sake, I wanted to highlight an ActiveRecord error relating to NULLS FIRST.
If you try to call:
Model.scope_with_nulls_first.last
Rails will attempt to call reverse_order.first, and reverse_order is not compatible with NULLS LAST, as it tries to generate the invalid SQL:
PG::SyntaxError: ERROR: syntax error at or near "DESC"
LINE 1: ...dents" ORDER BY table_column DESC NULLS LAST DESC LIMIT...
This was referenced a few years ago in some still-open Rails issues (one, two, three). I was able to work around it by doing the following:
scope :nulls_first, -> { order("table_column IS NOT NULL") }
scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }
It appears that by chaining the two orders together, valid SQL gets generated:
Model Load (12.0ms) SELECT "models".* FROM "models" ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1
The only downside is that this chaining has to be done for each scope.
Rails 6.1 adds nulls_first and nulls_last methods to Arel for PostgreSQL.
Example:
User.order(User.arel_table[:login_count].desc.nulls_last)
Source: https://www.bigbinary.com/blog/rails-6-1-adds-nulls-first-and-nulls-last-to-arel
Here are some Rails 6 solutions.
The answer by #Adam Sibik is a great summary about the difference between various database systems.
Unfortunately, though, some of the presented solutions, including "Universal solution" and "PostgreSQL and MySQL specific", would not work any more with Rails 6 (ActiveRecord 6) as a result of its changed specification of order() not accepting some raw SQLs (I confirm the "PostgreSQL specific" solution still works as of Rails 6.1.4). For the background of this change, see, for example,
"Updates for SQL Injection in Rails 6.1" by Justin.
To circumvent the problem, you can wrap around the SQL statements with Arel.sql as follows, where NULLs come last, providing you are 100% sure the SQL statements you give are safe.
Photo.order(Arel.sql('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id'))
Just for reference, if you want to sort by a Boolean column (is_ok, as an example) in the order of [TRUE, FALSE, NULL] regardless of the database systems, either of these should work:
Photo.order(Arel.sql('CASE WHEN is_ok IS NULL THEN 1 ELSE 0 END, is_ok DESC'))
Photo.order(Arel.sql('CASE WHEN is_ok IS NULL THEN 1 WHEN is_ok IS TRUE THEN -1 ELSE 0 END'))
(n.b., SQLite does not have the Boolean type and so the former may be safer arguably, though it should not matter because Rails should guarantee the value is either 0 or 1 (or NULL).)
In my case I needed sort lines by start and end date by ASC, but in few cases end_date was null and that lines should be in above, I used
#invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
Adding arrays together will preserve order:
#nonull = Photo.where("collection_id is not null").order("collection_id desc")
#yesnull = Photo.where("collection_id is null")
#wanted = #nonull+#yesnull
http://www.ruby-doc.org/core/classes/Array.html#M000271
It seems like you'd have to do it in Ruby if you want consistent results across database types, as the database itself interprets whether or not the NULLS go at the front or end of the list.
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}
But that is not very efficient.

Find the newest record in Rails 3

I was wondering if there is a way to find the newest record in a table in rails3?
Given a Post model, you could do #post = Post.order("created_at").last
(The reason I didn't just do a #post = Post.last is because that always defaults to sort by your primary key (usually id). Most of the time this is fine, but I'm sure there's a scenario where that could cause problems (e.g. setting custom IDs on records, database changes which affect the primary key sequencing/autonumbering, etc.). Sorting by the created_at timestamp ensures you are really getting the most recent record).
While dmarkow's answer is technically correct, you'll need to make an index on created_at or risk an increasingly slow query as your database grows.
If you know that your "id" column is an auto-increment primary key (which it likely is), then just use it since it is an index by definition.
Also, unless AREL is optimized to select only one record in a find(:last), you run the risk of making it select ALL records, then return you just the last one by using the "last()" method. More efficient is to limit the results to one:
MyModel.last(:order => "id asc", :limit => 1)
or
MyModel.first(:order => "id desc", :limit => 1)
you may run into ambiguity issues using created_at on a sufficiently high-traffic table.
eg. try:
INSERT INTO table (created_at) VALUES ( NOW() );
INSERT INTO table (created_at) VALUES ( NOW() );
..has the potential to have the same created_at, which only has 1 second of resolution. a sort would return them in no particular order.
you may be better off storing a microtime value and sorting on that.
Yes, you can use the method .last
So if your model is called Post then:
>> Post.last
=> #<Post ...>
Try, for a model named ModelName:
record = ModelName.last

Resources