0

私は、ユーザーが一連のファクト テーブルに対して任意にクエリを実行できるようにするレポート システムに取り組んでおり、ファクト テーブルごとに複数のディメンション テーブルを制限しています。制約パラメーターに基づいてすべての正しい結合とサブクエリを自動的にアセンブルするクエリ ビルダー クラスを作成しましたが、すべてが設計どおりに機能します。

しかし、私は最も効率的なクエリを生成していないと感じています。数百万件のレコードを含む一連のテーブルでは、これらのクエリの実行に約 10 秒かかります。1 秒未満の範囲に収まるようにしたいと考えています。サブクエリを取り除くことができれば、結果ははるかに効率的になると感じています。

実際のスキーマ (はるかに複雑です) を示すのではなく、アプリケーションとデータ モデル全体を説明することなく、要点を説明する類似の例を示します。

アーティストと会場を含むコンサート情報のデータベースがあるとします。ユーザーは、アーティストと会場を任意にタグ付けできます。したがって、スキーマは次のようになります。

concert
  id
  artist_id
  venue_id
  date

artist
  id
  name

venue
  id
  name

tag
  id
  name

artist_tag
  artist_id
  tag_id

venue_tag
  venue_id
  tag_id

ものすごく単純。

ここで、今日から 1 か月以内に開催されるすべてのコンサート、「cheap-beer」および「great-mosh-pits」タグでコンサートに出演する「テクノ」および「トロンボーン」タグを持つすべてのアーティストについて、データベースにクエリを実行するとします。 .

私が思いついた最高のクエリは次のようになります。

SELECT
  concert.id AS concert_id,
  concert.date AS concert_date,
  artist.id AS artist_id,
  artist.name AS artist_name,
  venue.id AS venue_id,
  venue.name AS venue_name,
FROM
  concert
INNER JOIN (
  artist ON artist.id = concert.artist_id
) INNER JOIN (
  venue ON venue.id = concert.venue_id
)
WHERE (
  artist.id IN (
    SELECT artist_id
    FROM artist_tag
    INNER JOIN tag AS a on (
      a.id = artist_tag.tag_id
      AND
      a.name = 'techno'
    ) INNER JOIN tag AS b on (
      b.id = artist_tag.tag_id
      AND
      b.name = 'trombone'
    )
  )
  AND
  venue.id IN (
    SELECT venue_id
    FROM venue_tag
    INNER JOIN tag AS a on (
      a.id = venue_tag.tag_id
      AND
      a.name = 'cheap-beer'
    ) INNER JOIN tag AS b on (
      b.id = venue_tag.tag_id
      AND
      b.name = 'great-mosh-pits'
    )
  )
  AND
  concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH)
)

クエリは機能しますが、これらの複数のサブクエリを使用するのは本当に好きではありません。純粋に JOIN ロジックを使用して同じロジックを実現できれば、パフォーマンスが大幅に向上すると感じています。

完璧な世界では、実際の OLAP サーバーを使用することになります。しかし、私の顧客は MySQL、MSSQL、または Postgres にデプロイする予定であり、互換性のある OLAP エンジンが利用できるとは保証できません。そのため、スター スキーマを持つ通常の RDBMS を使用することに行き詰まっています。

この例の詳細にあまりこだわらないでください (私の実際のアプリケーションは音楽とは何の関係もありませんが、ここで示したものと類似した関係を持つ複数のファクト テーブルがあります)。このモデルでは、「artist_tag」テーブルと「venue_tag」テーブルがファクト テーブルとして機能し、それ以外はすべてディメンションです。

この例では、ユーザーが単一の artist_tag またはvenue_tag 値に対してのみ制約できるようにすると、クエリの記述がはるかに簡単になることに注意することが重要です。複数の異なるタグを必要とする AND ロジックをクエリに含めることを許可する場合にのみ、非常に扱いにくくなります。

私の質問は、複数のファクト テーブルに対して効率的なクエリを作成するための、あなたが知っている最良の手法は何ですか?

4

3 に答える 3

2

私のアプローチはもう少し一般的で、フィルター パラメーターをテーブルに配置し、GROUP BY、HAVING、および COUNT を使用して結果をフィルター処理します。私はこの基本的なアプローチを非常に洗練された「検索」に数回使用しましたが、非常にうまく機能します(私にとってはにやにや笑い)。

また、最初は Artist と Venue のディメンション テーブルにも参加しません。結果を ID として取得し (artist_tag と vehicle_tag のみが必要)、artist テーブルと会場テーブルの結果を結合して、それらのディメンション値を取得します。(基本的に、サブクエリでエンティティ ID を検索し、次に外側のクエリで必要なディメンション値を取得します。それらを別々に保つと、状況が改善されるはずです...)

DECLARE @artist_filter TABLE (
  tag_id INT
)

DECLARE @venue_filter TABLE (
  tag_id INT
)

