Select product when product.is is the same in another table - join

I need to select product names from product table when product.id from CART table is the same as product name from product table.
result = [2, 3, 4, 5] # id from table cart
this table has a product_id
test = self.database.query(Cart, Product)
.filter(Cart.id.in_(result)).join(Product)
.filter(Product.id == Cart.product_id).all()
Thank in advance

Related

ActiveRecord joins - Return only exact matches

I have three models, Outfit, Product, and a join model OutfitProduct (Outfit has many Products through OutfitProducts).
I would like to find outfits that contain only exact product matches.
So far I have this
def by_exact_products(products)
joins(outfit_products: :product)
.where(outfit_products: { product: products })
.group("outfits.id")
.having('count(outfits.id) = ?', products.size)
end
The above returns any outfit that contains the products I am searching for, even if it is not an exact match. I would like it to return only outfits that are an exact match.
Example:
Assume we have the following outfits made up of the following products:
outfit_1.products = [product_1, product_2, product_3, product_4]
outfit_2.products = [product_1, product_2]
outfit_3.products = [product_1, product_2, product_3]
If I passed [product_1, product_2, product_3] to my query, it will return outfit_1 and outfit_3 - I would like it to only return outfit_3 which is an exact match.
UPDATE (More info)
Calling the query with an array of three products produces the following query:
SELECT "outfits".*
FROM "outfits"
INNER JOIN "outfit_products"
ON "outfit_products"."outfit_id" = "outfits"."id"
INNER JOIN "products"
ON "products"."id" = "outfit_products"."product_id"
WHERE "outfit_products"."product_id" IN ( 18337, 6089, 6224 )
GROUP BY outfits.id
HAVING ( Count(outfits.id) = 3 )
Let's first have a look at why this is happening. You use the following scenario:
outfit_1.products = [product_1, product_2, product_3, product_4]
outfit_2.products = [product_1, product_2]
outfit_3.products = [product_1, product_2, product_3]
This would have the following outfit_products table:
outfit_id | product_id
----------|-----------
1 | 1
1 | 2
1 | 3
1 | 4
2 | 1
2 | 2
3 | 1
3 | 2
3 | 3
When you add the restriction:
WHERE "outfit_products"."product_id" IN ( 1, 2, 3 )
It will eliminate the row:
outfit_id | product_id
----------|-----------
1 | 4
And leave product 1 with 3 records, when you group and count the records you'll end up with a resulting value of 3 for product 1. This means the current query will only check for a minimum of the provided products (aka make sure all the provided products are present).
To also eliminate records that have more products than the provided products you'll have to add a second count. Which counts the products without the above restriction.
def by_exact_products(products)
# all outfits that have at least all products
with_all_products = joins(outfit_products: :product)
.where(outfit_products: { product: products })
.group("outfits.id")
.having('count(outfits.id) = ?', products.size)
# all outfits that have exactly all products
joins(outfit_products: :product)
.where(id: with_all_products.select(:id))
.group("outfits.id")
.having('count(outfits.id) = ?', products.size)
end
This will select all outfits that have have at least all provided products, and count their product total.

How can I find each ascendent of a record in a self-referential table without N+1

