products
とstores
の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は表示しないことを意味しますこのストアの商品であり、show
はglobal
よりも優先されます。
私ができることは、既知のstore_idに基づいて製品をフェッチすることです。ここで、
現在、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はどういうわけか機能しますか?または何らかの形で交差していますか?
あなたのサンプルに基づいて、これは同等のクエリのようです:
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 に追加しました
ON
とWHERE
に条件を配置することは、外部結合にとって重要です。 ON
では、これは行をフィルターにかけず、内部テーブルにNULLを作成する可能性がある単なる条件です。 WHERE
が結合されてafter結合され、ここでフィルタリングされます(内部テーブルの列に適用されると、外部テーブルによって作成されたすべてのNULLがOR col IS NULL
)を追加しない限り、結合は再び削除されます。