为什么这个双重否定查询与正面查询不同?

问题描述 投票:2回答:1

当我做两个应该选择同一组人的查询时,它们会有不同的结果。逻辑是一样的,但数字是不同的。

这是我的模型:具有年龄的人类:整数名称:字符串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

这两个语句不应该返回相同的数字吗?

sql ruby-on-rails postgresql
1个回答
6
投票

在SQL中,null与任何东西都不相等(=) - 甚至不是另一个null。而且,这是Three-valued logic的一个常见例子。当你将null与其他任何东西进行比较时,它既不是真也不是假;这是UNKNOWN

基本的“问题”是(正如更深入讨论herewhere.not(x: true)WHERE x != true;它不是WHERE x == true的负面形式。

让我们将您的问题分解为最小,完整,可验证的示例:

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')包括除了带狗的老人和没有宠物的老人以外的所有人。

© www.soinside.com 2019 - 2024. All rights reserved.