I am working with a self-referential model (Category) in a Ruby on Rails app, which has the following columns:
id, name, level, parent_id
belongs_to :parent, class_name: 'Category', optional: true
The concept is that a level 1 category can have a level 2 subcategory, which can have a level 3 subcategory, etc.
For example:
Category id: 1, name: 'Dessert', level: 1, parent_id: nil
Category id: 2, name: 'Cold', level: 2, parent_id: 1
Category id: 3, name: 'Cake', level: 3, parent_id: 2
Category id: 4, name: 'Ice Cream', level: 3, parent_id: 2
Category id: 5, name: 'Sponge', level: 4, parent_id: 3
I'd like to find each ascendent of a record, regardless of how many levels deep it is. I then want to concatenate all the names, in ascending order, into one string.
i.e., if I'm starting with Sponge, I'd like a method which returns "Dessert - Cold - Cake - Sponge"
What I have so far works but is an n+1 and doesn't feel very Railsy:
def self.concatenate_categories(order)
category = order.category
categories_array = []
order.category.level.times do
categories_array.push category.name
category = Category.find(category.parent_id) if category.parent_id.present?
end
categories_array.reverse.join(' - ')
end
If this order is for Sponge, I get "Dessert - Cold - Cake - Sponge".
If the order is for Cake, I get "Dessert - Cold - Cake".
You can try a recursive CTE to get each category parent based on its parent_id:
WITH bar AS (
WITH RECURSIVE foo AS (
SELECT
categories.id,
categories.name,
categories.parent_id
FROM categories
WHERE categories.id = 5
UNION
SELECT
p.id,
p.name,
p.parent_id
FROM categories p
INNER JOIN foo f
ON f.parent_id = p.id
) SELECT name FROM foo ORDER BY id
) SELECT STRING_AGG(name, ' - ') FROM bar
How about this? I haven't tested that code, but you get the idea, join as many times as there are levels and query once. Depending on how many levels there are, your solution can be faster than too many joins.
def self.concatenate_categories(order)
scope = order.category
categories_array = if category.level > 1
scope = scope.select('categories.name')
order.category.level.downto(1) do |l|
scope = scope.joins("JOIN categories as c#{l} ON categories.id = c#{l}.parent_id")
.select("c#{l}.name")
end
scope.to_a
else
Array.wrap(scope.name)
end
categories_array.reverse.join(' - ')
end

List unique line items and their daily counts

In my rails app, new line items are created daily. I need to be able to have my smart_listing show how many apples and oranges were ordered. For instance:
Line Item QTY
Apple 2
Orange 1
What I am getting is:
Line Item QTY
Apple 1
Apple 1
Orange 1
line_item_scope = LineItem.all
line_item_scope = line_item_scope.where(created_at: Date.today.beginning_of_day..Date.today.end_of_day)
if customer_signed_in?
line_item_scope = line_item_scope.ticket.customer(current_customer.id)
end
#line_items = smart_listing_create(:line_items, line_item_scope, partial: "line_items/listing2", default_sort: {updated_at: "desc"})
My initial thought was to create a .map(&:name).uniq but that returns an array when I need a relationship to go into the smart listing.
If you need to display just LineItem's name and the number of items of that name, then group method can help:
line_item_scope.group(:name).count
This will construct a hash:
result = { "Apple" => 2, "Orange" => 1 }
Then this hash can be iterated to display the values:
result.each do |name, count|
...
end
Or the number of line items can be selected as a column:
line_items_scope =
LineItem.group(:name)
.order(:name)
.select("name, COUNT(*) as count")
Then line_items_scope can be fed to smart_listing_create as a ActiveRecordRelation

Rails query association AND

I have an User model with a HABTM association with Tag model. I need all users who necessarily have all conditions, not just one.
Ex:
User.includes(:tags).where(tags: { id: [2,3,...] })
Returns users who have tags with id 2 and / or 3, but I would like to only return users who have tags with ids 2 AND 3.
I can think of this option:
User.includes(:tags).where(tags: { id: 2 }).where(tags: { id: 3 })
If you have a severals tag_ids, and if the intermediate table between user and tags is user_tags
tag_ids = [1, 2, 3, 4, 5]
where = tag_ids.map do |id|
"tags.id = #{id}"
end.join(" AND ")
puts where # "tags.id = 1 AND tags.id = 2 AND tags.id = 3 AND tags.id = 4 AND tags.id = 5"
User.joins("INNER JOIN user_tags ON user_tags.user_id = user.id INNER JOIN tags ON tags.id = user_tags.tag_id").where(where)
If you wish to continue using the includes, there is no other choice that continue using the where in the rails-ish way.
How about:
user_ids = Tag.where(id: [2,3]).pluck(:user_id).uniq
User.where(id: user_id)
pluck won't instantiate all the tag objects, so while this may not be ideal, it should at least be pretty quick.

Rails - Query table with array of IDs and order based on the array

Let me describe with simple example.
I have a list of numbers:
ex: list = [ 3, 1, 2 ]
and I have a table in DB named Products which have 3 rows with product_id = 1, 2and 3.
Now I need to query Products sort by list values (3,1,2) so the result will be:
product_3
product_1
product_2
Query will be like:
product.sort(list) or product.order(list) or some other alternative pls
This should work for you:
list = [3, 1, 2]
Product.where(product_id: list).sort_by { |p| list.find_index(p.product_id) })

Resources