Пресечна точка на две отношения

Да кажем, че имам две релации, които държат записи в един и същ модел, като например:

@companies1 = Company.where(...)
@companies2 = Company.where(...)

Как мога да намеря пресечната точка на тези две отношения, т.е. само онези компании, които съществуват и в двете?


person sscirrus    schedule 22.06.2011    source източник
comment
Изглежда, че не искате sql тук. Така че можете да използвате метода за пресичане на масиви: ruby-doc.org/core /classes/Array.html#M000274   -  person apneadiving    schedule 22.06.2011


Отговори (5)


По подразбиране свързването на тези where заедно създава И, което е това, което искате.

Толкова много са:

class Company < ActiveRecord::Base
  def self.where_1
    where(...)
  end
  def self.where_2
    where(...)
  end
end

@companies = Company.where_1.where_2

====== АКТУАЛИЗИРАНО ======

Има два случая:

# case 1: the fields selecting are different
Company.where(:id => [1, 2, 3, 4]) & Company.where(:other_field => true)
# a-rel supports &, |, +, -, but please notice case 2

# case 2
Company.where(:id => [1, 2, 3]) & Company.where(:id => [1, 2, 4, 5])

# the result would be the same as
Company.where(:id => [1, 2, 4, 5])
# because it is &-ing the :id key, instead of the content inside :id key

Така че, ако сте в случай 2, ще трябва да направите като това, което @apneadiving коментира.

Company.where(...).all & Company.where(...).all

Разбира се, правенето на това изпраща две заявки и най-вероятно е поискало повече резултати, отколкото са ви необходими.

person PeterWong    schedule 22.06.2011
comment
Питър, приемете, че тези отношения са достигнати чрез различни средства, които не включват и двете where. Има ли ръчен начин за изчисляване на това пресичане? - person sscirrus; 22.06.2011
comment
@sscirrus Актуализира отговора, моля, погледнете. - person PeterWong; 22.06.2011
comment
Това не връща ли масив? Как мога да го накарам да върне ActiveRecord Resource? - person Mohamed El Mahallawy; 20.06.2014
comment
Току-що го пробвах със символа & и ми свърши работа. - person adg; 21.07.2016

Решавам подобен проблем по този начин

Company.connection.unprepared_statement do
  Company.find_by_sql "#{@companies1.to_sql} INTERSECT #{@companies2.to_sql}"
end

Имаме нужда от unprepared_statement блок тук, защото най-новите версии на Rails използват подготвени изрази за ускоряване на arel заявките, но се нуждаем от чист SQL на място.

person mikdiet    schedule 28.08.2013
comment
Това е толкова хубаво. Може да се приложи с string::join за пресичане на колекция от sql изрази. - person jgomo3; 15.03.2020

Използвайте sql ключова дума INTERSECT.

params1 = [1,2,4]
params2 = [1,3,4]
query = "
SELECT companies.* FROM companies
WHERE id in (?,?,?)
INTERSECT
SELECT companies.* FROM companies
WHERE id in (?,?,?)
"
Company.find_by_sql([query, *params1, *params2])

ще бъде по-бързо от предишното решение.

person Michael    schedule 02.07.2012
comment
Благодаря за вашият отговор! Какво се случва, ако params1 и params2 са масиви с неопределена дължина? - person sscirrus; 03.07.2012
comment
1. Можете да промените условието where, ако не намирате по ids about. 2. Нещо като: ...WHERE id IN (#{params1.map{'?'}.join(',')})... - person Michael; 04.07.2012
comment
Открих, че #{Array.new(params1.size, '?').join(',')} е по-бърз. Но това не е толкова важно в случая. - person Michael; 06.07.2012

Можете да използвате ActiveRecord::SpawnMethods#merge

Пример:

Company.where(condition: 'value').merge(Company.where(other_condition: 'value'))
person KARASZI István    schedule 07.10.2015
comment
Той просто добавя условие where. Не е алтернатива за INTERSECT - person Atul Yadav; 26.02.2017

За всеки, който е заседнал с Rails4 и не може да използва Rails5 .or синтаксис:

Имах динамичен брой големи заявки, които имаха подобни условия (и следователно също подобни резултати). Моят рейк сървър би имал проблеми, когато всички те наведнъж се инстанциират, преобразуват в масиви и след това се обединят.

Имах нужда от ActiveRecord::Relation (все още не е активирана), за да използвам с find_each.

Изглеждаше нещо подобно:

Class Conditions
  def initialize
    self.where_arr = []
    self.joins_arr = []
  end

  def my_condition1
    where_arr << 'customers.status = "active"'
    joins_arr << :customer
  end

  def my_condition2
    where_arr << 'companies.id = 1'
  end
end

conditions = []    
joins = []
# probably call them in a .each block with .send
conditions1 = Conditions.new
conditions1.my_condition1
conditions1.my_condition2
conditions << "(#{conditions1.where_arr.join(' AND ')})"
joins << conditions1.joins_arr

conditions2 = Conditions.new
conditions2.my_condition1
joins << conditions2.joins_arr

Company.joins(joins).where(conditions.join(' OR '))

=> SELECT companies.* FROM companies 
INNER JOIN customers ON companies.customer_id = customers.id 
WHERE (customers.status = 'active' AND companies.id = 1) OR
(customers.status = 'active')

Някак хакерско, но работи, докато, надяваме се, можете да мигрирате към Rails 5

person lennart_dev    schedule 17.04.2018