web-dev-qa-db-ja.com

Aのすべての行を、Aの属性に基づいて左結合Bからフェッチします。

productsstoresの2つのテーブルと、真ん中にproduct_storeというピボットテーブルがあります。

(MySQLからの)テーブル構造の関連部分は次のとおりです。

製品

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`global` tinyint(1) NOT NULL DEFAULT '0',

店舗

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,

product_store

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(10) unsigned NOT NULL,
`store_id` int(10) unsigned NOT NULL,
`show` tinyint(1) NOT NULL,

globalフラグとshowフラグのロジックは次のとおりです。

  • globalフラグが1の場合、product_storeエントリがある場合、この製品がすべての店舗にあると想定しますexceptshow == 0。
  • globalフラグが0の場合、show =のproduct_storeエントリがある店舗では、この製品はonlyであると想定します= 1。

つまり、global == 1はこの商品をどこにでも表示することを意味し、show == 1はこのストアの商品を表示することを意味し、show == 0は表示しないことを意味しますこのストアの商品であり、showglobalよりも優先されます。

私ができることは、既知のstore_idに基づいて製品をフェッチすることです。ここで、

  • products.global == 1かつ、product_id、store_idのproduct_storeエントリがない
  • OR
  • product_id、store_idおよびproduct_store.show == 1のproduct_storeエントリがあります

現在、GROUP BYとネストされたSELECTを使用したこのクエリがありますが、データベースから多くのレコードをプルし、GROUP BYによって統合すると、非常に遅くなります。ネストされた選択なしで、もっと速いものがあるに違いないと思います。

SELECT products.id, products.name, products.global, product_store.show, product_store.store_id
    FROM products
    LEFT JOIN product_store ON products.id=product_store.product_id
    WHERE ((products.global=1 AND
          ((SELECT COUNT(*) FROM product_store WHERE product_store.store_id=226 AND product_store.product_id=products.id) = 0)
         )
        OR (product_store.store_id=226 AND product_store.show=1)
        )
    GROUP BY products.id
    ;

226はこの場合のストアIDです。このクエリを実行する必要があるのは一度に1つのストアだけなので、これは定数と見なすことができます。

上記のクエリをGROUP BYで実行すると、約14レコードが得られます(自分でカウントした場合)。システムには数千の製品と数百のストアがあるため、product_store結合テーブルはかなり大きくなります。 GROUP BYなしで上記のクエリを実行すると、LEFT JOIN句に適合するすべての製品/製品ストアの組み合わせのリストのように見える〜1500レコードが得られます。

繰り返しますが、上記のクエリは正常に機能します(ただし、MySQLバージョン> 5.7.5ではGROUP BYで問題が発生します)が、効率が悪いようです。

表コンテンツの詳細

以下は、関連する商品や店舗などに関する簡単なクエリです。最初に、関連するすべての一致する商品を表示します。15個あり、すべてglobal = 1です。

SELECT products.id, MD5(products.name), products.global
    FROM products
    -- .. omitted some classification information here
    ;

+-----+----------------------------------+--------+
| id  | md5(products.name)               | global |
+-----+----------------------------------+--------+
| 555 | 56e597c43cff47af57f006b7de7ff552 |      1 |
| 591 | 7ba5549546ce20e1dee02c7f6454b16f |      1 |
| 558 | 7b66d8dce0c112b3966d1940de44f9ad |      1 |
| 556 | bd3d39b9f5194184ea0853c70d7faa07 |      1 |
| 590 | ef6eccbd3795fbe70890220c09ed880d |      1 |
| 559 | b763e2b9ba305538b497da97fba066ca |      1 |
| 593 | e26744d617ed12ebffea4b996fecbbef |      1 |
| 594 | 06ba8ab6649c83577db053e4a89c1655 |      1 |
| 596 | 79f9241643e84e2a27bfc0b260432e82 |      1 |
| 595 | 85fe772dc61d27292c09a7604cf76dd7 |      1 |
| 597 | fd6c21db00e1b679c0d0d2ef8f3f6e00 |      1 |
| 560 | c8056818e3ed5885188d78a9440fa77c |      1 |
| 561 | 33d79c194e39e8e84cf743eefcd909df |      1 |
| 562 | 1c74d85823e509ab4b09e82314b09604 |      1 |
| 557 | b6a8027035b63bd4d4bd21169434426d |      1 |
+-----+----------------------------------+--------+

次に、product_storeテーブルとの結合を確認します。 store_id 226のエントリは1つだけで、show = 0があります。

SELECT products.id, MD5(products.name), products.global, product_store.show
    FROM products
    INNER JOIN product_store ON products.id=product_store.product_id
    -- omitted the classification information as per the above
    WHERE product_store.store_id=226
    ;

+-----+----------------------------------+--------+------+
| id  | MD5(products.name)               | global | show |
+-----+----------------------------------+--------+------+
| 555 | 56e597c43cff47af57f006b7de7ff552 |      1 |    0 |
+-----+----------------------------------+--------+------+

...したがって、global = 1である15個の製品すべてを選択するクエリを作成できる必要があります。したがって、product_store.show = 0である製品を除き、残りの14個の製品のセットが必要です。

UNION selectはどういうわけか機能しますか?または何らかの形で交差していますか?

サンプル: http://sqlfiddle.com/#!9/1342cf/5

3
delatbabel

あなたのサンプルに基づいて、これは同等のクエリのようです:

SELECT products.id, products.name, products.global, product_store.show, product_store.store_id
FROM products
LEFT JOIN product_store
  ON products.id=product_store.product_id
 AND product_store.store_id=226
WHERE -- there is NO entry in product_store AND products.global=1
      (products.global=1 AND product_store.show IS NULL)
      -- there is an entry in product_store with show=1 
   OR (products.global=0 AND product_store.show = 1)

DISTINCTは必要ありません。

これをあなたの fiddle に追加しました

ONWHEREに条件を配置することは、外部結合にとって重要です。 ONでは、これは行をフィルターにかけず、内部テーブルにNULLを作成する可能性がある単なる条件です。 WHEREが結合されてafter結合され、ここでフィルタリングされます(内部テーブルの列に適用されると、外部テーブルによって作成されたすべてのNULLがOR col IS NULL)を追加しない限り、結合は再び削除されます。

2
dnoeth