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:

  1. 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')
  1. 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?

0

There are 0 best solutions below