You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ROM-SQL/Sequel单轮查询多列多值指定记录?

问题背景

现有用户表数据如下(无id字段,first_name+last_name为复合主键):

first_namelast_name
JohnDoe
WillSmith
JohnSmith

需求:一次数据库往返内检索John Doe和Will Smith,同时排除John Smith。

单条记录的查询方式

  • 若[first_name, last_name]为复合主键,可使用ROM-SQL的by_pk方法:
    users.by_pk('John', 'Doe')
    
  • 通用where方法(多数数据库适配器支持):
    users.where(first_name: 'John', last_name: 'Doe')
    

多记录查询的坑

直接用where传入数组会生成“或”逻辑的笛卡尔组合,误查John Smith:

users.where(first_name: ['John', 'Will'], last_name: ['Doe', 'Smith'])

而循环调用单条查询会导致多次数据库往返,不符合性能要求。

已尝试的Sequel实现

初始版本:

def find_all_by_pk(array_of_first_names_and_last_names:) # [['John', 'Doe'], ['Will', 'Smith']]
  dataset = users.dataset
  array_of_first_names_and_last_names.each.with_index do |first_name_and_last_name, index|
    first_name, last_name = first_name_and_last_name
    index += 1
    dataset = if index == 1
                dataset.where(first_name: first_name, last_name: last_name)
              else
                dataset.or(first_name: first_name, last_name: last_name)
              end
  end
  dataset.to_a
end

优化后版本:

def find_all_by_pk(array_of_first_names_and_last_names:) # [['John', 'Doe'], ['Will', 'Smith']]
  head, *tail = array_of_first_names_and_last_names
  initial_query = users.dataset.where(first_name: head[0], last_name: head[1])
  tail.reduce(initial_query) do |query, first_name_and_last_name|
    first_name, last_name = first_name_and_last_name
    query.or(first_name: first_name, last_name: last_name)
  end.to_a
end

请问有没有更简洁、符合Ruby惯用写法的实现方式?


解决方案

方式1:利用Sequel对复合IN条件的支持

这是最简洁的写法,Sequel直接支持将复合主键数组传入where的in条件:

def find_all_by_pk(array_of_pairs:)
  users.dataset.where(Sequel.pg_array([:first_name, :last_name]) => array_of_pairs).to_a
end
# 调用示例:find_all_by_pk(array_of_pairs: [['John', 'Doe'], ['Will', 'Smith']])

如果是PostgreSQL以外的数据库,可使用通用复合IN写法:

def find_all_by_pk(array_of_pairs:)
  users.dataset.where(Sequel.lit('(first_name, last_name) IN ?', array_of_pairs)).to_a
end

方式2:简化Reduce写法

用dataset.none作为初始值(避免空数组边界问题),结合解构赋值简化逻辑:

def find_all_by_pk(array_of_pairs:)
  array_of_pairs.reduce(users.dataset.none) do |ds, (first, last)|
    ds.or(first_name: first, last_name: last)
  end.to_a
end

方式3:条件数组拼接

先将每个主键对转为独立条件,再用inject(:or)拼接:

def find_all_by_pk(array_of_pairs:)
  conditions = array_of_pairs.map { |first, last| { first_name: first, last_name: last } }
  users.dataset.where(conditions.inject(:or)).to_a
end

内容的提问来源于stack exchange,提问作者Kenneth Teh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 13:24:21