Build triple UNION query using Arel (Rails 5) - ruby-on-rails

user has_many tasks
I'm trying to create a 3 select UNION:
Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")
But with Arel.
I can easily use Arel for UNION query for 2 selects:
tasks_t = Task.arel_table
select1 = tasks_t.project('id').where(tasks_t[:id].eq(1)) # SELECT id FROM tasks WHERE tasks.id = 1
select2 = Task.joins(:user).select(:id).where(users: {id: 15}).arel # SELECT "tasks".id FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = 15
union = select1.union(select2) # SELECT * FROM users WHERE users.id = 1 UNION SELECT * FROM users WHERE users.id = 2
union_with_table_alias = tasks_t.create_table_alias(union, tasks_t.name)
Task.from(union_with_table_alias)
# SELECT "tasks".* FROM ( SELECT id FROM "tasks" WHERE "tasks"."id" = 1 UNION SELECT "tasks"."id" FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = $1 ) "tasks" [["id", 15]]
#=> Task::ActiveRecord_Relation [#<Task: id: 1>, #<Task: id: 2>]
How can I do it with triple union select?
select3 = tasks_t.project('id').where(tasks_t[:id].eq(3)) # SELECT id FROM tasks WHERE tasks.id = 3
Something like:
triple_union = select1.union(select2).union(select3)
triple_union_with_table_alias = tasks_t.create_table_alias(triple_union, tasks_t.name)
Task.from(triple_union_with_table_alias)
Which should roughly translate to
Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")
Note the above line also fails:
Caused by PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1
To summarize:
How to use Arel to build a tripple UNION query? select 1 UNION select 2 UNION select 3
Thank you
Ruby 2.5.3 Rails 5.2.4 Arel 9.0.0

You can not invoke union in an Arel::Nodes::Union, because in the method definition the ast of both objects is called. And the result of a union operation doesn't respond to ast:
def union(operation, other = nil)
if other
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
else
other = operation
node_class = Nodes::Union
end
node_class.new(self.ast, other.ast)
end
What you can do is to manually call Arel::Nodes::Union, passing as any of those arguments the result of a union:
Arel::Nodes::Union.new(Arel::Nodes::Union.new(select1, select2), select3)

Related

How to use order with uniq in a PostgreSQL

How can I use order with uniq?
auction.invoices.get_auction_invoices.item_invoices.
joins("INNER JOIN users ON users.id = invoices.usable_id").order("users.first_name").uniq
The above query gives me following error:
This is my scopes
scope :item_invoices, ->{ joins(:invoice_details).where("invoice_details.invoiceable_type = ?", "Item")}
scope :get_auction_invoices, ->{where(:id => (item_invoices.skip_cancelled_invoice + donators.skip_cancelled_invoice))}
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...oice_details.invoiceable_type = 'Item') ORDER BY users.firs...
: SELECT DISTINCT "invoices".* FROM "invoices" INNER JOIN "invoice_details" ON "invoice_details"."invoice_id" = "invoices"."id" INNER JOIN users ON users.id = invoices.usable_id WHERE "invoices"."eventable_id" = $1 AND "invoices"."eventable_type" = $2 AND "invoices"."id" IN (1132, 1131, 777, 777, 777, 3013, 3024, 3024, 3024, 3024, 3041, 3041, 3013) AND (invoice_details.invoiceable_type = 'Item') ORDER BY users.first_name
Try this:
auction.invoices.get_auction_invoices.item_invoices.\
select("invoices.*, users.*").\
joins("INNER JOIN users ON users.id = invoices.usable_id").\
order("users.first_name").\
uniq

group by in view or controller

