2017-07-09 3 views
2

J'ai dans mon tableau "influencer_platforms" DB qui se compose de:Comment grouper des enregistrements dans Ecto et les renvoyer sous forme de carte?

id | influencer_handle | name 

Je veux les interroger et le groupe par nom comme:

%{ 
    name1: [%{id: 1, influencer_handle: "i1"}, %{id: 3, influencer_handle: "i2"}], 
    name2: [%{id: 2, influencer_handle: "i3"}] 
} 

Comment les faire dans Ecto? Jusqu'à présent j'ai:

defmacrop influencer_platform_json(influencer) do 
    quote do 
    fragment(
     "jsonb_agg(?)", 
     fragment(
     "json_build_object(?, ?, ?, ?)", 
     "id",    unquote(influencer).id, 
     "influencer_handle", unquote(influencer).influencer_handle 
    ) 
    ) 
    end 
end 

def all do 
    from ip in InfluencerPlatform, 
    group_by: :name, 
    select: %{ 
     ip.name => influencer_platform_json(ip) 
    } 
end 

Est-ce une façon plus élégante de l'atteindre?

+1

Peut-être utiliser 'bind_quoted' pour éviter de ne pas avoir plusieurs fois le mot' influencer'? –

+0

@MikeBuhot Je ne sais pas si ça marche dans 'fragment'. – squixy

Répondre

2

Si vous voulez que tout cela soit fait dans la base de données, vous pouvez réduire les deux fragments en un seul. Vous n'avez également pas besoin de passer les noms de colonnes en tant qu'arguments à fragment s'il s'agit de simples constantes; vous pouvez mettre les noms de colonne à l'intérieur de la requête dans fragment.

defmacrop influencer_platform_json(influencer) do 
    quote do 
    fragment("jsonb_agg(jsonb_build_object('id', ?, 'influencer_handle', ?))", unquote(influencer).id, unquote(influencer).influencer_handle) 
    end 
end 

Vous pouvez également récupérer les données nécessaires et faire du groupe par Elixir à l'aide Enum.group_by/2. Cela serait beaucoup plus élégant mais pourrait être moins performant que ce qui précède, en fonction de l'optimisation de la gestion JSON de PostgreSQL pour la requête ci-dessus.

from(ip in InfluencerPlatform, select: map(ip, [:id, :name, :influencer_handle])) 
|> Repo.all 
|> Enum.group_by(fn ip -> ip.name end) 
# Delete `:name` from each map. 
|> Enum.map(fn {k, v} -> 
    {k, Enum.map(v, &Map.delete(&1, :name))} 
end) 
|> Map.new