Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add in GROUP BY querying #234

Merged
merged 7 commits into from Sep 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions spec/query_builder_spec.cr
Expand Up @@ -262,6 +262,53 @@ describe Avram::QueryBuilder do
old_query.statement.should eq "SELECT users.name, users.age FROM users INNER JOIN posts ON users.id = posts.user_id WHERE name = $1 ORDER BY id ASC LIMIT 1 OFFSET 2"
end
end

describe "grouped?" do
it "returns true when there's a grouping" do
query = Avram::QueryBuilder
.new(table: :users)
.group_by(:id)

query.grouped?.should eq true
end

it "returns false when there's no groups" do
query = Avram::QueryBuilder
.new(table: :users)

query.grouped?.should eq false
end
end

describe "group_by" do
it "groups by a column" do
query = Avram::QueryBuilder
.new(table: :users)
.group_by(:name)

query.statement.should eq "SELECT * FROM users GROUP BY name"
end

it "groups by multiple columns" do
query = Avram::QueryBuilder
.new(table: :users)
.group_by(:age)
.group_by(:average_score)
query.statement.should eq "SELECT * FROM users GROUP BY age, average_score"
end

it "groups in the proper order with other query parts" do
query = Avram::QueryBuilder
.new(table: :users)
.where(Avram::Where::Equal.new(:name, "Paul"))
.order_by(Avram::OrderBy.new(:name, :desc))
.group_by(:age)
.group_by(:average_score)
.limit(10)
query.statement.should eq "SELECT * FROM users WHERE name = $1 GROUP BY age, average_score ORDER BY name DESC LIMIT 10"
query.args.should eq ["Paul"]
end
end
end

private def new_query
Expand Down
20 changes: 20 additions & 0 deletions spec/query_spec.cr
Expand Up @@ -737,4 +737,24 @@ describe Avram::Query do
companies.first.should eq company
end
end

describe "#group" do
it "groups" do
user = UserBox.create &.age(25).name("Michael")
user = UserBox.create &.age(25).name("Dwight")
user = UserBox.create &.age(21).name("Jim")

users = UserQuery.new.group(&.age).group(&.id)
users.query.statement.should eq "SELECT #{User::COLUMN_SQL} FROM users GROUP BY users.age, users.id"
users.map(&.name).should eq ["Dwight", "Michael", "Jim"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so does it just return an array? What does group by do in this case. I thought it'd be a hash or something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns an array here of .map(), but UserQuery.new.group(&.age).group(&.id) just returns the BaseQuery object like normal. It is confusing though because technically in this query, the group is useless.

I see this more as building blocks for the next parts to come as well as the underlying access. But if you have suggestions, or whatever, I'm all ears. Postgres grouping hurts my head lol

end

it "raises an error when grouped incorrectly" do
users = UserQuery.new.group(&.age)

expect_raises(PQ::PQError, /column "users\.id" must appear in the GROUP BY/) do
users.map(&.name).should contain "Pam"
end
end
end
end
5 changes: 5 additions & 0 deletions src/avram/criteria.cr
Expand Up @@ -130,6 +130,11 @@ class Avram::Criteria(T, V)
rows.query.distinct_on(column)
end

# :nodoc:
def __group : Avram::QueryBuilder
rows.query.group_by(column)
end

private def add_clause(sql_clause) : T
sql_clause = build_sql_clause(sql_clause)
rows.query.where(sql_clause)
Expand Down
26 changes: 25 additions & 1 deletion src/avram/query_builder.cr
Expand Up @@ -7,6 +7,7 @@ class Avram::QueryBuilder
@raw_wheres = [] of Avram::Where::Raw
@joins = [] of Avram::Join::SqlClause
@orders = [] of Avram::OrderBy
@groups = [] of ColumnName
@selections : String = "*"
@prepared_statement_placeholder = 0
@distinct : Bool = false
Expand Down Expand Up @@ -37,6 +38,10 @@ class Avram::QueryBuilder
query_to_merge.orders.each do |order|
order_by(order)
end

query_to_merge.groups.each do |group|
group_by(group)
end
end

# Similar to `merge`, but includes ALL query parts
Expand Down Expand Up @@ -89,7 +94,7 @@ class Avram::QueryBuilder
end

private def sql_condition_clauses
[joins_sql, wheres_sql, order_sql, limit_sql, offset_sql]
[joins_sql, wheres_sql, group_sql, order_sql, limit_sql, offset_sql]
end

def delete
Expand Down Expand Up @@ -152,6 +157,25 @@ class Avram::QueryBuilder
@orders.uniq!(&.column)
end

def group_by(column : ColumnName)
@groups << column
self
end

def group_sql
if grouped?
"GROUP BY " + groups.join(", ")
end
end

def groups
@groups
end

def grouped?
@groups.any?
end

def select_count
add_aggregate "COUNT(*)"
end
Expand Down
6 changes: 6 additions & 0 deletions src/avram/queryable.cr
Expand Up @@ -106,6 +106,12 @@ module Avram::Queryable(T)
raise "#{e.message}. Accepted values are: :asc, :desc"
end

def group(&block) : self
criteria = yield self
criteria.__group
jwoertink marked this conversation as resolved.
Show resolved Hide resolved
self
end

def none : self
query.where(Avram::Where::Equal.new("1", "0"))
self
Expand Down