カテゴリーが階層内のいずれかのカテゴリーに属する製品を選択します
-
10-07-2019 - |
質問
カテゴリのFKを含む製品テーブルがあります。カテゴリテーブルは、各カテゴリが親カテゴリを持つことができるように作成されます。例:
Computers
Processors
Intel
Pentium
Core 2 Duo
AMD
Athlon
選択したカテゴリがプロセッサの場合、Intel、Pentium、Core 2 Duo、Amdなどの製品を返すという選択クエリを作成する必要があります。
何らかのキャッシュを作成することを考えました。これは、db内のすべてのカテゴリの階層内のすべてのカテゴリを格納し、" IN" where句で。これが最良の解決策ですか?
解決
これに最適なソリューションは、データベースの設計段階です。カテゴリテーブルは、ネストされたセットである必要があります。 MySQLでの階層データの管理は、MySQL固有のものではありません(タイトルにもかかわらず)、データベーステーブルに階層を格納するさまざまな方法の概要を説明します。
エグゼクティブサマリー:
ネストされたセット
- 選択はどの深さでも簡単です
- 挿入と削除は難しい
標準のparent_idベースの階層
- 選択は内部結合に基づいています(そのため、毛むくじゃらになります)
- 挿入と削除は簡単です
したがって、例に基づいて、階層テーブルがネストされたセットである場合、クエリは次のようになります。
SELECT * FROM products
INNER JOIN categories ON categories.id = products.category_id
WHERE categories.lft > 2 and categories.rgt < 11
2と11は、それぞれ Processors
レコードの左と右です。
他のヒント
共通テーブル式の仕事のように見えます。
with catCTE (catid, parentid)
as
(
select cat.catid, cat.catparentid from cat where cat.name = 'Processors'
UNION ALL
select cat.catid, cat.catparentid from cat inner join catCTE on cat.catparentid=catcte.catid
)
select distinct * from catCTE
「Processors」という名前のカテゴリとその子孫を選択する必要があり、それをIN句で使用して製品をプルバックできる必要があります。
過去に同様のことを行いました。最初にカテゴリIDを照会し、次に製品「IN」を照会しました。それらのカテゴリ。カテゴリを取得するのは難しいことであり、いくつかのオプションがあります:
- カテゴリのネストのレベルがわかっている場合、または上限を見つけることができる場合:多数のJOINを使用して、恐ろしい外観のSELECTを構築します。これは高速ですが、いため、階層のレベルに制限を設定する必要があります。
- 合計カテゴリ数が比較的少ない場合は、それらすべて(単にID、親)をクエリし、関心のあるIDを収集し、製品に対してSELECT .... INを実行します。これは私にとって適切なオプションでした。
- 一連のSELECTを使用して、階層を上下に照会します。シンプルですが、比較的遅いです。
- SQLServerの最近のバージョンは再帰クエリをサポートしていると思いますが、自分では使用していません。
ストアドプロシージャは、このアプリ側で行いたくない場合に役立ちます。
見つけたいのは、カテゴリ「親」の推移的な閉鎖です。関係。カテゴリ階層の深さに制限はないため、すべてのカテゴリを検索する単一のSQLクエリを作成することはできません。私が(擬似コードで)することはこれです:
categoriesSet = empty set
while new.size > 0:
new = select * from categories where parent in categoriesSet
categoriesSet = categoriesSet+new
したがって、子が見つからなくなるまで子のクエリを続けます。これは、縮退した階層(たとえば、1000のカテゴリ、それぞれが別の子)、または多数の合計カテゴリを持たない限り、速度の点でうまく機能します。 2番目のケースでは、常に一時テーブルを使用して、アプリとデータベース間のデータ転送を小さく保つことができます。
たぶん次のようなもの:
select *
from products
where products.category_id IN
(select c2.category_id
from categories c1 inner join categories c2 on c1.category_id = c2.parent_id
where c1.category = 'Processors'
group by c2.category_id)
[編集]カテゴリの深さが1より大きい場合、これは最も内側のクエリを形成します。内部クエリによって返されたIDに子がなくなるまでテーブルをドリルダウンするストアドプロシージャを設計できると思います(おそらく、階層内のターミナルノードとしてカテゴリをマークする属性を持つ方がよいでしょう)。それらのIDに対して外部クエリを実行します。
CREATE TABLE #categories (id INT NOT NULL, parentId INT, [name] NVARCHAR(100))
INSERT INTO #categories
SELECT 1, NULL, 'Computers'
UNION
SELECT 2, 1, 'Processors'
UNION
SELECT 3, 2, 'Intel'
UNION
SELECT 4, 2, 'AMD'
UNION
SELECT 5, 3, 'Pentium'
UNION
SELECT 6, 3, 'Core 2 Duo'
UNION
SELECT 7, 4, 'Athlon'
SELECT *
FROM #categories
DECLARE @id INT
SET @id = 2
; WITH r(id, parentid, [name]) AS (
SELECT id, parentid, [name]
FROM #categories c
WHERE id = @id
UNION ALL
SELECT c.id, c.parentid, c.[name]
FROM #categories c JOIN r ON c.parentid=r.id
)
SELECT *
FROM products
WHERE p.productd IN
(SELECT id
FROM r)
DROP TABLE #categories
このようにまっすぐに実行している場合、例の最後の部分は実際には機能しません。製品から選択を削除し、単純なSELECT * FROM r
に置き換えます。これは、指定されたカテゴリから始まるすべての「子」カテゴリを再帰する必要があります。
DECLARE @startingCatagoryId int
DECLARE @current int
SET @startingCatagoryId = 13813 -- or whatever the CatagoryId is for 'Processors'
CREATE TABLE #CatagoriesToFindChildrenFor
(CatagoryId int)
CREATE TABLE #CatagoryTree
(CatagoryId int)
INSERT INTO #CatagoriesToFindChildrenFor VALUES (@startingCatagoryId)
WHILE (SELECT count(*) FROM #CatagoriesToFindChildrenFor) > 0
BEGIN
SET @current = (SELECT TOP 1 * FROM #CatagoriesToFindChildrenFor)
INSERT INTO #CatagoriesToFindChildrenFor
SELECT ID FROM Catagory WHERE ParentCatagoryId = @current AND Deleted = 0
INSERT INTO #CatagoryTree VALUES (@current)
DELETE #CatagoriesToFindChildrenFor WHERE CatagoryId = @current
END
SELECT * FROM #CatagoryTree ORDER BY CatagoryId
DROP TABLE #CatagoriesToFindChildrenFor
DROP TABLE #CatagoryTree
iは、階層データにスタック一時テーブルを使用します。 以下に大まかな例を示します-
-- create a categories table and fill it with 10 rows (with random parentIds)
CREATE TABLE Categories ( Id uniqueidentifier, ParentId uniqueidentifier )
GO
INSERT
INTO Categories
SELECT NEWID(),
NULL
GO
INSERT
INTO Categories
SELECT TOP(1)NEWID(),
Id
FROM Categories
ORDER BY Id
GO 9
DECLARE @lvl INT, -- holds onto the level as we move throught the hierarchy
@Id Uniqueidentifier -- the id of the current item in the stack
SET @lvl = 1
CREATE TABLE #stack (item UNIQUEIDENTIFIER, [lvl] INT)
-- we fill fill this table with the ids we want
CREATE TABLE #tmpCategories (Id UNIQUEIDENTIFIER)
-- for this example we’ll just select all the ids
-- if we want all the children of a specific parent we would include it’s id in
-- this where clause
INSERT INTO #stack SELECT Id, @lvl FROM Categories WHERE ParentId IS NULL
WHILE @lvl > 0
BEGIN -- begin 1
IF EXISTS ( SELECT * FROM #stack WHERE lvl = @lvl )
BEGIN -- begin 2
SELECT @Id = [item]
FROM #stack
WHERE lvl = @lvl
INSERT INTO #tmpCategories
SELECT @Id
DELETE FROM #stack
WHERE lvl = @lvl
AND item = @Id
INSERT INTO #stack
SELECT Id, @lvl + 1
FROM Categories
WHERE ParentId = @Id
IF @@ROWCOUNT > 0
BEGIN -- begin 3
SELECT @lvl = @lvl + 1
END -- end 3
END -- end 2
ELSE
SELECT @lvl = @lvl - 1
END -- end 1
DROP TABLE #stack
SELECT * FROM #tmpCategories
DROP TABLE #tmpCategories
DROP TABLE Categories
ここに良い説明がありますリンクテキスト
数日前の別の質問に対する私の答えはここに適用されます... SQLの再帰
私がリンクした本には、あなたの状況をうまくカバーするいくつかの方法があります。