Delete multiple tables in a single query with join in rails 5 - ruby-on-rails

This is my query in rails, I want to delete entries from feed and feed mappings table which match certain conditions in a single query, If I use destroy_all it deletes items one by one, first it will delete feed and then feed_mappings for each entry, whereas If I can just change the Select statement on the resulting query into Delete feed, feed_mappings I can delete everything in one query. Help me solve this problem in a single query.
Feed.joins(:feed_mappings)
.joins('inner join notifications on notifications.id = feed_mappings.source_id')
.joins('inner join user_feedbacks on user_feedbacks.source_id = notifications.picked_for_company')
.joins('inner join user_feedback_contexts on user_feedback_contexts.user_feedback_id = user_feedbacks.id')
.where(user_feedbacks: {source_type: 'Company', user_id: user.id},
user_feedback_contexts: {context: 'block'},
notifications: {user_id: user.id},
feed_mappings: {source_type: 'Notifications::Notification'})
This generates
SELECT `feeds`.* FROM `feeds`
INNER JOIN `feed_mappings`
ON `feed_mappings`.`feed_id` = `feeds`.`id`
INNER JOIN notifications
ON notifications.id = feed_mappings.source_id
INNER JOIN user_feedbacks
ON user_feedbacks.source_id = notifications.picked_for_company
INNER JOIN user_feedback_contexts
ON user_feedback_contexts.user_feedback_id = user_feedbacks.id
WHERE `user_feedbacks`.`source_type` = 'Company'
AND `user_feedbacks`.`user_id` = 6
AND `user_feedback_contexts`.`context` = 'block'
AND `notifications`.`user_id` = 6
AND `feed_mappings`.`source_type` = 'Notifications::Notification'
What I want instead
DELETE `feeds`, `feed_mappings` FROM `feeds`
INNER JOIN `feed_mappings`
ON `feed_mappings`.`feed_id` = `feeds`.`id`
INNER JOIN notifications
ON notifications.id = feed_mappings.source_id
INNER JOIN user_feedbacks
ON user_feedbacks.source_id = notifications.picked_for_company
INNER JOIN user_feedback_contexts
ON user_feedback_contexts.user_feedback_id = user_feedbacks.id
WHERE `user_feedbacks`.`source_type` = 'Company'
AND `user_feedbacks`.`user_id` = 6
AND `user_feedback_contexts`.`context` = 'block'
AND `notifications`.`user_id` = 6
AND `feed_mappings`.`source_type` = 'Notifications::Notification'

All the joins complicates things, but does the following work?
Feed.joins(:feed_mappings)
.joins('inner join notifications on notifications.id = feed_mappings.source_id')
.joins('inner join user_feedbacks on user_feedbacks.source_id = notifications.picked_for_company')
.joins('inner join user_feedback_contexts on user_feedback_contexts.user_feedback_id = user_feedbacks.id')
.where(user_feedbacks: {source_type: 'Company', user_id: user.id},
user_feedback_contexts: {context: 'block'},
notifications: {user_id: user.id},
feed_mappings: {source_type: 'Notifications::Notification'}).delete_all
FeedMappings.joins('inner join notifications on notifications.id = feed_mappings.source_id')
.joins('inner join user_feedbacks on user_feedbacks.source_id = notifications.picked_for_company')
.joins('inner join user_feedback_contexts on user_feedback_contexts.user_feedback_id = user_feedbacks.id')
.where(user_feedbacks: {source_type: 'Company', user_id: user.id},
user_feedback_contexts: {context: 'block'},
notifications: {user_id: user.id},
feed_mappings: {source_type: 'Notifications::Notification'}).delete_all

Related

How do I change this query to return an association instead of an array in Rails and Postgres?

