Почему этот двойной отрицательный запрос отличается от положительного? - PullRequest
2 голосов
/ 29 марта 2019

Когда я делаю два запроса, которые должны выбрать одну и ту же группу людей, у них разные результаты.Логика та же, но цифры разные.

Вот моя модель: класс Person с возрастом: целое имя: string pet: string

class Person < ApplicationRecord
  PET_TYPES = ['dog', 'cat', 'bird', 'fish']
  validates :pet, inclusion: { in: PET_TYPES }, allow_nil: true
  validates :age, numericality: { greater_than: 0 }
end

Я заполнил базу данныхс людьми, имеющими каждого типа домашних животных (включая ноль) в возрасте до 21 года или старше 21 года в моем файле начальных чисел:

Person.all.destroy_all
Person::PET_TYPES.each do |pet|
    10.times do |n|
        Person.create!(name: "Person-young-#{pet}-#{n}", pet: pet, age: (1..20).to_a.sample)
    end
    10.times do |n|
        Person.create!(name: "Person-old-#{pet}-#{n}", pet: pet, age: (21..80).to_a.sample)
    end
end
10.times do |n|
    Person.create!(name: "Person-young-no-pet-#{n}", pet: nil, age: (1..20).to_a.sample)
end
10.times do |n|
    Person.create!(name: "Person-old-no-pet-#{n}", pet: nil, age: (21..80).to_a.sample)
end

Я выполнил следующие два запроса, которые должны были выбрать одну и ту же группу людей.Но я получаю разные числа.

Person.count
#>>   (1.0ms) SELECT COUNT(*) FROM "people"
#=> 100
Person.where.not(id: Person.where.not('age > ? AND pet = ?', 21, 'dog')).count
#>>   (1.7ms) SELECT COUNT(*) FROM "people" WHERE "people"."id" NOT IN (SELECT "people"."id" FROM "people" WHERE NOT (age > 21 AND pet = 'dog'))
#=> 20
Person.where('age > ? AND pet = ?', 21, 'dog').count
#>>   (1.0ms) SELECT COUNT(*) FROM "people" WHERE (age > 21 AND pet = 'dog')
#=> 10

Разве эти два утверждения не должны возвращать одно и то же число?

1 Ответ

6 голосов
/ 29 марта 2019

В SQL null не равно (=) чему-либо - даже другому null.Более того, это общий пример Трехзначная логика .Когда вы сравниваете null с чем-либо еще, это не является ни истинным, ни ложным ;это UNKNOWN.

Фундаментальная «проблема» заключается в том (как обсуждено более подробно здесь ), что where.not(x: true) есть WHERE x != true;это не отрицательная форма WHERE x == true.

Давайте разберем вашу проблему до Minimal , Complete, Verifiable Пример:

Person.count
   (1.2ms)  SELECT COUNT(*) FROM "people"
=> 100
Person.where(pet: 'dog').count
   (1.0ms)  SELECT COUNT(*) FROM "people" WHERE "people"."pet" = 'dog'
=> 20
Person.where.not(pet: 'dog').count
   (1.4ms)  SELECT COUNT(*) FROM "people" WHERE "people"."pet" != 'dog'
=> 60
Person.where.not(pet: 'dog').pluck(:pet).uniq
=> ["cat", "bird", "fish"]

Как вы можете видеть, люди с null питомцами отсутствуют в обоих результатах.


Итак, подведем итог: ваша попытка двойного отрицания запроса фактически приводит к другому набору результатов из-зак трехзначной логике.

Person.where.not('age > ? AND pet = ?', 21, 'dog') включает всех, кроме стариков с собакой и стариков без домашних животных .

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