INSERT INTO @artist_filter
SELECT id FROM tag
WHERE name IN ('techno','trombone')

INSERT INTO @venue_filter
SELECT id FROM tag
WHERE name IN ('cheap-beer','great-most-pits')


SELECT
  concert.id AS concert_id,
  concert.date AS concert_date,
  artist.id AS artist_id,
  venue.id AS venue_id
FROM
  concert
INNER JOIN
  artist_tag
    ON artist_tag.artist_id = concert.artist_id
INNER JOIN
  @artist_filter AS [artist_filter]
    ON [artist_filter].tag_id = artist_tag.id
INNER JOIN
  venue_tag
    ON venue_tag.venue_id = concert.venue_id
INNER JOIN
  @venue_filter AS [venue_filter]
    ON [venue_filter].tag_id = venue_tag.id
WHERE
  concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH)
GROUP BY
  concert.id,
  concert.date,
  artist_tag.artist_id,
  venue_tag.id
HAVING
  COUNT(DISTINCT [artist_filter].id) = (SELECT COUNT(*) FROM @artist_filter)
  AND
  COUNT(DISTINCT [venue_filter].id)  = (SELECT COUNT(*) FROM @venue_filter)

(私はネットブックを使用していて、それに苦しんでいるので、アーティストと会場のテーブルからアーティストと会場の名前を取得する外側のクエリは省略します)

編集
注:

もう 1 つのオプションは、サブクエリ/派生テーブルで artist_tag およびvenue_tag テーブルをフィルタリングすることです。これが価値があるかどうかは、Concert テーブルでの結合の影響力によって異なります。ここでの私の仮定は、多くのアーティストと会場が存在することですが、コンサート テーブルでフィルター処理されると (それ自体が日付でフィルター処理されます)、アーティスト/会場の数は劇的に減少します。

また、artist_tags および/またはvenue_tags が指定されていない場合に対処する必要性/要望がしばしばあります。経験上、これはプログラムで処理することをお勧めします。つまり、これらのケースに特に適した IF ステートメントとクエリを使用します。それを処理するために単一の SQL クエリを作成することはできますが、プログラムによる代替方法よりもはるかに遅くなります。同様に、似たようなクエリを何度も書くと、面倒に見えて保守性が低下する可能性がありますが、これを単一のクエリにするために必要な複雑さが増すと、多くの場合、保守が難しくなります。

編集

別の同様のレイアウトは次のようになります...
- サブクエリ/派生テーブルとしてアーティスト別にコンサートをフィルター処理する
- サブクエリ/派生テーブルとして会場別に結果をフィルター処理する
- ディメンション テーブルで結果を結合して名前を取得するなど

(カスケード フィルタリング)

SELECT
   <blah>
FROM
  (
    SELECT
      <blah>
    FROM
      (
        SELECT
          <blah>
        FROM
          concert
        INNER JOIN
          artist_tag
        INNER JOIN
          artist_filter
        WHERE
        GROUP BY
        HAVING
      )
    INNER JOIN
      venue_tag
    INNER JOIN
      venue_filter
    GROUP BY
    HAVING
  )
INNER JOIN
  artist
INNER JOIN
  venue

フィルタリングをカスケードすることにより、後続の各フィルタリングには、処理する必要がある削減セットがあります。これにより、クエリの GROUP BY - HAVING セクションで行われる作業が削減される場合があります。2 レベルのフィルタリングでは、これが劇的になる可能性は低いと思います。

オリジナルは、別の方法で追加のフィルタリングを利用できるため、パフォーマンスがさらに向上する可能性があります。あなたの例では:
- あなたの日付範囲には多くのアーティストがいるかもしれませんが、少なくとも1つの基準を満たすものはほとんど
ありません - あなたの日付範囲には多くの会場があるかもしれませんが、少なくとも1つの基準を満たすものはほとんどありません
- ただし、GROUP BYの前に、
---> アーティストがいずれの基準も満たしていない場合
---> AND/OR 会場がいずれの基準も満たし ていない場合、すべてのコンサートが除外されます。

多くの基準で検索している場合、このフィルタリングは低下します。また、会場やアーティストが多くのタグを共有している場合、フィルタリングも低下します。

では、いつオリジナルを使用するのでしょうか、それともカスケード バージョンをいつ使用するのでしょうか?
- オリジナル : 検索条件が少なく、会場/アーティストが互いに類似していない
- カスケード : 検索条件が多いか、会場/アーティストが類似している傾向がある

于 2009-04-18T16:25:49.380 に答える
1

モデルを非正規化します。会場とアーティストのテーブルにタグ名を含めます。このようにして、多対多の関係を回避し、単純なスタースキーマを作成します。

この非正規化を適用することにより、where句は両方のテーブル(アーティストと会場)のこの追加のtag_nameフィールドのみをチェックできます。

于 2009-04-18T15:52:10.740 に答える