Convert your raw sql to Ecto query
TLDR;
We're using with_cte
, combined with matching the Schema when using select
:
"img"
|> with_cte("img", as: fragment("select * from images TABLESAMPLE system_rows(1)"))
|> from(as: ^:img)
|> select([img: i],
%Images{
title: i.title,
description: i.description,
field :slug,
field :tags,
}
)
|> limit(1)
|> Repo.all
Non-TLDR
Before we get to the point, allow me to start at this part. I was contented with the randomized paginated results (using RANDOM()
) that looked like this:
def paginate_product(status_name, params \\ [page: 1, page_size: 10]) do
Product
|> where(status_name: ^status_name)
|> order_by(fragment("RANDOM()"))
|> preload(:user)
|> Repo.paginate(params)
end
TABLESAMPLE
clause
Actually what I do need is:
- It (query) should be fast even on large records.
- Not required to be purely random.
- Easily pipe it in
Repo.paginate
(akascrivener_ecto
), as it needed an Ecto query not a list of schemas.
I'm not sure if my current implementation is enough, until I read this article:
The
system_rows
version of theTABLESAMPLE
function will pick a random disk block in the table, and then fetch rows sequentially from there. Picking a random block can be done by just looking at the size of the table so this is very very fast. Here's the sql for the alternative version ofRANDOM()
:
# randomly picks a starting point, then
# fetches 10 rows in order.
select * from images TABLESAMPLE system_rows(10);
Attempt #1: Repo.query!/1
I've stumbled on this answer which is great, given that it returns a list of structs. But scrivener_ecto
's paginate
function accepts an Ecto query. But what's an Ecto query?
something like:
iex()> Images |> limit(1)
#Ecto.Query<from i0 in Nappy.Catalog.Images, limit: 1>
But using Repo.query!/1
returns %Postgrex.Result{...}
struct :(
Attempt #2: with_cte/3
function and forming the struct
Using with_cte/3
, I can finally use the raw sql and expecting a query, happily passing it to paginate
:
"img"
|> with_cte("img", as: fragment("select * from images TABLESAMPLE system_rows(1)"))
|> from(as: ^:img)
|> select([img: i],
%Images{
title: i.title,
description: i.description,
field :slug,
field :tags,
}
)
|> limit(1)
|> Repo.all
Shortcomings
Here's the raw sql source (at the very bottom of the blogpost) on using with
clause. But there's a problem which I'm still solving: How can I preload and use where clause for this? I'll write another post for this once I find it.