web-dev-qa-db-ja.com

複数の自己結合を最適化する方法は?

複数の自己結合の最適化、またはより優れたテーブル/ DB設計に関するアドバイスを探しています。

テーブルの1つは次のようになります(関連するcolsのみ)。

CREATE TABLE IF NOT EXISTS CountryData (
  countryDataID INT PRIMARY KEY AUTO_INCREMENT,
  dataID INT NOT NULL REFERENCES DataSources (dataID),
  dataCode VARCHAR(30) NULL,
  countryID INT NOT NULL REFERENCES Countries (countryID),
  year INT NOT NULL ,
  data DEC(20,4) NULL,
  INDEX countryDataYear (dataID, countryID, year));

data列には、数百の指標、90か国、合計で100万行までの30年の値があります。標準クエリでは、特定の年とCの国のN個のインジケーターを選択し、最大90行のCxNテーブルを生成する必要があります。

すべての値が1つの列にあるため、自己結合は適切な方法のように見えました。そのため、インデックス作成や新しい(一時)テーブルの作成など、これらを高速化するためのさまざまな提案を試しました。 9回の自己結合では、クエリは1分弱かかります。それを超えて、それは永遠に回転します。

自己結合が行われる新しいテーブルには、約1,000行しかなく、関連する変数と思われるものにインデックスが付けられています。作成には約0.5秒かかります。

CREATE TABLE Growth
    SELECT dataID, countryID, year, data
    FROM CountryData
    WHERE dataID > 522 AND year = 2017;

CREATE INDEX growth_ix 
    ON Growth (dataID, countryID);

SELECTクエリは、結果テーブルに最大XXのインジケーターを配置しますが、XXは残念ながら<10です。

SELECT 
    Countries.countryName AS Country,   
    em01.em,
    em02.em,
    em03.em
    ...
    emX.em
FROM    
    (SELECT
        em1.data AS em,
        em1.countryID
    FROM Growth AS em1
    WHERE
    em1.dataID = 523) as em01
    JOIN 
    (SELECT
        em2.data AS em,
        em2.countryID
    FROM Growth AS em2
    WHERE
    em2.dataID = 524) as em02
    USING (countryID)
    JOIN
    (SELECT
        em3.data AS em,
        em3.countryID
    FROM Growth AS em3
    WHERE
    em3.dataID = 525) as em03
    USING (countryID)
    ...
    JOIN
    (SELECT
        emX.data AS em,
        emX.countryID
    FROM Growth AS em5
    WHERE
    emX.dataID = 527) as emXX
    USING (countryID)
    JOIN Countries 
    USING (countryID)

さらにいくつかの変数を取得し、さらに他のテーブルを結合する可能性があります。これをより効率的に実行する方法があるのか​​、または自己結合を回避するために異なる列にインジケーターがあるワイドテーブルを使用するなど、まったく異なるアプローチを取るべきなのか、と私は思っています。

4
Stefan

テーブルを非正規化する必要はないと思います。効果的に使用されるインデックスがある場合、自己結合は正常に機能します。特定のクエリでは、(year, dataID, countryID, data)にインデックスを追加し、派生テーブルは使用しません。

SELECT 
    c.countryName AS Country,   
    em01.data AS data01,
    em02.data AS data02,
    ...
    emXX.data AS dataXX
FROM  
    Countries AS c
      JOIN CountryData AS em01
        ON  em01.year = 2017
        AND em01.dataID = 523 
        AND em01.countryID = c.countryID 
      JOIN CountryData AS em02
        ON  em02.year = 2017
        AND em02.dataID = 524
        AND em02.countryID = c.countryID
      ... 
      JOIN CountryData AS emXX
        ON  emXX.year = 2017
        AND emXX.dataID = YYY
        AND emXX.countryID = c.countryID 
 ;

あなたが持つ唯一の問題は、MySQLがクエリで最大61の結合というハード制限を持っていることです。したがって、上記のクエリでは90列にすることはできません。

別の観察として、集約されたデータは必要ないようですが、(小さなテーブルでも大きなテーブルでも構いません)のごく一部です。推奨インデックスを使用すると、次のようなクエリを作成できます。

SELECT 
    cd.countryID,
    c.countryName AS Country,   
    cd.dataID,
    cd.data
FROM  
    Countries AS c
      JOIN CountryData AS cd
        ON  cd.countryID = c.countryID 
WHERE 
      cd.year = 2017
  AND cd.dataID IN (522, 523, ..., YYY) 
ORDER BY
    cd.countryID,
    cd.dataID ; 

アプリケーションでピボット変換を実行します。

4
ypercubeᵀᴹ