1

3番目の接続テーブルを介して多対多に関連する2つのテーブルがあります:製品とカテゴリ。各製品はいくつかのカテゴリに分類できます。これは典型的な多対多の実現です。

products
-------------
id
product_name


categories
-------------
id
category_name


products_to_categories
-------------
product_id
caregory_id

選択したカテゴリの一部に含まれ、同時に他の選択したカテゴリに含まれていない製品をユーザーが検索できるようにしたい。

例:カテゴリ「コンピュータ」と「ソフトウェア」に含まれているが、カテゴリ「ゲーム」、「プログラミング」、「教育」には含まれていないすべての製品を検索します。

これを行うために私が設計したクエリは次のとおりです。

SELECT product_name
FROM products
WHERE
    EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 1 AND product_id = products.id) 
    AND EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 2 AND product_id = products.id) 
    AND NOT EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 3 AND product_id = products.id)
    AND NOT EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 4 AND product_id = products.id) 
    AND NOT EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 5 AND product_id = products.id)
ORDER BY id

できます。しかし、それは非常に遅いので、私はそれを本番環境で使用することができません。すべてのidexeが配置されていますが、このクエリでは5つの従属サブクエリが生成され、テーブルは巨大になります。

依存するサブクエリなしで同じタスクを解決したり、他の方法でこのクエリを最適化する方法はありますか?

アップデート

インデックスは次のとおりです。

products: PRIMARY KEY (id)
categories: PRIMARY KEY (id)
products_to_categories: PRIMARY KEY (product_id, caregory_id)

すべてのテーブルはInnoDBです

4

4 に答える 4

2

テーブルの定義を投稿してください (使用されているエンジンと定義されているインデックスが表示されます)。

クエリの実行計画を投稿することもできます (EXPLAINステートメントを使用)。

さまざまな方法でクエリを書き直すこともできます。ここに1つあります:

SELECT p.product_name
FROM products  AS p
  JOIN products_to_categories  AS pc1
    ON pc1.category_id = 1 
    AND pc1.product_id = p.id
  JOIN products_to_categories  AS pc2
    ON  pc2.category_id = 2 
    AND pc2.product_id = p.id
WHERE
    NOT EXISTS 
    ( SELECT * 
      FROM products_to_categories  AS pc 
      WHERE pc.category_id IN (3, 4, 5)
        AND pc.product_id = p.id
    )

(category_id, product_id)更新:インデックスがありません。追加してみてください。

于 2012-02-11T18:02:18.440 に答える
0

他の回答がより包括的だったため、回答を削除しました。一般的なヒントです。ステートメント内の AND の数を減らすには、IN 演算子を使用して複数のカテゴリをチェックします。

where category_id IN(1,2)

また

where category_id NOT IN(1,2)
于 2012-02-11T17:35:07.777 に答える
0
SELECT product_name
FROM products
-- we can use an inner join as an optimization, as some categories MUST exist
INNER JOIN products_to_categories ON products.product_id=products_to_categories.product_id
WHERE 
  products_to_categories.category_id NOT IN (3,4,5) -- substitute unwanted category IDs
  AND EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 1 AND product_id = products.id) 
  AND EXISTS (SELECT product_id FROM products_to_categories WHERE category_id = 2 AND product_id = products.id) 
于 2012-02-11T17:33:54.547 に答える
0

inSQLサーバーは複数のクエリを実行するか、「または」を実行するため、句を避けたいと思います。これは、インデックスを利用できない可能性があるため、以下に貼り付けているものよりも効率が悪くなります。

また、#product_categories_filtered 一時テーブルを取り除き、すべてを 1 つの大きなクエリで実行し、必要に応じてエイリアス サブクエリを使用することもできます。さまざまな構成を試してみて、どれが最適かを確認したいかもしれませんが、誰かが数千万のレコードで何かをクエリしようとしない限り、私のアプリでは一時テーブルがパフォーマンスの問題になることはありません. #product_categories_filtered を使用したのは、特にあなたのような大きなテーブルでは、クエリを分割して使用する結合を減らすと、SQL サーバー クエリのパフォーマンスが向上する場合があるためですproduct

create table #includes (category_id int not null primary key)
create table #excludes (category_id int not null primary key)

insert #includes (category_id) 
    select 1
    union all select 2
insert #excludes (category_id) 
    select 3
    union all select 4
    union all select 5

select 
  pc.product_id
into #product_catories_filtered
from 
  product_categories pc
  join #includes i 
    on pc.category_id = i.category_id
  left join #excludes e 
    on pc.category_id = i.category_id
where 
  e.category_id is null


select distinct
  p.product_name
from 
  #product_categories_filtered pc
  join products p
    on pc.product_id = p.id
order by 
  p.id
于 2012-02-11T17:45:53.093 に答える