Объединение двух результатов монго

У меня есть ситуация, когда мне нужно вернуть одну коллекцию объектов из монго, но для получения результатов нужно использовать два запроса. Порядок этих результатов важен, потому что они разбиты на страницы.

Вот первый запрос: (объявления на основе категории и ценового диапазона)

my_listings = MoListing.where(criteria_a)

Второй запрос должен использовать результаты первого запроса в качестве фильтра. Итак, что-то вроде:

everything_else = MoListing.where(criteria_b)

Затем объедините результаты:

my_listings << everything_else

И, наконец, верните результаты с разбивкой на страницы:

my_listings.page(1).per(25)

Кажется, часть моей проблемы заключается в том, что запросы монго не выполняются до тех пор, пока они не потребуются. Есть ли способ запустить выполнение запроса в заданной точке? Или есть другой подход, который я должен использовать при создании этого набора результатов?

Обновить с дополнительной информацией

Поведение, которое я вижу, заключается в том, что возвращаются только результаты в listings. Я также подтвердил, что everything_else действительно содержит ожидаемые записи (48 записей в my_listings, 52 во всем_остальном, как и ожидалось).

При применении .all к моим запросам, как указано в комментариях, это не влияет. puts listings.inspect приводит к

10:57:00 web.1   |    #<Mongoid::Criteria
10:57:00 web.1   |    selector: {"price"=>{"$gte"=>25, "$lte"=>75}},
10:57:00 web.1   |    options:  {},
10:57:00 web.1   |    class:    MoListing,
10:57:00 web.1   |    embedded: false>

Однако listings.count приводит к 48. Я просто пропустил какой-то глупый простой способ слияния этих результатов? И как только я получу результаты в одной коллекции, как это повлияет на последующие функции разбиения на страницы. Я использую kaminari для нумерации страниц.

Обновление 2

Согласно ответу ниже и моим собственным пробам и ошибкам, я нашел to_a решением, но не идеальным. Это работает:

#merge the results together as an Array
results = (listings.to_a | everything_else.to_a)

Это приводит к тому, что нумерация страниц через Kaminari должна измениться, поскольку мы больше не работаем с критериями монго, а вместо этого используем стандартный массив. Вот новый метод пагинации:

Kaminari.paginate_array(results).page(page).per(per_page)

Работа с небольшим набором данных из 100 записей, это нормально и денди - 54 мс

"debug":{"success":true,"pre_render_duration":54.808775999999995,"overall_duration":86.36554100000001,"count":25},"pagination":{"total_pages":4,"current_page":1}}

Однако, используя больший набор данных, я вижу значительно более медленное время при использовании метода .to_a для их объединения. Хотя примеры не совсем похожи друг на друга, эта большая разница указывает на проблему, заключающуюся в том, что to_a возвращает все, что вынуждает Каминари работать с гораздо большим количеством реальных данных:

Мои результаты без to_a, просто возвращая все записи с примененными критериями - 15 мс

"debug":{"success":true,"pre_render_duration":15.107164,"overall_duration":18.267599,"count":25},"pagination":{"total_pages":81,"current_page":1}}

Мои результаты с to_a, объединение двух наборов результатов - 415 мс

"debug":{"success":true,"pre_render_duration":415.258199,"overall_duration":450.66537800000003,"count":25},"pagination":{"total_pages":81,"current_page":1}}

Подводя итог, это недопустимый вариант. Возврат каждого набора данных по отдельности занимает ‹15 мс даже с большим набором данных, поэтому я думаю, что мне нужно выполнить способ объединения критериев вместе, чтобы один запрос выполнялся для Mongo, позволяя выполнять разбиение на страницы в БД, где это должно быть быть.

В SQL я бы сделал что-то вроде

select
  *
from
  listings
where
  field = "blah"
union all
select
  *
from
  listings
where
  field <> "blah"

Возможно ли это сделать в Монго?


