sql >> Base de Datos >  >> RDS >> Mysql

Consulta ecto y función MySQL personalizada con aridad variable

Los ORM son maravillosos, hasta que filtran . Todos lo hacen, eventualmente. Ecto es joven (es decir, solo obtuvo la capacidad de OR where cláusulas juntas hace 30 días ), por lo que simplemente no es lo suficientemente maduro como para haber desarrollado una API que considere giros de SQL avanzados.

Estudiando las posibles opciones, no estás solo en la petición. La incapacidad de comprender listas en fragmentos (ya sea como parte de order_by o where o en cualquier otro lugar) ha sido mencionado en Número de Ecto #1485 , en StackOverflow , en el Elixir Forum y esto entrada de blog . El último es particularmente instructivo. Más sobre esto en un momento. Primero, hagamos algunos experimentos.

Experimento n.º 1: Primero se podría intentar usar Kernel.apply/3 para pasar la lista a fragment , pero eso no funcionará:

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

Experimento n.º 2: Entonces quizás podamos construirlo con la manipulación de cadenas. ¿Qué tal dar fragment una cadena creada en tiempo de ejecución con suficientes marcadores de posición para extraerla de la lista:

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

Que produciría FIELD(id,?,?,?) dado ids = [1, 2, 3] . No, esto tampoco funciona.

Experimento n.º 3: Crear el SQL final completo creado a partir de los ID, colocando los valores de ID sin procesar directamente en la cadena compuesta. Además de ser horrible, tampoco funciona:

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

Experimento n.º 4: Esto me lleva a la publicación de blog que mencioné. En él, el autor soluciona la falta de or_where usando un conjunto de macros predefinidas basadas en la cantidad de condiciones para reunir:

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

Si bien esto funciona y usa el ORM "con el grano", por así decirlo, requiere que tenga un número finito y manejable de campos disponibles. Esto puede o no ser un cambio de juego.

Mi recomendación:no intentes hacer malabarismos con las fugas de un ORM. Conoces la mejor consulta. Si el ORM no lo acepta, escríbalo directamente con SQL sin procesar y documente por qué el ORM no funciona. Protéjalo detrás de una función o módulo para que pueda reservar el derecho futuro de cambiar su implementación. Un día, cuando el ORM se ponga al día, puede volver a escribirlo bien sin efectos en el resto del sistema.