How to fetch distinct values with arel/relational algebra
Asked Answered
S

6

14

I'm doing my best to bend my brain around arel and the relational algebra behind it, but how to represent a SELECT DISTINCT is consistently eluding my comprehension. Can anyone explain how to arel:

SELECT DISTINCT title FROM posts; 

Many thanks!

Senzer answered 28/6, 2010 at 21:28 Comment(1)
I don't know arel but from my reading of "Database in Depth" by C.J.Date, in relational algebra the result of a query is a set of tuples. So if arel is following this theory than distinct should be the default.Luben
J
1

Post.select('DISTINCT title')

Update 1:

At the time of the post, this was not available in Arel. These days, ActiveRecord::QueryMethods has the uniq method (http://apidock.com/rails/ActiveRecord/QueryMethods/uniq), so you'd want:

Post.select(:title).uniq

Update 2: Looks like Arel now supports this behavior. @maerics has the correct answer. I'd delete this if it wasn't the accepted answer.

Jailbird answered 29/6, 2010 at 0:28 Comment(3)
Not exactly algebraic, but hard to argue with it's efficiency ;-)Senzer
There is a fatal problem with this approach: if you have more than one scope with a select statement, chaining them together can cause invalid SQL.Scalpel
This is not AREL, and thus it doesn't answer the question.Enrobe
T
17

Using pure Arel (not Rails/ActiveRecord) there is a "distinct" method:

Arel::VERSION # => '3.0.2'
posts = Arel::Table.new(:posts)
posts.project(posts[:title])
posts.distinct
posts.to_sql # => 'SELECT DISTINCT "posts"."title" FROM "posts"'

Curiously, the "distinct" method is not chainable, per the other Arel methods.

Tupungato answered 18/9, 2012 at 21:29 Comment(3)
With Arel version, 5.0.1, this no longer works :(.Unbridled
Arel 6, everything works perfectly. It's also chainable, as it's returning SelectManager.Avalanche
arel 6.0.3 and this saved my day, though i used distinct_onArmagnac
F
15

The Arel way to do it is:

t = Arel::Table.new(:foo)
count_distinct = t[:field].count(true)
count_distinct.to_sql # => "COUNT(DISTINCT `foo`.`field`)"
Foret answered 29/5, 2014 at 21:33 Comment(1)
This doesn't strictly answer the question but it does happen to answer my question: how do you express count(DISTINCT attr) using Arel.Appurtenance
U
7

The previous answer is the Rails way, no? Not the Arel way.

This works for 1.x:

posts = Table(:posts)
posts.project(Arel::Distinct.new(posts[:title]))

I'd guess there's another "more correct" way to do this via the API but I haven't figured that out yet.

Urban answered 19/8, 2010 at 18:47 Comment(2)
This is true. This answer was for Arel 1.x and will no longer work.Urban
Do you know what's the alternative?Enrobe
C
3

If you are doing this using a scope:

  scope :recent, lambda {|count|
    select("DISTINCT posts.*").
    joins(:whatever).
    limit(count).
    order("posts.updated_at DESC")
  }
Cotto answered 1/2, 2011 at 0:4 Comment(0)
J
1

Post.select('DISTINCT title')

Update 1:

At the time of the post, this was not available in Arel. These days, ActiveRecord::QueryMethods has the uniq method (http://apidock.com/rails/ActiveRecord/QueryMethods/uniq), so you'd want:

Post.select(:title).uniq

Update 2: Looks like Arel now supports this behavior. @maerics has the correct answer. I'd delete this if it wasn't the accepted answer.

Jailbird answered 29/6, 2010 at 0:28 Comment(3)
Not exactly algebraic, but hard to argue with it's efficiency ;-)Senzer
There is a fatal problem with this approach: if you have more than one scope with a select statement, chaining them together can cause invalid SQL.Scalpel
This is not AREL, and thus it doesn't answer the question.Enrobe
D
0

Since AREL always uses SET in it's operation, duplicate row results will be deleted automatically. Just use a normal Project (Phi) operation.

Dynameter answered 25/3, 2012 at 14:5 Comment(1)
This is a nice idea in theory but it is patently false in reality. Arel queries will return duplicate entries from any projection unless constrained by the "distinct" method explicitly.Tupungato

© 2022 - 2024 — McMap. All rights reserved.