person Levi Rosol    schedule 07.09.2012    source источник
comment
Что вы имеете в виду под частью вашей проблемы? Вы получаете неполные результаты? Вы получаете только списки?? Это так? пожалуйста, уточните, в чем здесь проблема?   -  person Jatin Ganhotra    schedule 07.09.2012
comment
Решение для запуска выполнения в Active Record использует функции .all. Просто добавьте .all после ваших запросов и они будут выполняться там сами и ленивой загрузки не будет.   -  person Jatin Ganhotra    schedule 07.09.2012


Ответы (3)


Возможно, вы могли бы создать класс для инкапсуляции сведений о том, как данные извлекаются для этого конкретного массива, и с помощью драйвера Mongo вы могли бы пропустить и ограничить параметры запроса, чтобы уменьшить объем передаваемых данных.

Используя этот подход, вы можете использовать что-то вроде этого (имена не очень хорошие, и я не тестировал код, но вы поймете):

class MoListingDataRetriever
  def initialize(page_size)
    @page_size = page_size / 2 #since you'll have two queries
    driver_instance = MoListing.db #just an exemple. You could use any of your classes that are mongo documents to do this
    @collection_driver = driver_instance.collection("mo_listing") #or whatever you collection name is on mongo
  end

  def retrieve_mo_listings(query_param_a, query_param_b, current_page)
    query_options = {
      limit: @page_size,
      page: current_page,
      skip: (@page_size * (current_page - 1)) #to skip a number of records already retrieved from the query
    }
    results_from_query_a = @driver_instance.find(query_param_a, query_options)
    results_from_query_b = @driver_instance.find(query_param_a, query_options)
    results_from_query_b.to_a.concat(results_from_query_b.to_a)    
  end
end
person Rudy Seidinger    schedule 13.09.2012
comment
В конечном итоге это стало ответом. Вместо того, чтобы использовать Kaminari для пейджинга, мне нужно было свернуть свой собственный и выполнить логику, чтобы выяснить, нужны ли вообще записи из любого набора результатов. Не идеально, но работает и относительно быстро. - person Levi Rosol; 17.09.2012

Это может быть грубый способ сделать это:

# Let us say the listings is obtained using listing_query_params
listings = MoListing.where(listing_query_params)

# and everything else is from everything_else_query_params
everything_else = MoListing.where(everything_else_query_params)

results = [listings.to_a, everything_else.to_a].flatten

results.page(1).per(25)

Это то, что вы хотите? Я попробовал это на одной из моих монгоидных моделей и, похоже, работает так.

PS: Но у .to_a есть удар по производительности - все наборы результатов извлекаются и объединяются. Но, глядя на количество записей (~50 за штуку), которые вы упомянули, это должно быть в порядке.

person Suren    schedule 10.09.2012
comment
Это очень похоже на решение, которое я придумал. Единственная разница в том, что я использовал объединение Array вместо flatten — results = (listings | Everything_else). Это повлияло на то, как работает Kaminari, поэтому я обновлю свой пост с подробностями для уточнения после дополнительного тестирования. Кроме того, в реальном мире это будет использовать 1000 записей, поэтому вы поднимаете хороший вопрос о производительности. Я собираюсь проверить больше данных, чтобы убедиться, что они все еще приемлемы, прежде чем отметить этот ответ как правильный и присудить награду. Похоже, должно быть решение получше, чем .to_a. - person Levi Rosol; 11.09.2012
comment
Мой ответ на это был добавлен к исходному вопросу. - person Levi Rosol; 11.09.2012
comment
Я мог бы очень хорошо написать results = MoListing.any_of(listing_query_params, everything_else_query_params), но вам также нужно было упорядочить результаты, поэтому я не совсем уверен, как их упорядочить в том же запросе! - person Suren; 13.09.2012

попробуй это:

my_listings = MoListing.where(criteria_a)
everything_else = MoListing.where(criteria_b)
all_listings = MoListing.or(my_listings.selector).or(everything_else.selector).page(1).per(25)
person absar mushtaq    schedule 15.01.2015