sql >> Databasteknik >  >> RDS >> Mysql

Ecto-fråga och anpassad MySQL-funktion med variabel aritet

ORM är underbara, tills de läcker . Alla gör det till slut. Ecto är ung (t.ex. den fick bara förmågan att OR där klausuler tillsammans 30 dagar sedan ), så det är helt enkelt inte tillräckligt mogen för att ha utvecklat ett API som tar hänsyn till avancerade SQL-gyrationer.

När du undersöker möjliga alternativ är du inte ensam om begäran. Oförmågan att förstå listor i fragment (oavsett om det är en del av order_by eller where eller någon annanstans) har nämnts i Ecto-nummer #1485 , på StackOverflow , på Elixir Forum och detta blogginlägg . Det senare är särskilt lärorikt. Mer om det om ett tag. Låt oss först testa några experiment.

Experiment #1: Man kan först försöka använda Kernel.apply/3 för att skicka listan till fragment , men det kommer inte att fungera:

|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))

Experiment #2: Då kanske vi kan bygga den med strängmanipulation. Vad sägs om att ge fragment en sträng byggd vid körning med tillräckligt många platshållare för att den ska kunna hämtas från listan:

|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))

Vilket skulle producera FIELD(id,?,?,?) givna ids = [1, 2, 3] . Nej, det här fungerar inte heller.

Experiment #3: Skapar hela den slutliga SQL som är byggd från ID:n och placerar de råa ID-värdena direkt i den sammansatta strängen. Förutom att det är hemskt så fungerar det inte heller:

|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))

Experiment #4: Detta leder mig till det där blogginlägget jag nämnde. I den hackar författaren avsaknaden av or_where använder en uppsättning fördefinierade makron baserat på antalet villkor som ska dras samman:

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

Även om detta fungerar och använder ORM "med kärnan" så att säga, kräver det att du har ett begränsat, hanterbart antal tillgängliga fält. Detta kan vara en spelförändring eller inte.

Min rekommendation:försök inte att jonglera runt en ORM:s läckor. Du vet den bästa frågan. Om ORM inte accepterar det, skriv det direkt med rå SQL och dokumentera varför ORM inte fungerar. Skydda den bakom en funktion eller modul så att du kan förbehålla dig framtida rätten att ändra dess implementering. En dag, när ORM kommer ikapp, kan du sedan bara skriva om det snyggt utan några effekter på resten av systemet.



  1. Räkna besök idag, denna vecka, förra månaden och totalt [MySQL-fråga]

  2. MySQL Workbench mycket snabbare än Python för samma fråga

  3. Hur ändrar jag alla tabeller i min databas till UTF8-teckenuppsättning?

  4. PHP:Dynamisk rullgardinsmeny med optgroup