ORM sind wunderbar, bis sie lecken . Alle tun es schließlich. Ecto ist jung (d.h. es hat nur die Fähigkeit zum OR
erlangt where-Klauseln zusammen vor 30 Tagen
), also ist es einfach nicht ausgereift genug, um eine API zu entwickeln, die fortgeschrittene SQL-Drehungen berücksichtigt.
Bei der Betrachtung möglicher Optionen sind Sie mit der Anfrage nicht allein. Die Unfähigkeit, Listen in Fragmenten zu verstehen (ob als Teil von order_by
oder where
oder woanders) wurde in Ecto issue #1485
erwähnt , auf StackOverflow
, im Elixir-Forum
und dieses Blogbeitrag
. Letzteres ist besonders lehrreich. Mehr dazu gleich. Versuchen wir zunächst einige Experimente.
Experiment 1: Man könnte zuerst versuchen, Kernel.apply/3
zu verwenden um die Liste an fragment
zu übergeben , aber das wird nicht funktionieren:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Experiment 2: Dann können wir es vielleicht mit String-Manipulation bauen. Wie wäre es mit fragment
eine zur Laufzeit erstellte Zeichenfolge mit genügend Platzhaltern, um sie aus der Liste zu ziehen:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Was würde FIELD(id,?,?,?)
erzeugen gegeben ids = [1, 2, 3]
. Nein, das funktioniert auch nicht.
Experiment Nr. 3: Erstellen des gesamten endgültigen SQL, das aus den IDs erstellt wird, und Platzieren der Roh-ID-Werte direkt in der zusammengesetzten Zeichenfolge. Abgesehen davon, dass es schrecklich ist, funktioniert es auch nicht:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Experiment Nr. 4: Das bringt mich zu dem von mir erwähnten Blogbeitrag. Darin umgeht der Autor das Fehlen von or_where
Verwenden einer Reihe vordefinierter Makros basierend auf der Anzahl der Bedingungen, die zusammengeführt werden sollen:
defp orderby_fragment(query, [v1]) do
from u in query, order_by: fragment("FIELD(id,?)", ^v1)
end
defp orderby_fragment(query, [v1,v2]) do
from u in query, order_by: fragment("FIELD(id,?,?)", ^v1, ^v2)
end
defp orderby_fragment(query, [v1,v2,v3]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3)
end
defp orderby_fragment(query, [v1,v2,v3,v4]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3, ^v4)
end
Das funktioniert zwar und nutzt das ORM sozusagen „mit dem Korn“, erfordert aber eine endliche, überschaubare Anzahl an verfügbaren Feldern. Dies kann ein Game Changer sein oder auch nicht.
Meine Empfehlung:Versuchen Sie nicht, die Lecks eines ORMs zu umgehen. Sie kennen die beste Abfrage. Wenn das ORM es nicht akzeptiert, schreiben Sie es direkt mit rohem SQL und dokumentieren Sie, warum das ORM nicht funktioniert. Schirmen Sie es hinter einer Funktion oder einem Modul ab, damit Sie sich das zukünftige Recht vorbehalten können, seine Implementierung zu ändern. Eines Tages, wenn das ORM aufholt, können Sie es einfach umschreiben, ohne Auswirkungen auf den Rest des Systems.