Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Consulta Ecto e função MySQL personalizada com aridade variável


ORM são maravilhosos, até que eles vazem . Todos o fazem, eventualmente. Ecto é jovem (ou seja, só ganhou habilidade para OR where cláusulas juntas 30 dias atrás ), então simplesmente não é maduro o suficiente para desenvolver uma API que considere giros SQL avançados.

Pesquisando opções possíveis, você não está sozinho na solicitação. A incapacidade de compreender listas em fragmentos (seja como parte de order_by ou where ou em qualquer outro lugar) foi mencionado na Ecto issue #1485 , no StackOverflow , no Fórum Elixir e este postagem do blog . O último é particularmente instrutivo. Mais sobre isso daqui a pouco. Primeiro, vamos tentar alguns experimentos.

Experiência nº 1: Pode-se primeiro tentar usar Kernel.apply/3 para passar a lista para fragment , mas isso não vai funcionar:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))

Experiência nº 2: Então talvez possamos construí-lo com manipulação de strings. Que tal dar fragment uma string construída em tempo de execução com espaços reservados suficientes para que ela seja extraída da lista:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))

Qual produziria FIELD(id,?,?,?) dado ids = [1, 2, 3] . Não, isso também não funciona.

Experiência nº 3: Criando todo o SQL final construído a partir dos ids, colocando os valores brutos de ID diretamente na string composta. Além de ser horrível, também não funciona:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))

Experiência nº 4: Isso me leva a esse post do blog que mencionei. Nele, o autor fala sobre a falta de or_where usando um conjunto de macros predefinidas com base no número de condições 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

Embora isso funcione e use o ORM "com o grão", por assim dizer, ele requer que você tenha um número finito e gerenciável de campos disponíveis. Isso pode ou não ser um divisor de águas.

Minha recomendação:não tente fazer malabarismos com os vazamentos de um ORM. Você sabe a melhor consulta. Se o ORM não aceitar, escreva-o diretamente com SQL bruto e documente por que o ORM não funciona. Proteja-o atrás de uma função ou módulo para que você possa reservar o direito futuro de alterar sua implementação. Um dia, quando o ORM alcançar, você pode simplesmente reescrevê-lo bem, sem efeitos no resto do sistema.