views:

89

answers:

4

For example this query:

SELECT `variants`.* 
  FROM `variants` INNER JOIN `variant_attributes` 
    ON variant_attributes.variant_id = variants.id 
 WHERE (variant_attributes.id IN ('2','5'))

And variant has_many variant_attributes

What I actually want to do is to find which variant has BOTH variant attributes with ID = 2 and 5. Is this possible with MySQL? Bonus Question, is there a quick way to do this with Ruby on Rails, perhaps with SearchLogic?

solution

Thank you Quassnoi for the query you provided, that worked perfectly.

To use on Rails, I used the named_scope below, I think this is simpler to understand for beginners.

Basically named_scope would return {:from => x, :conditions => y} and the lines above were used to setup the y variable.

  named_scope :with_variant_attribute_values, lambda { |values|
    conditions = ["(
            SELECT  COUNT(*)
            FROM    `variant_attributes`
            WHERE   variant_attributes.variant_id = variants.id
                    AND variant_attributes.value IN (#{values.collect { |value| "?" }.join ", "})
            ) = ?
    "]
    conditions = conditions + values + [values.length]
    {
    :from => 'variants', 
    :conditions => conditions
  }}
+3  A: 

Assiuming that variant_attributes (variant_id, id) is unique:

SELECT  `variants`.*
FROM    `variants`
WHERE   (
        SELECT  COUNT(*)
        FROM    `variant_attributes`
        WHERE   variant_attributes.variant_id = variants.id
                AND variant_attributes.id IN ('2','5')
        ) = 2
Quassnoi
Thank you, this works! Please read my post below for named_scope I use in Rails.
jaycode
+1  A: 

Quassnoi has posted the mysql query that does what you would like. Here is a method for the Variant model that will do the equivalent. I'm doing two approaches, one if variant_attributes (variant_id, id) are a unique combination, and one if they aren't

Unique:

class Variant < ActiveRecord::Base
  has_many :variant_attributes
  named_scope :with_variant_attributes, lamda { |*ids|
     ids = ids.flatten
     if(ids.length>0)
       result = {:include => :variant_attributes}
       sql_params = {:length => ids.length,:ids => ids}
       result[:conditions] = ["(:length = (select count(*) from variant_attributes
                                          where id in (:ids))",sql_params]
       result
     else
       nil
     end
   }
end

Non Unique

class Variant < ActiveRecord::Base
  has_many :variant_attributes
  named_scope :with_variant_attributes, lamda { |*ids|
     ids = ids.flatten
     if(ids.length>0)
       result = {:include => :variant_attributes}
       conditions = []
       sql_params = {}

       ids.each_with_index do |id,i|
         conditions << "( 1 = Select Count(*) from variant_attributes where id = :id#{i})"
         sql_params["id#{i}"] =  id
       end
       result[:conditions] = [ '(' + conditions.join(' AND ') + ')', sql_params]
       result
     else
       nil
     end
   }
end

Which can be used in the following ways:

# Returns all Variants with variant_attributes 1, 2, & 3
vars = Variant.with_variant_attributes(1,2,3) 

# Returns Variant 5 if it has attributes 3 & 5, or null if it doesn't
vars = Variant.with_variant_attributes(3,5).find_by_id(5)

#Returns Variants between 1 and 20 if that have an attribute of 2
vars = Variant.with_variant_attributes(2).find(:conditions => "id between 1 and 20")

#can accept a variable array of ids
my_ids = [3,5]
vars = Variant.with_variant_attributes(my_ids)

This code hasn't been tested.

Tilendor
the code is too complicated... and it returns error: Mysql::Error: Unknown column '(:length = (select count(*) from variant_attributes . . .how to fix?Thanks for trying to help though
jaycode
Whoops, I had the wrong brackets on results[:conditions] = ... I had put in {} brackets, but that was wrong. I've corrected the answer to use [] brackets.About the complication... You want a feature that isn't supported natively. To make it easy to use takes some work.
Tilendor
A: 

I would create a named_scope for this:

class Variant < ActiveRecord::Base
  has_many    :variant_attributes

  named_scope :with_variant_attributes, lambda { |*ids| { 
            :joins => :variant_attributes, 
            :conditions => {:variant_attributes=>{:id=>ids}},
            :group => "variants.id",
            :having => "count(variants.id) = #{ids.size}"
            }
          }
end

Now you can use the named scope as follows:

Variant.with_variant_attributes(1,2)
Variant.with_variant_attributes(1,2,3,4)
KandadaBoggu
A: 

Thank you Quassnoi for the query you provided, that worked perfectly.

To use on Rails, I used the named_scope below, I think this is simpler to understand for beginners.

Basically named_scope would return {:from => x, :conditions => y} and the lines above were used to setup the y variable.

  named_scope :with_variant_attribute_values, lambda { |values|
    conditions = ["(
            SELECT  COUNT(*)
            FROM    `variant_attributes`
            WHERE   variant_attributes.variant_id = variants.id
                    AND variant_attributes.value IN (#{values.collect { |value| "?" }.join ", "})
            ) = ?
    "]
    conditions = conditions + values + [values.length]
    {
    :from => 'variants', 
    :conditions => conditions
  }}
jaycode