5

いくつかのサブクエリ (内部選択) を含むクエリがあります。1 つの大きなクエリと多数の小さなクエリのどちらがパフォーマンスに優れているかを試していますが、変化に応じて違いを試して時間を計るのが難しいと感じています。私のサーバーで常に。

以下のクエリを使用して、ページネーション (オフセットと制限) を使用して、一度に 10 件の結果を返し、自分の Web サイトに表示します。

SELECT adverts.*, breed.breed, breed.type, sellers.profile_name, sellers.logo, users.user_level , 
round( sqrt( ( ( (adverts.latitude - '51.558430') * (adverts.latitude - '51.558430') ) * 69.1 * 69.1 ) + ( (adverts.longitude - '-0.0069345') * (adverts.longitude - '-0.0069345') * 53 * 53 ) ), 1 ) as distance, 
( SELECT advert_images.image_name FROM advert_images WHERE advert_images.advert_id = adverts.advert_id AND advert_images.main = 1 LIMIT 1) as imagename, 
( SELECT count(advert_images.advert_id) from advert_images WHERE advert_images.advert_id = adverts.advert_id ) AS num_photos 
FROM adverts 
LEFT JOIN breed ON adverts.breed_id = breed.breed_id 
LEFT JOIN sellers ON (adverts.user_id = sellers.user_id) 
LEFT JOIN users ON (adverts.user_id = users.user_id) 
WHERE (adverts.status = 1) AND (adverts.approved = 1) 
AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719) AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613) 
having (distance <= '20') 
ORDER BY distance ASC 
LIMIT 0,10

以下の2つの内部選択をメインクエリから削除してから、私のPHPループで、ループ内のレコードごとに1回、2つの選択を10回呼び出す方がよいでしょうか?

( SELECT advert_images.image_name FROM advert_images WHERE advert_images.advert_id = adverts.advert_id AND advert_images.main = 1 LIMIT 1) as imagename, 
( SELECT count(advert_images.advert_id) from advert_images WHERE advert_images.advert_id = adverts.advert_id ) AS num_photos 
4

2 に答える 2

2

サブクエリを避ける

あなたの内部選択を理解しているように、それらは2つの目的を果たします。関連する画像の名前を見つけ関連する画像の数を数えます。おそらく、内部選択の代わりに左結合を使用して両方を達成するかもしれません:

SELECT …,
      advert_images.image_name AS imagename,
      COUNT(advert_images.advert_id) AS num_photos,
      …
FROM …
     LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id
…
GROUP BY adverts.advert_id
…
LIMIT 0,10

私はこれを試したことはありませんが、おそらく MySQL エンジンは、実際に返される行に対してクエリのその部分のみを実行するのに十分スマートです。

このクエリが特定の画像セットに対して返す画像名については、まったく保証されないことに注意してください。再現可能な結果が必要な場合は、たとえばMIN(advert_images.image_name)、辞書順で最初の画像を選択するなど、集計関数を使用する必要があります。

個別の選択、ループなし

上記がうまくいかない場合、つまり、クエリが計算結果のすべてのadvert_images行についてテーブルを調べている場合は、おそらく 2 番目のクエリを実行したほうがよいでしょう。ただし、ループを回避して、代わりにこれらすべての行を単一のクエリでフェッチすることもできます。for

SELECT advert_images.image_name AS imagename,
       COUNT(advert_images.advert_id) AS num_photos
FROM advert_images
WHERE advert_images.advert_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
GROUP BY advert_images.advert_id

このクエリの 10 個のパラメーターは、現在生成している結果の 10 行に対応しています。写真が関連付けられていない広告は、その結果にまったく含まれないことに注意してください。そのため、デフォルトnum_photosをゼロにimagenameNULLて、コード内で に設定してください。

一時テーブル

あなたがしようとしていることを達成する別の方法は、明示的な一時メモリ内テーブルを使用することです: まず、関心のある結果を選択してから、関連するすべての情報を取得します。

CREATE TEMPORARY TABLE tmp
SELECT adverts.advert_id, round(…) as distance
FROM adverts
WHERE (adverts.status = 1) AND (adverts.approved = 1)
  AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719)
  AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613)
HAVING (distance <= 20)
ORDER BY distance ASC
LIMIT 0,10;

SELECT tmp.distance, adverts.*, …
       advert_images.image_name AS imagename,
       COUNT(advert_images.advert_id) AS num_photos,
       …
FROM tmp
     INNER JOIN adverts ON tmp.advert_id = adverts.advert_id
     LEFT JOIN breed ON adverts.breed_id = breed.breed_id
     LEFT JOIN sellers ON adverts.user_id = sellers.user_id
     LEFT JOIN users ON adverts.user_id = users.user_id
     LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id
GROUP BY adverts.advert_id
ORDER BY tmp.distance ASC;

DROP TABLE tmp;

これにより、他のすべてのテーブルに対して、現在作業中の結果のみがクエリされるようになります。結局のところ、advert_imagesテーブルから複数の行が必要になる可能性があるという事実を除いて、テーブルには魔法はほとんどありません。

結合要素としてのサブクエリ

前の段落のアプローチに基づいて、一時テーブルの管理を回避し、代わりにサブクエリを使用することもできます。

SELECT sub.distance, adverts.*, …
       advert_images.image_name AS imagename,
       COUNT(advert_images.advert_id) AS num_photos,
       …
FROM ( SELECT adverts.advert_id, round(…) as distance
        FROM adverts
        WHERE (adverts.status = 1) AND (adverts.approved = 1)
          AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719)
          AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613)
        HAVING (distance <= 20)
        ORDER BY distance ASC
        LIMIT 0,10;
     ) AS sub
     INNER JOIN adverts ON sub.advert_id = adverts.advert_id
     LEFT JOIN breed ON adverts.breed_id = breed.breed_id 
     LEFT JOIN sellers ON (adverts.user_id = sellers.user_id) 
     LEFT JOIN users ON (adverts.user_id = users.user_id) 
     LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id
GROUP BY adverts.advert_id
ORDER BY sub.distance ASC

ここでも、テーブルのデータのみを使用して関連する行を決定し、adverts他のテーブルから必要な行のみを結合します。ほとんどの場合、その中間結果は内部的に一時テーブルに格納されますが、それは SQL サーバーによって決定されます。

于 2012-09-25T14:41:42.690 に答える
0

MySQLはファイルソート+一時テーブルを使用してクエリを実行していると思います。そのため、大きなテーブルでは、あなたの提案がより良い結果をもたらします。一般に、1 つの大きなクエリよりも小さなクエリを実行する方が適切です。

于 2012-09-25T13:16:00.007 に答える