Given a database table with these three rows:
first_name | last_name |
---|---|
John | Doe |
Will | Smith |
John | Smith |
Note: I specifically left out the id
here because we might be dealing with a case where either we don't have the id
or maybe [first_name, last_name]
is the composite primary key.
How do I retrieve John Doe and Will Smith without also retrieving John Smith in one database round-trip?
We can do it for one record:
- We can use the
rom-sql
by_pk
method to retrieve one record by composite primary keys (if this happens to be a composite primary key).
users.by_pk('John', 'Doe')
- We can use
where
(from pretty much any database adapter) to retrieve the record
users.where(first_name: 'John', last_name: 'Doe')`
But how do we do the same for multiple records?
(1) doesn't work (or at least I don't know the syntax). Please enlighten me if you do!
The trivial way is to just run a map in Ruby and retrieve it:
array_of_first_names_and_last_names.map do |first_name_and_last_name|
first_name, last_name = first_name_and_last_name
users.by_pk(first_name, last_name)
end
We can do the same trivial map in Ruby for (2).
But how can we do it in one database round-trip?
(2) does not work because I will accidentally retrieve John Smith as well if I just do this:
users.where(first_name: ['John', 'Will'], last_name: ['Doe', 'Smith'])
Describe what you’ve tried
I've tried to do this in Sequel (and you can do it in SQL as well if you wanted):
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
UPDATE: A few months later (17th Nov 2022), I stumbled upon my own question while trying to solve a similar problem. All I managed to do was make the Ruby part of it a bit cleaner:
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
Is there a better/cleaner/more idiomatic way to do this?