Blog

Ecto, fragments, with ordinality de PostgreSQL

Ecto, fragments, with ordinality de PostgreSQL

Tenía una query con un where in:

DataStructureVersion
|> ...varias transformaciones de la query
|> where(q, [dsv], dsv.id in ^ids)

Sin un order_by el orden no está definido. En mi caso, resultaba ser por defecto por id ascendiente de DataStructureVersion. En lugar de ello, quería conservar el mismo que el del array de ids, porque proviene de una búsqueda de ElasticSearch que ya llega ordenada. Para conseguirlo, se puede utilizar WITH ORDINALITY de PostgreSQL:

 defp enrich_order_by(query, ds_ids, :asc_data_structure_ids_with_ordinality) do
    query
    |> join(:inner, [dsv], ordinality in fragment("SELECT * FROM UNNEST(?::int[]) WITH ORDINALITY as ordinality(id, num)", ^ds_ids), on: dsv.data_structure_id == ordinality.id)
    |> order_by([_dsv, ordinality], asc: ordinality.num)
end

Voy a contar de paso algo más. Sobre la query resultante tenía que hacer un distinct on. Hay que incluir en éste las expresiones que estén más a la izquierda del order by. De lo contrario, saltará este error:

SELECT DISTINCT ON expressions must match initial ORDER BY expressions

DISTINCT ON ( expression [, …] ) keeps only the first row of each set of rows where the given expressions evaluate to equal. […] Note that the «first row» of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first. […] The DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s).

https://stackoverflow.com/a/9795768
http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-DISTINCT

En este caso sólo hay una expresión, el campo ordinality.num. Encadené la query anterior con:

defp distinct_by(query, %{order_by: :asc_data_structure_ids_with_ordinality}) do
  distinct(query, [_dsv, ordinality], ordinality.num)
end