web-dev-qa-db-ja.com

グループ内のアイテムをカウントするためのより効率的なグループ化

私のクエリは:

SELECT COUNT("EventType"."id") AS "eventCount", "EventType"."id" AS "EventType.id"
  FROM "events" AS "Event" 
  INNER JOIN "event_types" AS "EventType" ON "Event"."eventTypeId" = "EventType"."id"
  INNER JOIN "projects" AS "EventType->Project" ON "EventType"."projectId" = "EventType->Project"."id"
  WHERE "EventType->Project"."id" = 142
  GROUP BY "EventType"."id";

基本的に、特定のプロジェクトで、各タイプのイベントがいくつ発生したかを知りたいです。

関連するスキーマは次のとおりです。

                                                           Table "public.projects"
      Column       |           Type           |                             Modifiers
-------------------+--------------------------+---------------------------------------------------------
 id                | integer                  | not null default nextval('projects_id_seq'::regclass) 
Indexes:
    "projects_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "event_types" CONSTRAINT "event_types_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE

                                                         Table "public.event_types"
    Column     |           Type           |                                 Modifiers
---------------+--------------------------+------------------------------------------------------------
 id            | integer                  | not null default nextval('event_types_id_seq'::regclass)
 projectId     | integer                  | not null
Indexes:
    "event_types_pkey" PRIMARY KEY, btree (id)
    "event_types_project_id" btree ("projectId")
Foreign-key constraints:
    "event_types_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE
Referenced by:
    TABLE "events" CONSTRAINT "events_eventTypeId_fkey" FOREIGN KEY ("eventTypeId") REFERENCES event_types(id) ON UPDATE CASCADE ON DELETE CASCADE

                                              Table "public.events"
 Column      |  Type   |                               Modifiers
-------------+---------+-------------------------------------------------------
 id          | integer | not null default nextval('events_id_seq'::regclass)
 eventTypeId | integer | not null
Indexes:
    "events_pkey" PRIMARY KEY, btree (id)
    "events_event_type_id" btree ("eventTypeId")
Foreign-key constraints:
    "events_eventTypeId_fkey" FOREIGN KEY ("eventTypeId") REFERENCES event_types(id) ON UPDATE CASCADE ON DELETE CASCADE

EXPLAIN ANALYZE結果は次のとおりです。

    QUERY PLAN                                                                                                       
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=320957.49..320962.39 rows=490 width=12) (actual time=2612.748..2612.814 rows=459 loops=1)
   Group Key: "EventType".id
   ->  Hash Join  (cost=122.12..312038.18 rows=1783862 width=4) (actual time=386.978..2501.421 rows=690140 loops=1)
         Hash Cond: ("Event"."eventTypeId" = "EventType".id)
         ->  Seq Scan on events "Event"  (cost=0.00..239469.41 rows=14562141 width=4) (actual time=0.026..1272.817 rows=14558556 loops=1)
         ->  Hash  (cost=116.00..116.00 rows=490 width=4) (actual time=0.323..0.323 rows=459 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 25kB
               ->  Nested Loop  (cost=0.28..116.00 rows=490 width=4) (actual time=0.061..0.263 rows=459 loops=1)
                     ->  Seq Scan on projects "EventType->Project"  (cost=0.00..1.56 rows=1 width=4) (actual time=0.017..0.021 rows=1 loops=1)
                           Filter: (id = 142)
                           Rows Removed by Filter: 47
                     ->  Index Scan using event_types_project_id on event_types "EventType"  (cost=0.28..109.53 rows=490 width=8) (actual time=0.042..0.193 rows=459 loops=1)
                           Index Cond: ("projectId" = 142)
 Planning time: 3.891 ms
 Execution time: 2613.033 ms

イベントテーブル全体(かなり大きい)をスキャンしていて、クエリ全体にかなりの時間がかかっているようです。インデックスをスキャンするだけで済むと思っていました。私の考えでは、インデックスは各インデックスキーのカウントを維持していましたが、おそらくそのメンタルモデルに欠陥があります。

このタイプのクエリを高速化する方法はありますか?そうでない場合は自分でカウントを追跡できますが、クエリまたはスキーマで簡単に見えるものを修正することで単純化できる場合は、.

2
Pace
_SELECT et.id AS event_type_id, count(e."eventTypeId") AS event_count
FROM   event_types et 
LEFT   JOIN events e ON e."eventTypeId" = et.id
WHERE  et."projectId" = 142
GROUP  BY et.id;
_

主なポイント

0.スタイル

可能であれば、CaMeLケース名を避けて、あなたと私たちの生活をより簡単にしてください。

少なくとも、二重引用符を必要としない有効なテーブルエイリアスをクエリで使用してください。

1.正当性

クエリは、宣言された目的のためのコーナーケースを見逃しています。

特定のプロジェクトで、発生した各タイプのイベントの数を知りたい。

_[INNER] JOIN_からeventsは、イベントが0のすべてのタイプを結果から除外します。通常、イベントタイプが0のイベントタイプを含む完全なリストを取得するには、_LEFT [OUTER] JOIN_が必要です。

したがって、代わりにテーブルeventsからnull以外の列をカウントします。明白な候補はcount(e."eventTypeId")です。タイプのイベントが見つからなかった場合(およびその場合のみ)、その列はNULLであり、count()はNULL値をカウントしません。

2.パフォーマンス

参照整合性はFK制約で強制されるため、テーブルprojectsを含める必要はまったくありません。 idがあります。これで十分です。

したがって、WHERE句を_WHERE et."projectId" = 142_に適合させます。より短く、より速く。

3.インデックス

理想的には、_event_types_project_id_の既存のインデックス_("projectId")_を_("projectId", id)_の複数列インデックスに置き換えます。正確に同じサイズ、複数の利点。主に、テーブル_event_types_でインデックスのみのスキャンを目指しています。見る:

そして、すでに持っているテーブルeventsの( "eventTypeId")のインデックス(_events_event_type_id_)。

また:

インデックスをスキャンするだけで済むと思っていました。私の考えでは、インデックスは各インデックスキーのカウントを維持していましたが、おそらくそのメンタルモデルに欠陥があります。

いいえ、インデックスはカウントを保持しません。最も一般的な値などの内部統計しかありません。しかし、あなたはcan逃げるインデックスのスキャンのみ-インデックスのみのスキャンの前提条件が満たされている場合。

3