Подтвердить что ты не робот

Проблема при извлечении записей с пустым массивом

У меня есть таблица около 100 пользователей, и у меня также есть массив идентификаторов пользователей. Я хотел бы показать всех пользователей, которые не являются частью этого массива идентификаторов пользователей. Когда я делаю что-то вроде этого

 User.where('id NOT IN (?)', [9, 2, 3, 4])

Он успешно возвращает записи, в которых идентификатор пользователя не принадлежит этому массиву. Однако, если этот массив пуст, так

 User.where('id NOT IN (?)', [])

Он не возвращает пользователей, и запрос SQL выглядит так:

 SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))

Кто-нибудь знает, почему это происходит, или это может быть ошибкой? Я использую Rails 3.2.5 с PostgreSQL.

4b9b3361

Ответ 1

ActiveRecord (по крайней мере, 3.2.1) обрабатывает пустые массивы как NULL. Заполнители в вызове where обрабатываются sanitize_sql. Если вы немного проигнорируете код, вы перейдете к replace_bind_variables:

def replace_bind_variables(statement, values) #:nodoc:
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
  bound = values.dup
  c = connection
  statement.gsub('?') { quote_bound_value(bound.shift, c) }
end

а затем quote_bound_value:

def quote_bound_value(value, c = connection) #:nodoc:
  if value.respond_to?(:map) && !value.acts_like?(:string)
    if value.respond_to?(:empty?) && value.empty?
      c.quote(nil)
    else
      value.map { |v| c.quote(v) }.join(',')
    end
  else
    c.quote(value)
  end
end

Пустой массив будет удовлетворять всем четырем условиям, чтобы довести вас до c.quote(nil) и от того, откуда приходит ваш NULL. Вся специальная логика, которая ведет к c.quote(nil), указывает, что это преднамеренное поведение.

Вывод IN (или NOT IN) с пустым списком:

where c in ()

должен вызывать ошибку SQL, поэтому, возможно, люди AR пытаются предотвратить это, спокойно превратив этот плохой SQL в c in (null). Обратите внимание, что ни одно из них:

select ... from t where c in (null);
select ... from t where c not in (null);

должен давать какие-либо результаты из-за поведения SQL NULL. Это классическая ошибка новичка, и люди AR действительно должны знать лучше.

Я бы предпочел исключение сам: сказать мне, что я собираюсь развернуть фул-пулю, было бы намного дружелюбно, чем просто передать мне другое оружие.


Исполнительное резюме:

  • Это поведение "пустой массив означает, что NULL" преднамеренно.
  • Вы никогда не должны пытаться использовать where('c in (?)', []) или where('c not in (?)', []), поскольку ни один из них не имеет большого смысла.
  • Обновите свой Ruby-код, чтобы проверить наличие пустых массивов и сделайте все, что нужно сделать, чтобы получить ожидаемые результаты.

Ответ 2

В Rails 4 вы можете использовать User.where.not(id: []), который даст вам правильный результат. Он производит:

SELECT "users".* FROM "users" WHERE (1 = 1)

К сожалению, User.where('id NOT IN (?)', []) должен быть эквивалентным, но это не так. Он по-прежнему дает неверный результат:

SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))

Литература:

Ответ 3

User.where('id NOT IN (?)', ids+[0])

Ответ 4

Использовать оболочку активной записи ruby:

User.where.not(id: [])

Эта проблема устраняет проблему с пустым массивом.

Ответ 5

Я не знаю, спросила ли эта проблема, но я пришел сюда, чтобы найти все записи с пустым (сериализованным) атрибутом массива. Я решил это для Rails 5.0 следующим образом:

User.where(my_array_attribute: nil)

Или для обратного:

User.where.not(my_array_attribute: nil)