個々のクエリは10ミリ秒で実行され、UNION ALLでは290ミリ秒以上かかります(7.7MレコードMySQL DB)。最適化するには?


9

教師が利用できる予定を保存するテーブルがあり、2種類の挿入が可能です。

  1. 時間単位:教師ごとに1日あたり無制限のスロットを追加する自由(スロットがオーバーラップしない限り):4月15日、教師は10:00、11:00、12:00、16:00にスロットを持つことができます。特定の教師の時間/スロットを選択すると、担当者が対応します。

  2. 時間帯/範囲:4月15日/ 4月に別の教師が10:00から12:00まで、そして14:00から18:00まで働くことができます。人は到着順に提供されるため、教師が10:00から12:00に勤務している場合、この期間に到着するすべての人には到着順に参加します(ローカルキュー)。

検索で利用可能なすべての教師を返す必要があるため、到着範囲の順序と同じテーブルにすべてのスロットを保存する必要があります。この方法では、date_from ASCで注文でき、最初に利用可能なスロットを検索結果の最初に表示します。

現在のテーブル構造

CREATE TABLE `teacher_slots` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `teacher_id` mediumint(8) unsigned NOT NULL,
  `city_id` smallint(5) unsigned NOT NULL,
  `subject_id` smallint(5) unsigned NOT NULL,
  `date_from` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `date_to` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `status` tinyint(4) NOT NULL DEFAULT '0',
  `order_of_arrival` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `by_hour_idx` (`teacher_id`,`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`),
  KEY `order_arrival_idx` (`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`,`date_to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

検索クエリー

実際の日時、city_id、subject_id、およびスロットが使用可能かどうか(status = 0)でフィルターする必要があります。

時間ベースの場合、すべての先生の最初の最も近い利用可能な日にすべての利用可能なスロットを表示する必要があります(特定の日のすべてのタイムスロットを表示し、同じ先生に複数の日を表示することはできません)。(私はmattedgodの助けを借りてクエリを取得しました)。

以下のために基づいて、範囲(order_of_arrival = 1)、私は最も近い利用可能な範囲、教員ごとに1つの時間を表示する必要があります。

最初のクエリは約0.10ミリ秒で個別に実行され、2番目のクエリは0.08ミリ秒、UNION ALLは平均300ミリ秒で実行されます。

(
    SELECT id, teacher_slots.teacher_id, date_from, date_to, order_of_arrival
    FROM teacher_slots
    JOIN (
        SELECT DATE(MIN(date_from)) as closestDay, teacher_id
        FROM teacher_slots
        WHERE   date_from >= '2014-04-10 08:00:00' AND order_of_arrival = 0
                AND status = 0 AND city_id = 6015 AND subject_id = 1
        GROUP BY teacher_id
    ) a ON a.teacher_id = teacher_slots.teacher_id
    AND DATE(teacher_slots.date_from) = closestDay
    WHERE teacher_slots.date_from >= '2014-04-10 08:00:00'
        AND teacher_slots.order_of_arrival = 0
        AND teacher_slots.status = 0
        AND teacher_slots.city_id = 6015
        AND teacher_slots.subject_id = 1
)

UNION ALL

(
    SELECT id, teacher_id, date_from, date_to, order_of_arrival
    FROM teacher_slots
    WHERE order_of_arrival = 1 AND status = 0 AND city_id = 6015 AND subject_id = 1
        AND (
            (date_from <= '2014-04-10 08:00:00' AND  date_to >= '2014-04-10 08:00:00')
            OR (date_from >= '2014-04-10 08:00:00')
        )
    GROUP BY teacher_id
)

ORDER BY date_from ASC;

質問

UNIONを最適化する方法はありますか?たった1つのクエリ(IFなど)で、最大20msまでの適切な応答を得ることができます。

SQLフィドル: http ://www.sqlfiddle.com/#!2/59420/1/0

編集:

日付のみを格納するフィールド "only_date_from"を作成して、いくつかの非正規化を試みたので、これを変更できます...

DATE(MIN(date_from)) as closestDay / DATE(teacher_slots.date_from) = closestDay

...これに

MIN(only_date_from) as closestDay / teacher_slots.only_date_from = closestDay

すでに100ms節約できました!まだ平均200ms。

回答:


1

まず、元のクエリは「正しくない」かもしれません。あなたのSQLFiddleを参照すると、それはあなたが持つ行を返すべきであるかのように私には見えますID= 23および4(と行のほかにID= 1あなたがしているあなたがあるかのように、既存のロジックが表示されますので、この半分から取得)意図これらの他の行のためにOR (date_from >= '2014-04-10 08:00:00')2番目のWHERE条項の一部を明示的に満たしているため、含まれます。

GROUP BY teacher_idあなたのあなたのあなたの2番目の部分の節はあなたにUNIONそれらの行を失うことを引き起こしています。これは、実際には選択リストの列を集計していないためであり、この場合はGROUP BY「定義するのが難しい」動作になります。

また、のパフォーマンスの低下については説明できませんUNIONが、クエリから完全に削除することで回避できます。

同じテーブルから行を取得するために2つの個別の(そして部分的に繰り返しの)ロジックセットを使用するのではなく、ロジックを1つのクエリに統合して、ロジックの違いをORまとめました(つまり、行がどちらか一方に出会う場合)あなたの元のWHERE条項の、それは含まれています。私は交換してきたので、これは可能である(INNER) JOINあなたが見つけるために使用していたclosestDateLEFT JOIN

このLEFT JOIN手段は、我々は今も区別することができ、その行に適用されるべきロジックのセット。結合が機能する場合(closestDate IS NOT NULL)、前半からロジックを適用しますが、結合が失敗した場合(closestDate IS NULL)、後半からロジックを適用します。

したがって、これはクエリが(フィドルで)返したすべての行を返し、それらの追加の行も取得します。

  SELECT
    *

  FROM 
    teacher_slots ts

    LEFT JOIN 
    (
      SELECT 
        teacher_id,
        DATE(MIN(date_from)) as closestDay

      FROM 
        teacher_slots

      WHERE   
        date_from >= '2014-04-10 08:00:00' 
        AND order_of_arrival = 0
        AND status = 0 
        AND city_id = 6015 
        AND subject_id = 1

      GROUP BY 
        teacher_id

    ) a
    ON a.teacher_id = ts.teacher_id
    AND a.closestDay = DATE(ts.date_from)

  WHERE 
    /* conditions that were common to both halves of the union */
    ts.status = 0
    AND ts.city_id = 6015
    AND ts.subject_id = 1

    AND
    (
      (
        /* conditions that were from above the union 
           (ie when we joined to get closest future date) */
        a.teacher_id IS NOT NULL
        AND ts.date_from >= '2014-04-10 08:00:00'
        AND ts.order_of_arrival = 0
      ) 
      OR
      (
        /* conditions that were below the union 
          (ie when we didn't join) */
        a.teacher_id IS NULL       
        AND ts.order_of_arrival = 1 
        AND 
        (
          (
            date_from <= '2014-04-10 08:00:00' 
            AND  
            date_to >= '2014-04-10 08:00:00'
          )

          /* rows that met this condition were being discarded 
             as a result of 'difficult to define' GROUP BY behaviour. */
          OR date_from >= '2014-04-10 08:00:00' 
        )
      )
    )

  ORDER BY 
   ts.date_from ASC;

さらに、クエリをさらに「整理」してstatuscity_idsubject_idパラメータを「プラグイン」する必要がないようにすることができます。

これを行うには、サブクエリaを変更して、これらの列も選択し、それらの列でグループ化するようにします。次に、JOIN' ON句はこれらの列をts.xxx同等の列にマップする必要があります。

これがパフォーマンスに悪影響を与えるとは思いませんが、大規模なデータセットでテストしないと確信が持てません。

したがって、結合は次のようになります。

LEFT JOIN 
(
  SELECT 
    teacher_id,
    status,
    city_id,
    subject_id,
    DATE(MIN(date_from)) as closestDay

  FROM 
    teacher_slots

  WHERE   
    date_from >= '2014-04-10 08:00:00' 
    AND order_of_arrival = 0
  /* These no longer required here...
    AND status = 0 
    AND city_id = 6015 
    AND subject_id = 1
  */

  GROUP BY 
    teacher_id,
    status,
    city_id,
    subject_id

) a
ON a.teacher_id = ts.teacher_id
AND a.status = ts.status 
AND a.city_id = ts.city_id 
AND a.subject_id = ts.city_id
AND a.closestDay = DATE(ts.date_from)

2

このクエリを試してください:

(
select * from (SELECT id, teacher_slots.teacher_id, date_from, date_to,  order_of_arrival
FROM teacher_slots  WHERE teacher_slots.date_from >= '2014-04-10 08:00:00'
    AND teacher_slots.order_of_arrival = 0
    AND teacher_slots.status = 0
    AND teacher_slots.city_id = 6015
    AND teacher_slots.subject_id = 1) 
 teacher_slots
JOIN (
    SELECT DATE(MIN(date_from)) as closestDay, teacher_id
    FROM teacher_slots
    WHERE   date_from >= '2014-04-10 08:00:00' AND order_of_arrival = 0
            AND status = 0 AND city_id = 6015 AND subject_id = 1
    GROUP BY teacher_id
) a ON a.teacher_id = teacher_slots.teacher_id
AND DATE(teacher_slots.date_from) = closestDay

)

UNION ALL

(
SELECT id, teacher_id, date_from, date_to, order_of_arrival
FROM teacher_slots
WHERE order_of_arrival = 1 AND status = 0 AND city_id = 6015 AND subject_id = 1
    AND (
        (date_from <= '2014-04-10 08:00:00' AND  date_to >= '2014-04-10 08:00:00')
        OR (date_from >= '2014-04-10 08:00:00')
    )
GROUP BY teacher_id
)

ORDER BY date_from ASC;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.