i am trying to loop through a list of products which a supplier sells via its different variants. i can get the list of products to display, but i wish to group these by the product id as to only display it once.
in my controller i have
#supplier = Supplier.joins(products: :variants).find(params[:id])
in my view i have
- #supplier.variants.group_by(&:product_id).each do |product_id, item|
= render :partial => 'product', :locals => {:item => item }
and my partial
= link_to shopping_supplier_path(item) do
%li.mdl-list__item.mdl-list__item--three-line
%span.mdl-list__item-primary-content
%span= item.product.name
%span.mdl-list__item-text-body
= item.product.description.downcase
%span.mdl-list__item-secondary-content
%i.material-icons
chevron_right
%hr
which when the sql executes returns the following query
Started GET "/shopping/suppliers/latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1" for 127.0.0.1 at 2016-04-19 23:22:08 +1000
Processing by Shopping::SuppliersController#show as HTML
Parameters: {"id"=>"latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Supplier Load (32.7ms) SELECT "suppliers".* FROM "suppliers" WHERE "suppliers"."permalink" = $1 ORDER BY "suppliers"."id" ASC LIMIT 1 [["permalink", "latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"]]
Supplier Load (41.9ms) SELECT "suppliers".* FROM "suppliers" INNER JOIN "variant_suppliers" ON "variant_suppliers"."supplier_id" = "suppliers"."id" INNER JOIN "variants" ON "variants"."id" = "variant_suppliers"."variant_id" INNER JOIN "products" ON "products"."id" = "variants"."product_id" INNER JOIN "variants" "variants_products" ON "variants_products"."product_id" = "products"."id" WHERE "suppliers"."permalink" = $1 ORDER BY "suppliers"."id" ASC LIMIT 1 [["permalink", "latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"]]
Variant Load (0.9ms) SELECT "variants".* FROM "variants" INNER JOIN "variant_suppliers" ON "variants"."id" = "variant_suppliers"."variant_id" WHERE "variant_suppliers"."supplier_id" = $1 [["supplier_id", 1]]
Rendered shopping/suppliers/_product.html.haml (53.5ms)
error
NoMethodError at /shopping/suppliers/latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1
undefined method `name' for #<Array:0x007faa5302d5a0>
Use SQL joins. When you create a query joining the tables that you are going to use, they will be previously loaded in memory, so the famous n+1 queries will not occur.
#supplier = Supplier.joins(variants: :products).find(params[:id])
# This will translate to something like this
SELECT * FROM suppliers
INNER JOIN variants ON variants.supplier_id = suppliers.id
INNER JOIN products ON products.variant_id = variants.id
WHERE suppliers.id = ?
Remember to always avoid the lazy loading of your associations.

Rails join with where using AND instead of OR

I have a scope (where title is an array)
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
By default this does an AND query. How can I get this to do an OR query? I can't seem to find any examples of this.
I believe this is the SQL that is generated:
Label Load (0.1ms) SELECT "labels".* FROM "labels" WHERE "labels"."project_id" = $1 AND "labels"."title" = $2 ORDER BY "labels"."title" ASC LIMIT 1 [["project_id", 8], ["title", "label1,label2,label3"]]
Rails/ActiveRecord only supports OR queries in Rails 5. In earlier versions, you need to use a SQL string to achieve an OR query:
Widget.where("name = ? OR name = ?", "foo", "bar") # postgres
When there are many possibilities, the same thing can be accomplished more efficiently with an IN query, which is generated in ActiveRecord by passing an array with more than one element to the #where method:
arr = ["foo", "bar", "baz"]
Widget.where(name: arr)
As for the SQL output you've posted:
Label Load (0.1ms) SELECT "labels".* FROM "labels" WHERE "labels"."project_id" = $1 AND "labels"."title" = $2 ORDER BY "labels"."title" ASC LIMIT 1 [["project_id", 8], ["title", "label1,label2,label3"]]
The AND here is coming from the fact that a query is being made against the product_id as well as the title, not because of anything to do with title.

Rails: How would I retrieve records where child element includes any of another array?

I have a User and Document model, which both have tag_lists (from acts_as_taggable)
I want to find the Documents that have any of the tags a certain user has. So for example,
user.tag_list = ["ceo" , "sales"]
documentA.tag_list = ["ceo"]
documentB.tag_list = ["all-users']
documentC.tag_list = ["sales", "marketing"]
documentD.tag_list = ["marketing"]
I want to do something like
Document.where(tag_list.includes_any?("all-users" || user.tag_list))
-> which would retrieve document A,B,C.
Obviously the syntax is wrong, but you get the idea. How would I do this in an efficient way?
-------update
Just fyi, the tag_list is not a direct attribute of either User or Document model, but a active record relation from the acts_as_taggable gem: how would I call this in a .where operator?
2.1.1 :036 > Document.last.tag_list
Document Load (3.7ms) SELECT "assignable_objects".* FROM "assignable_objects" WHERE "assignable_objects"."type" IN ('Document') ORDER BY "assignable_objects"."id" DESC LIMIT 1
ActsAsTaggableOn::Tag Load (3.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 52], ["taggable_type", "AssignableObject"]]
=> []
This might help you:
Document.where((tag_list-["all-users", user.tag_list].flatten).size != tag_list.size)

rails gem will_paginate on top of custom method not working

In my controller, my filter_with_params() method is causing a syntax error in postgres when I try and stack will_paginate on top of it.
In my controller I call:
#styles = Style.filter_with_params(params).paginate(:page => params[:page], :per_page => 6)
Model method:
class Style < ActiveRecord::Base
def self.filter_with_params(params)
scoped = where("styles.item != ''")
if params[:t]
scoped = scoped.joins(:tags)
scoped = scoped.select("distinct styles.*, count(*) AS count")
scoped = scoped.where(tags: { name: params[:t] })
scoped = scoped.group('styles.id')
scoped = scoped.having("count(*) = #{params[:t].size}")
end
scoped
end
basically my filter method is looking for tags, and then i need to paginate on top of those results. Anyone with similar experience?
I'm using Rails 3 on this app
here is the postgres error
PG::Error: ERROR: syntax error at or near "distinct" LINE 1: SELECT COUNT(*) AS count_all, distinct styles.*, count(*) AS...
^
: SELECT COUNT(*) AS count_all, distinct styles.*, count(*) AS count, styles.id AS styles_id FROM "styles" INNER JOIN "tagizations" ON "tagizations"."style_id" = "styles"."id" INNER JOIN "tags" ON "tags"."id" = "tagizations"."tag_id" WHERE "tags"."name" IN ('engagement') AND (styles.polenza_item != '') GROUP BY styles.id HAVING count(*) = 1
Your SQL has a problem. You need to say distinct clause before the count ('count(*) as count_all'). That is, once you remove the first call to the count function, it should work.
SELECT distinct styles.*, count(*) AS count, styles.id AS styles_id FROM "styles" INNER JOIN "tagizations" ON "tagizations"."style_id" = "styles"."id" ...
You can test your query in your rails console:
>> sql = "SELECT distinct styles.*, count(*) AS count, styles.id AS styles_id..."
>> a = ActiveRecord::Base.connection.execute(sql)
>> a[0]
Hope this helps.

Resources