2016-06-30 5 views
1

私は最近、エリクシル(elixir 1.3、phoenix 1.2、ecto 2.0.1およびmariaex 0.7.7を使用)に既存のPHPメッセンジャーアプリを移植し始めました。このアプリは何百万人ものユーザーにサービスを提供し、パフォーマンスが重要です。エリクシール:サブクエリまたはサイドローディングと正規化されたjson出力を表示

Database schema

各スレッドは、複数のthread_participantsとのメッセージを持っている:私は私の質問

私は、次のデータベース・スキーマを持っているの無言症を許してくださいエリクサーに非常に新しいです。 thread_participantは、リンクされたスレッドのコンテキストでユーザーに関する情報を持っています(ユーザーがこのスレッドを最後に見たときなど)。スレッドは、ユーザーが作成した複数のメッセージで構成されています。

私は私のAPIがあることを最後に戻るには、JSONをしたいと思います。ここでは

"data": { 
    "result": [1, 2], 
    "threads": { 
    1: { 
     "id": 1, 
     "unread_count": 2, 
     "starred": false, 
     "muted": false, 
     "last_seen": "2015-10-20T19:01:46", 
     "participants": [1, 2] 
    }, 
    22: { 
     "id": 22, 
     "unread_count": 0, 
     "starred": true, 
     "muted": false, 
     "last_seen": "2016-06-20T12:00:00", 
     "participants": [1, 3] 
    } 
    }, 
    users: { 
    1: { 
     id: 1, 
     name: 'John' 
    }, 
    2: { 
     id: 2, 
     name: 'Dan' 
    }, 
    3: { 
     id: 3, 
     name: 'Eric' 
    }   
    } 

は、スレッドとThreadParticipantのための私のスキーマです:

schema "thread" do 
    field :created, Ecto.DateTime, usec: true, autogenerate: true 
    belongs_to :creator, UserAbstract 
    has_many :messages, ThreadMessage 
    has_many :participants, ThreadParticipant 
    has_many :users, through: [:participants, :user] 
    field :last_seen, Ecto.DateTime, virtual: true, default: :null 
    field :muted, :boolean, virtual: true, default: false 
    field :starred, :boolean, virtual: true, default: false 
    field :unread_count, :integer, virtual: true, default: 0 
end 

@primary_key false 
schema "thread_participant" do 
    belongs_to :thread, Messenger.Thread, primary_key: true 
    belongs_to :user, Messenger.UserAbstract, primary_key: true 
    field :last_seen, Ecto.DateTime, usec: true, autogenerate: true 
    field :starred, :boolean, default: false 
    field :muted, :boolean, default: false 
end 

と私は文脈するクエリ組成物を用いますユーザーのスレッド一覧:

def for_user(query, user_id) do 
    from t in query, 
    join: p in assoc(t, :participants), 
    join: message in assoc(t, :messages), 
    left_join: messageNew in ThreadMessage, on: messageNew.id == message.id and messageNew.created > p.last_seen, 
    where: p.user_id == ^user_id, 
    order_by: [desc: max(message.created)], 
    group_by: t.id, 
    select: %{ t | last_seen: p.last_seen, muted: p.muted,starred: p.starred, unread_count: count(messageNew.id)} 
end 

だから私は

Thread |> Thread.for_user(user_id) |> Repo.all 

私は正しい集約情報をほぼすべて得ることができますが、group_by thread.idのために参加者IDが不足しています。純粋なSQLで

は、私は以下のコードのような何かをして、コードで私のモデルを再構築します:

SELECT s.id, s.last_seen, s.muted, s.starred, s.last_message_date, s.unread_count, p.user_id 
    FROM (
    SELECT t0.`id` , t2.`last_seen` , t2.`muted` , t2.`starred` , max(t1.`created`) as last_message_date, count(t3.id) as unread_count 
    FROM `thread` AS t0 
    INNER JOIN `thread_message` AS t1 ON t0.`id` = t1.`thread_id` 
    INNER JOIN `thread_participant` AS t2 ON (t0.`id` = t2.`thread_id`) AND (t2.`user_id` = 9854) 
    LEFT JOIN `thread_message` AS t3 ON t3.`id` = t1.`id` AND t3.`created` > t2.`last_seen` 
    GROUP BY t0.`id` 
) as s 
    INNER JOIN `thread_participant` AS p ON p.`thread_id` = s.`id` 
    ORDER BY s.`last_message_date` DESC 

すべてのエクト(でも、サブクエリまたはフラグメントを使用して)にこれを変換する私の試みは失敗した(無マックス()サブクエリでは、サブクエリでのフィールドエイリアスが保存されていない、...)

だから、最初のクエリ(for_user())に加えて、私は2番目のクエリで参加者をロードしています:

thread_ids = Enum.map(threads, fn (x) -> x.id end) 

def get_participating_user(thread_ids) do 
    from tp in ThreadParticipant, 
    join: user in assoc(tp, :user), 
    where: tp.thread_id in ^thread_ids, 
    preload: :user 
end 

participants = Thread.get_participating_user(thread_ids) |> Repo.all 

しかし、私はどのように2つの結果セットをマージすることができますか(参加者キーの下の最初のクエリから各スレッドに属する2番目のクエリのThreadParticipantsを入れます)、次にそれをどのように出力するか、 (唯一の参加者のIDのはthread.participants下に保持され、すべての個別のユーザーが、ユーザーの下に出力されている)

は時間この上で立ち往生していた私の見解では、私は本当にあなたが

答えて

0

Iを共有できる知識をいただければと思います最終的にすべてが機能するようになりました。何時間もかけてホイールを再作成した後(つまり、2番目のクエリでthread_participantsを読み込み、スレッドのリストを通って参加者を追加する)、最初のクエリに何を入れても、ectoは別のクエリで事前ロードされた関連をフェッチします。

問題1(どのように2つの結果セットをマージできますか)を解決するには、解決策は次のとおりです。限り、あなたが持っているとして、あなたのメインクエリのエクトにロードされたスレッドIDはあなたのために懸命に仕事をするために幸せになります:

def for_user(query, user_id) do 
    from t in query, 
    join: p in assoc(t, :participants), 
    join: message in assoc(t, :messages), 
    join: u in assoc(p, :user), 
    left_join: messageNew in ThreadMessage, on: messageNew.id == message.id and messageNew.created > p.last_seen, 
    where: p.user_id == ^user_id, 
    order_by: [desc: max(message.created)], 
    group_by: t.id, 
    preload: [:participants,:users], 
    select: %{ t | last_seen: p.last_seen, muted: p.muted,starred: p.starred, unread_count: count(messageNew.id)} 
    end 

デバッグモードでは、あなたがそのエクトは、次のクエリを実行します見ることができます

SELECT t0.`id`, t0.`created`, t0.`creator_id`, t1.`last_seen`, t1.`muted`, t1.`starred`, count(t4.`id`) FROM `thread` AS t0 INNER JOIN `thread_participant` AS t1 ON t1.`thread_id` = t0.`id` INNER JOIN `thread_message` AS t2 ON t2.`thread_id` = t0.`id` INNER JOIN `user` AS u3 ON u3.`id` = t1.`user_id` LEFT OUTER JOIN `thread_message` AS t4 ON (t4.`id` = t2.`id`) AND (t4.`created` > t1.`last_seen`) WHERE (t1.`user_id` = ?) GROUP BY t0.`id` ORDER BY max(t2.`created`) DESC LIMIT 5 [20] 

SELECT t0.`thread_id`, t0.`user_id`, t0.`last_seen`, t0.`starred`, t0.`muted`, t0.`thread_id` FROM `thread_participant` AS t0 WHERE (t0.`thread_id` IN (?,?,?,?,?)) ORDER BY t0.`thread_id` [45, 47, 66, 77, 88] 

SELECT u0.`id`, u0.`display_name`, u0.`id` FROM `user` AS u0 WHERE (u0.`id` IN (?,?,?,?,?,?)) [10, 11, 12, 13, 14, 15] 

問題を修正するには(私の見解では、どのように正規化して出力することができますか(参加者IDはthread.participantsの下に置かれ、すべての別個のユーザーはユーザーの下に出力されます))エリクシルの理解を開始すると、マップ、リスト、列挙型:

コントロール

def render("index.json", %{thread: threads}) do 
%{ data: 
    %{ 
    threads: render_many(threads, Messenger.ThreadView, "user_thread.json"), 
    users: render_many(threads |> Stream.flat_map(&(&1.users)) |> Stream.uniq, Messenger.UserAbstractView, "user_abstract.json") 
    } 
} 

def render("user_thread.json", %{thread: thread}) do 
    %{id: thread.id, 
    last_seen: thread.last_seen, 
    muted: thread.muted, 
    starred: thread.starred, 
    unread_count: thread.unread_count, 
    participants: Enum.map(thread.participants, fn(tp) -> tp.user_id end) 
    } 
end 

トリッキーパーツ:あなたが行く

#Here we extract a list of uniq users from our list of threads 
#and use our user view to render them 
users: render_many(threads |> Stream.flat_map(&(&1.users)) |> Stream.uniq, Messenger.UserAbstractView, "user_abstract.json") 

#Here we populate the participants key with a list of the participants ids 
participants: Enum.map(thread.participants, fn(tp) -> tp.user_id end) 

そして、そこrの次のコードを持っているビューにスレッドリストを渡します! →正規化された構造。

エリクサーがこのすばらしい言葉で私のようにあなたのつま先を浸していれば、それはあなたにいくつかの時間を節約することを願っています。

関連する問題