如何从不在 table 中的列表中获取 ID?
How to get id's from a list that are NOT in a table?
我收到了一份 ID 列表。其中大部分已经存在于 table 中。我需要找出哪些 ID 不在 table 中。此问题与连接无关。
我的API会收到一个ID列表,例如:[1,2,3,4,5]
假设 table 中有三个记录:[2, 3, 4]
我要找的结果是数组:[1, 5]
我们的 SQL 大脑会迅速跳转到如下内容,但显然这不是我们需要的:
select * from widgets where id not in [list]
我们不需要不在列表中的记录,我们需要列表中不在记录中的部分!
我的后备方法是检索列表中的所有记录并从列表中减去,如下所示:
existing_ids = Widget.where(id: id_list).pluck(:id)
new_ids = id_list - existing_ids
这会奏效...但感觉太笨重了。特别是如果 id_list 有 100,000 条记录,而 table 有 99,999 条记录。
我四处搜索,唯一相似的结果是 ...没有找到可行的解决方案。
有没有办法在单个 SQL 查询中完成此操作? (ActiveRecord 解决方案加分!)
要将列表相互比较,输入列表需要进入数据库,或者现有 ID 列表需要从数据库中取出。后者你已经尝试过但不喜欢,所以这里有一个替代方案
SELECT "id" FROM unnest('{1,2,3,4,5}'::integer[]) AS "id" WHERE "id" NOT IN (SELECT "id" FROM "widgets");
不确定性能。
根据您的数据库中有多少条记录,最简单的方法可能就是 select 所有 ID,然后将重复项删除 Ruby。
from_api = [1,2,3,4,5]
existing = Widgets.pluck(:id) # => [2,3,4]
from_api.difference(existing) # => [1,5]
显然,如果您有大量数据集,这将不是最佳选择。
这应该有效。
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
new_ids = []
from_api.each{ |n| new_ids << n unless existing.include? n }
new_ids # => [1,5]
或
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
from_api.map{ |n| n == existing.first ? (nil if existing = existing.drop(1)) : n }.compac # => [1,5]
平衡 unset
方法的复杂性(对当前和未来的开发人员而言),我决定为我的项目采用更简单的方法。虽然我没有描述性能,但我相信任何收益都是微乎其微的。
这是我最终得到的解决方案:
class Widget < ApplicationRecord
def self.absent(names)
uniq_names = names.uniq
uniq_names - where(name: uniq_names).pluck(:name)
end
end
并测试:
describe '.absent' do
subject { described_class.absent(names) }
let!(:widget1) { create(:widget, name: 'old-1') }
let!(:widget2) { create(:widget, name: 'old-2') }
let(:names) { %w[new-2 old-2 new-1 old-1 new-1 old-1] }
it { is_expected.to eq %w[new-2 new-1] }
end
我收到了一份 ID 列表。其中大部分已经存在于 table 中。我需要找出哪些 ID 不在 table 中。此问题与连接无关。
我的API会收到一个ID列表,例如:[1,2,3,4,5]
假设 table 中有三个记录:[2, 3, 4]
我要找的结果是数组:[1, 5]
我们的 SQL 大脑会迅速跳转到如下内容,但显然这不是我们需要的:
select * from widgets where id not in [list]
我们不需要不在列表中的记录,我们需要列表中不在记录中的部分!
我的后备方法是检索列表中的所有记录并从列表中减去,如下所示:
existing_ids = Widget.where(id: id_list).pluck(:id)
new_ids = id_list - existing_ids
这会奏效...但感觉太笨重了。特别是如果 id_list 有 100,000 条记录,而 table 有 99,999 条记录。
我四处搜索,唯一相似的结果是
有没有办法在单个 SQL 查询中完成此操作? (ActiveRecord 解决方案加分!)
要将列表相互比较,输入列表需要进入数据库,或者现有 ID 列表需要从数据库中取出。后者你已经尝试过但不喜欢,所以这里有一个替代方案
SELECT "id" FROM unnest('{1,2,3,4,5}'::integer[]) AS "id" WHERE "id" NOT IN (SELECT "id" FROM "widgets");
不确定性能。
根据您的数据库中有多少条记录,最简单的方法可能就是 select 所有 ID,然后将重复项删除 Ruby。
from_api = [1,2,3,4,5]
existing = Widgets.pluck(:id) # => [2,3,4]
from_api.difference(existing) # => [1,5]
显然,如果您有大量数据集,这将不是最佳选择。
这应该有效。
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
new_ids = []
from_api.each{ |n| new_ids << n unless existing.include? n }
new_ids # => [1,5]
或
from_api = [1,2,3,4,5]
existing = Widgets.order(:id).ids # => [2,3,4]
from_api.map{ |n| n == existing.first ? (nil if existing = existing.drop(1)) : n }.compac # => [1,5]
平衡 unset
方法的复杂性(对当前和未来的开发人员而言),我决定为我的项目采用更简单的方法。虽然我没有描述性能,但我相信任何收益都是微乎其微的。
这是我最终得到的解决方案:
class Widget < ApplicationRecord
def self.absent(names)
uniq_names = names.uniq
uniq_names - where(name: uniq_names).pluck(:name)
end
end
并测试:
describe '.absent' do
subject { described_class.absent(names) }
let!(:widget1) { create(:widget, name: 'old-1') }
let!(:widget2) { create(:widget, name: 'old-2') }
let(:names) { %w[new-2 old-2 new-1 old-1 new-1 old-1] }
it { is_expected.to eq %w[new-2 new-1] }
end