如何用ROM-SQL/Sequel单轮查询多列多值指定记录?
问题背景
现有用户表数据如下(无id字段,first_name+last_name为复合主键):
| first_name | last_name |
|---|---|
| John | Doe |
| Will | Smith |
| John | Smith |
需求:一次数据库往返内检索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
相关产品推荐
相关产品推荐