Suppose I have this query:
t.pool_tournament_matches
.where(status: "unstarted")
.joins("INNER JOIN pool_tournament_match_users ON pool_tournament_match_users.pool_tournament_match_id = pool_tournament_matches.id")
.joins("INNER JOIN users ON users.id = pool_tournament_match_users.user_id")
.group("pool_tournament_matches.id")
.select("pool_tournament_matches.*, COUNT(users.id) AS user_count")
.select {|m| m.user_count == 2}
The result does return the right matches but it's an array which does not work well with RABL template engine. What converts the query to an array is
.select {|m| m.user_count == 2}` I think.
How would I do this using SQL so that it will return an association and I can chain it further if needed.
I've tried:
t.pool_tournament_matches
.where(status: "unstarted")
.joins("INNER JOIN pool_tournament_match_users ON pool_tournament_match_users.pool_tournament_match_id = pool_tournament_matches.id")
.joins("INNER JOIN users ON users.id = pool_tournament_match_users.user_id")
.group("pool_tournament_matches.id")
.select("pool_tournament_matches.*, COUNT(users.id) = 2)
but it does not work.
t.pool_tournament_matches
.where(status: "unstarted")
.joins("INNER JOIN pool_tournament_match_users ON pool_tournament_match_users.pool_tournament_match_id = pool_tournament_matches.id")
.joins("INNER JOIN users ON users.id = pool_tournament_match_users.user_id")
.group("pool_tournament_matches.id")
.having("COUNT(users.id) = 2")
.select("pool_tournament_matches.*, COUNT(users.id) AS user_count")

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

Multi-dimension instance loop

I am trying to use a custom sql query to display different attributes for a product i.e. Size and Price. The query I have when running in console displays as it should
SELECT products.id, products.name, variant_properties.description, LEFT(variant_properties.description,1) as short_desc, variants.price FROM products
INNER JOIN product_properties ON product_properties.product_id = products.id
INNER JOIN variant_properties on product_properties.property_id = variant_properties.property_id AND variant_properties."primary" = true
INNER JOIN properties ON properties.id = product_properties.property_id AND properties.id = variant_properties.property_id AND properties.display_name = 'Size'
INNER JOIN variants on variants.product_id = products.id AND variants.id = variant_properties.variant_id
In my HAML template I have done the following
- #products.each_with_index do |product, i|
.product-list.grid-block
.small-8.grid-content.text-center
%h4= product.name.titlecase
- #sizes.each do |size|
= link_to size.short_desc, product, class: 'hollow button tiny'
%small= size.price
and in the controller
products = Product.active
# products = Product.active.includes(:variants)
product_types = nil
if params[:product_type_id].present? && product_type = ProductType.find_by_id(params[:product_type_id])
product_types = product_type.self_and_descendants.map(&:id)
end
if product_types
#products = products.where(product_type_id: product_types)
else
#products = products
end
#sizes = Product.find_by_sql("SELECT products.id, LEFT(variant_properties.description,1) as short_desc, variants.price FROM products
INNER JOIN product_properties ON product_properties.product_id = products.id
INNER JOIN variant_properties on product_properties.property_id = variant_properties.property_id
INNER JOIN properties ON properties.id = product_properties.property_id AND properties.id = variant_properties.property_id AND properties.display_name = 'Size'
INNER JOIN variants on variants.product_id = products.id AND variants.id = variant_properties.variant_id")
Ideally I am trying to get it to look something like below, though I am having issues achieving this
The first thing is notice is that Model.find_by_sql will return a list of models and nothing more no matter what you select in your sql query.
So the solution I suggest is trying to convert to ActiveRecord::Relation like this:
Product.joins(:product_properties)
.joins('INNER JOIN variant_properties on product_properties.property_id = variant_properties.property_id')
.joins('INNER JOIN properties ON properties.id = product_properties.property_id AND properties.id = variant_properties.property_id AND properties.display_name = \'Size\'')
.joins('INNER JOIN variants on variants.product_id = products.id AND variants.id = variant_properties.variant_id')
.pluck('products.id', 'LEFT(variant_properties.description,1)', 'variants.price')
I haven't tried yet but I think it could produce an array of arrays contains the value you need.

Why I cannot use both ORDER BY and DISTINCT * in SQL?

I'm trying to do the following, and if I were to uncomment the distinct it will break. Also if I comment out the order and leave the distinct in, it will work.
Contestant.joins('INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = '+ season_number.to_s)
.joins('LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = '+self.id.to_s+') AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id')
.joins('INNER JOIN season_rosters ON season_rosters.season_id = V.season_id')
.where('V.is_jury_vote = (?) AND V.contestant_id <> (?) AND XV.tribal_council_key IS NOT NULL', :false, self.id)
.order('season_rosters.finished')
#.distinct
The error I get is below...
TinyTds::Error: Incorrect syntax near '*'.: EXEC sp_executesql N'SELECT DISTINCT *, __order FROM ( SELECT [contestants].*, DENSE_RANK() OVER (ORDER BY season_rosters.finished ASC) AS __order, ROW_NUMBER() OVER (PARTITION BY [contestants].* ORDER BY season_rosters.finished ASC) AS __joined_row_num FROM [contestants] INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = 6 LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = 112) AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id INNER JOIN season_rosters ON season_rosters.season_id = V.season_id WHERE (V.is_jury_vote = (''false'') AND V.contestant_id <> (112) AND XV.tribal_council_key IS NOT NULL) ) AS __sq WHERE __joined_row_num = 1 ORDER BY __order'
The issue is with this part:
SELECT DISTINCT *, __order
Try adding the required columns to your GROUP BY.
Contestant.joins('INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = '+ season_number.to_s)
.joins('LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = '+self.id.to_s+') AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id')
.joins('INNER JOIN season_rosters ON season_rosters.season_id = V.season_id')
.where('V.is_jury_vote = (?) AND V.contestant_id <> (?) AND XV.tribal_council_key IS NOT NULL', :false, self.id)
.order('season_rosters.finished')
.group('col1,col2,__order')
Also in your SQL error, order by is on a different column while in your code, it is on season_rosters.finished.

PostgreSQL - must appear in the GROUP BY clause or be used in an aggregate function

This query works locally, but doesn't work on Heroku for some reason.
2014-06-26T00:45:11.334388+00:00 app[web.1]: PG::GroupingError: ERROR: column
"conversations.updated_at" must appear in the GROUP BY clause or be used in an
aggregate
function
This is the SQL.
Conversation.joins("INNER JOIN notifications ON
notifications.conversation_id = conversations.id AND
notifications.type IN ('Message') INNER JOIN receipts
ON receipts.notification_id = notifications.id WHERE
notifications.type = 'Message' AND (receipts.receiver_id = #{a.id}
AND receipts.receiver_type = 'Profile') AND conversations.id
IN #{active_conversations_ids} ORDER BY conversations.updated_at DESC")
I tried doing distinct and it did not work, and I tried group_by('conversation_id')
the to_sql
SELECT "conversations".* FROM "conversations" INNER JOIN
notifications ON notifications.conversation_id = conversations.id
AND notifications.type IN ('Message') INNER JOIN receipts
ON receipts.notification_id = notifications.id WHERE
notifications.type = 'Message' AND (receipts.receiver_id = 104
AND receipts.receiver_type = 'Profile') AND conversations.id
IN (…) ORDER BY conversations.updated_at DESC"
You can see the SQL in your server window, if you are running the app in local machine.
It is a good practice to keep the joins where they belong to.
Conversation.where('id' => active_conversations_ids).joins(
"INNER JOIN notifications ON notifications.conversation_id = conversations.id
AND notifications.type IN ('Message')
INNER JOIN receipts ON receipts.notification_id = notifications.id
AND (receipts.receiver_id = #{a.id} AND receipts.receiver_type = 'Profile')"
).order('updated_at')
And for the better readability, you can use notifications with symbols.
Conversation
.find_all_by_id(active_conversations_ids)
.joins(:notifications, :receipts).where({
notifications: { type: 'Message' },
receipts: { receiver_id: a.id, receiver_type: 'Profile'}
})
.order('updated_at')
My final suggestion would be to google for named scopes.
http://zachholman.com/2010/01/simplifying-rails-controllers-with-named_scopes/
It was as simple as adding
GROUP BY conversations.id
which ends up looking like
Conversation.joins("INNER JOIN notifications ON
notifications.conversation_id = conversations.id AND
notifications.type IN ('Message') INNER JOIN receipts ON
receipts.notification_id = notifications.id WHERE
notifications.type = 'Message' AND (receipts.receiver_id = #{self.id}) AND
conversations.id IN #{active_conversation_ids} GROUP BY
conversations.id ORDER BY conversations.updated_at DESC")
.select('conversations.id, conversations.updated_at')

Resources