Rails - Оптимизация запросов - PullRequest
0 голосов
/ 03 июля 2018

Я пытаюсь выполнить простую операцию, но сталкиваюсь с ненужными запросами. Я хотел знать, как оптимизировать. В моем фрагменте кода ниже строка 2 получает всех пользователей. В параметрах «numbers» также есть несколько чисел, которых нет в моей системе. Поэтому мне нужно сделать операцию, чтобы найти как существует, так и не существует. Но Line2 и Line4 делают несколько запросов. Есть ли способы оптимизировать его?

  def sync_fit!(numbers, current_user)
    contacts = User.where(number: numbers)
    numbers.each do |number|
      user = contacts.find_by(number: number)
      if user.present?
        # Another operation
      end
    end
  end

РЕДАКТИРОВАТЬ 1:

Имя таблицы: пользователи

    #  id                     :bigint(8)        not null, primary key
    #  country                :string(255)      not null
    #  number                 :string(255)      not null
    #  first_name             :string(255)      default("")
    #  last_name              :string(255)      default("")
    #  username               :string(255)
    #  email                  :string(255)

Нет никакого отношения. Используется только модель пользователя.

Ответы [ 7 ]

0 голосов
/ 03 июля 2018

find_by - это метод запроса, таким образом, запрос запускается в строке 4. Вы можете достичь того же с помощью Enumerable#find, так как данные уже загружены и никакие запросы не будут выполняться в цикле

def sync_fit!(numbers, current_user)
  contacts = User.where(number: numbers)
  numbers.each do |number|
    user = contacts.find { |c| c.number == number } 
    if user.present?
      # Another operation
    end
  end
end

Однако в обоих случаях, если User делит число с другим User, будет возвращено только первое. Дополнительно вы можете изменить цикл на

def sync_fit!(numbers, current_user)
  numbers = numbers.dup 
  User.where(number: numbers).each do |contact|
    number = numbers.delete(contact.number)
    # Another operation
  end
end

В этом случае мы зациклим все найденные Users и удалим найденные числа из локальной переменной numbers. По завершении цикла все числа, которые не были связаны с пользователем, останутся в numbers

0 голосов
/ 05 июля 2018
def sync_fit!(numbers, current_user)
  contacts = User.where(number: numbers).to_a
  numbers.each do |number|
    user = contacts.find {|contact| contact.number == number}
    if user.present?
      # Another operation
    end
  end
end

В строке 2 вы получаете всех пользователей, но то, что вы фактически получаете и сохраняете в переменной contacts, является отношением ActiveRecord. Если вы хотите выполнить запрос в строке 2 и больше не делать запросов, преобразуйте отношение ActiveRecord в массив и выполните операции с массивом над результатом.

0 голосов
/ 03 июля 2018

В качестве альтернативы другим ответам может оказаться полезным метод Ruby Array#select (его не следует путать с методом Active Record с тем же именем). Он принимает блок, который должен отображать элементы массива в логические значения, и фильтрует массив для тех, которые возвращают true. Э.Г.

[0,1,2,3].select {|x| x%2 == 0 }
# [0,2]

Таким образом, ваш код может потребовать минимального изменения:

  def sync_fit!(numbers, current_user)
    contacts = User.where(number: numbers)
    numbers.each do |number|
      users = contacts.select{|contact| contact.number == number }
      if user.present?
        # Another operation
      end
    end
  end

Это «на уровне рубина». Однако тот факт, что контакты являются отношением ActiveRecord, может вызвать дополнительные запросы. Поскольку у вас больше нет отношений для запроса, вы можете предотвратить это, убедившись, что контакты полностью загружены как объекты ruby, прежде чем продолжить:

contacts = User.where(number: numbers).to_a

должен сделать трюк. Обратите внимание, что этот подход, вероятно, наиболее близок к существующему коду, который будет выполнять то, что вы хотите. Однако, в зависимости от того, что является «другой операцией», вы можете или не можете найти один из других ответов более эффективным.

0 голосов
/ 03 июля 2018

Следующий код выполняет только один запрос и работает дальше:

def sync_fit!(numbers, current_user)
  contacts = User.where(number: numbers)
  unfound_numbers = numbers - contacts.map(&:number)

  contacts.each do |user|
    # code for found users
  end

  unfound_numbers.each do |number|
    # code for numbers not found in users table
  end
end

Примечание:

Это, однако, предполагает, что ваш numbers ввод соответствует типу user.number ввода. Предполагая, что user.number является целым числом, оно будет зацикливаться на всех предоставленных числах, даже если они найдены, если задан список номеров строк, например. %w[1 2 3 4]. Если вам нужна дополнительная безопасность, убедитесь, что результат number и предоставленные числа одинакового типа. numbers.map(&:to_i)

Вы также обычно используете contacts.pluck(:number), чтобы получить список чисел. Но так как вы все равно будете использовать экземпляры контактов, я выбрал map, потому что это загружает экземпляры и работает оттуда. В результате на один запрос меньше к базе данных.

0 голосов
/ 03 июля 2018

Разве я не могу получить все и сделать фильтр на стороне ruby, а не на стороне db?

Да, вы можете использовать Enumerable#group_by:

def sync_fit!(numbers, current_user)
  contacts = User.where(number: numbers).group_by(&:number)
  numbers.each do |number|
    # contacts is {number1=>[user1, ...], number2=>[...]}
    user = contacts[number].first # or other logic when many users with same number
    if user
      # Operation with `user`
    end
    # Operation with `number`
  end
end

Примечание: если вам не нужно обрабатывать точно каждое число (# Операция с номером), вы можете упростить свой код до:

def sync_fit!(numbers, current_user)
  contacts = User.where(number: numbers)
  contacts.each do |user|
    number = user.number
    # Operation with `user`
  end
end

UPD: Исправлен #group_by пример с комментарием @ johan-wentholt.

0 голосов
/ 03 июля 2018

Вместо find_by используйте метод find.

user = contacts.find(number: number)

find обрабатывается на уровне Ruby. find_by отправит запрос в БД

0 голосов
/ 03 июля 2018

Вы можете просто пренебречь строкой № 2, т. Е.

contacts = User.where(number: numbers)

И на линии № 4 сделайте это

user = User.find_by(number: number).

Но имейте в виду, что find_by вернет вам только один результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...