【问题标题】:How to get id's from a list that are NOT in a table?如何从不在表中的列表中获取 id?
【发布时间】:2020-09-28 18:59:00
【问题描述】:

我收到一份 ID 列表。其中大部分已经存在于表中。我需要找出哪些 ID 不在表中。这个问题与join无关。

我的 API 会收到一个 ID 列表,例如:[1, 2, 3, 4, 5]

假设表中有3条记录:[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 条记录,而表中有 99,999 条记录。

我四处搜索,唯一类似的结果是ID from list that is not in a table ... 没有找到可行的解决方案。

有没有办法在单个 SQL 查询中做到这一点? (ActiveRecord 解决方案的奖励积分!)

【问题讨论】:

  • 从逻辑上讲,您必须要么将 id 发送到 db,要么从 db 中检索所有 id 的列表。您的解决方案是前者,我也倾向于前者(除非 I'd_list 非常大)。唯一可能探索的其他途径是布隆过滤器之类的东西,但要等到更简单的解决方案出现实际问题。

标签: sql ruby-on-rails activerecord


【解决方案1】:

为了将列表相互比较,输入列表需要进入数据库,或者现有 id 的列表需要从数据库中出来。后者你已经尝试过但不喜欢,所以这里有一个替代方案

SELECT "id" FROM unnest('{1,2,3,4,5}'::integer[]) AS "id" WHERE "id" NOT IN (SELECT "id" FROM "widgets");

不确定性能。

【讨论】:

  • 可以使用EXISTS 代替IN 来提高性能吗?
  • 可能,但不一定。无论如何,Postgres 可能足够聪明,可以制定相同的查询计划。试试看。
【解决方案2】:

根据您的数据库中有多少记录,最简单的方法可能就是选择所有 ID,然后在 Ruby 中删除重复项。

from_api = [1,2,3,4,5]
existing = Widgets.pluck(:id) # => [2,3,4]

from_api.difference(existing) # => [1,5]

显然,如果您有大量数据集,这将不是最佳的。

【讨论】:

    【解决方案3】:

    这应该可行。

    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]
    

    【讨论】:

      【解决方案4】:

      平衡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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-03
        • 2018-10-16
        • 2021-09-14
        • 1970-01-01
        • 1970-01-01
        • 2019-12-31
        • 2015-05-10
        相关资源
        最近更新 更多