Rails: Find rows without connection in HABTM relation

前端 未结 3 615
悲哀的现实
悲哀的现实 2020-12-29 15:15

I have two models, Users and Leads connected with HABTM relation:

class Lead < ActiveRecord::Base
  has_and_belongs_to_many :use         


        
相关标签:
3条回答
  • 2020-12-29 15:53

    Kerozu, if you want to display leads that have not been bought by current user only, you can use raw sql like this

        sql = <<-SQL
          SELECT *
          FROM leads
          WHERE id NOT IN (
            SELECT lead_id
            FROM leads_users
            WHERE user_id = ?
          )
        SQL
    
        Lead.find_by_sql [sql, id]
    
    0 讨论(0)
  • 2020-12-29 15:56

    What you're looking for is known as an anti join .

    There are three standard ways to accomplish this,

    1. Using a null left outer join
    2. Using a where clause with a sub query with the NOT & IN keywords
    3. Using a where clause with the NOT & EXISTS keywords

    Basically, the EXISTS keyword will check if any row is returned by the sub query and report that as a match, NOT obviously negates that true match.

    here's my preferred way (using NOT & EXISTS)

    class User < ActiveRecord::Base
      has_and_belongs_to_many :leads
      def self.without_leads
        where(<<-SQL)
          NOT EXISTS (SELECT 1 
            FROM   leads_users 
            WHERE  users.id = leads_users.user_id) 
        SQL
      end
    end
    
    class Lead < ActiveRecord::Base
      has_and_belongs_to_many :users
      def self.without_users
        where(<<-SQL)
          NOT EXISTS (SELECT 1 
            FROM   leads_users 
            WHERE  leads.id = leads_users.lead_id) 
        SQL
      end
    
     def self.not_connected_to(user)
        where(<<-SQL, user.id)
          NOT EXISTS (SELECT 1 
            FROM   leads_users 
            WHERE  leads.id = leads_users.lead_id
            AND leads_users.user_id = ?
            ) 
        SQL
      end
    end
    

    here's a non SQL approach using arel

    class User < ActiveRecord::Base
      has_and_belongs_to_many :leads
      def self.without_leads
        habtm_table = Arel::Table.new(:leads_users)
        join_table_with_condition = habtm_table.project(habtm_table[:user_id])
        where(User.arel_table[:id].not_in(join_table_with_condition))
      end
    end
    
    class Lead < ActiveRecord::Base
      has_and_belongs_to_many :users
      def self.without_users
        habtm_table = Arel::Table.new(:leads_users)
        join_table_with_condition = habtm_table.project(habtm_table[:lead_id])
        where(Lead.arel_table[:id].not_in(join_table_with_condition))
      end
    end
    

    here is an example repo

    Find users without leads

    User.where(user_id: 1).without_leads
    

    Find leads without users

    Lead.without_users
    

    Find leads not connected to a specific user

    Lead.not_connected_to(user)
    

    chain a sort

    Lead.without_users.order(:created_at)
    
    0 讨论(0)
  • 2020-12-29 16:00

    You can use a left join. Assuming your join table is named leads_users:

    Lead.joins('LEFT JOIN leads_users ON leads_users.lead_id = leads.id').
         where(leads_users: { user_id: nil })
    
    0 讨论(0)
提交回复
热议问题