حدد المنتجات التي تنتمي فئتها إلى أي فئة في التسلسل الهرمي
-
10-07-2019 - |
سؤال
لدي جدول منتجات يحتوي على FK لفئة ما، تم إنشاء جدول الفئات بطريقة يمكن أن تحتوي كل فئة على فئة أصل، على سبيل المثال:
Computers
Processors
Intel
Pentium
Core 2 Duo
AMD
Athlon
أحتاج إلى إجراء استعلام تحديد أنه إذا كانت الفئة المحددة هي المعالجات، فسوف يتم إرجاع المنتجات الموجودة في Intel وPentium وCore 2 Duo وAmd وما إلى ذلك...
فكرت في إنشاء نوع من "ذاكرة التخزين المؤقت" التي ستخزن جميع الفئات في التسلسل الهرمي لكل فئة في قاعدة البيانات وتضمين "IN" في جملة المكان.هل هذا هو الحل الأمثل؟
المحلول
أفضل حل لذلك هو في مرحلة تصميم قاعدة البيانات.يجب أن يكون جدول الفئات الخاص بك أ مجموعة متداخلة.المقالة إدارة البيانات الهرمية في 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
وهذا يجب أن حدد الفئة اسمه "المعالجات" وأي من انها سلالة، يجب أن تكون قادرة على استخدام ذلك في جملة IN لسحب المنتجات.
لقد قمت بأشياء مماثلة في الماضي، حيث قمت أولاً بالاستعلام عن معرفات الفئات، ثم الاستعلام عن المنتجات الموجودة في تلك الفئات.يعد الحصول على الفئات أمرًا صعبًا، ولديك بعض الخيارات:
- إذا كان مستوى تداخل الفئات معروفًا أو يمكنك العثور على حد أعلى:قم ببناء SELECT ذو مظهر فظيع مع الكثير من عمليات JOIN.هذا سريع ولكنه قبيح وتحتاج إلى وضع حد لمستويات التسلسل الهرمي.
- إذا كان لديك عدد صغير نسبيًا من إجمالي الفئات، فاستفسر عنها جميعًا (فقط المعرفات والآباء)، واجمع معرفات الأشخاص الذين تهتم بهم، وقم بإجراء SELECT....IN للمنتجات.وكان هذا هو الخيار المناسب بالنسبة لي.
- الاستعلام لأعلى/لأسفل التسلسل الهرمي باستخدام سلسلة من التحديدات.بسيطة، ولكنها بطيئة نسبيا.
- أعتقد أن الإصدارات الأخيرة من SQLServer لديها بعض الدعم للاستعلامات العودية، ولكنني لم أستخدمها بنفسي.
يمكن أن تساعدك الإجراءات المخزنة إذا كنت لا ترغب في القيام بهذا الجانب من التطبيق.
وماذا كنت تريد أن تجد لإغلاق انتقال من فئة "الأم" العلاقة. أعتقد ليس هناك قيود على عمق فئة التسلسل الهرمي، بحيث لا يمكنك وضع استعلام SQL واحد والذي يجد كل الفئات. ما أود القيام به (في شبة الكود) هو هذا:
categoriesSet = empty set
while new.size > 0:
new = select * from categories where parent in categoriesSet
categoriesSet = categoriesSet+new
وهكذا تبقي فقط على الاستعلام عن الأطفال حتى يتم العثور على أي أكثر من ذلك. هذا يتصرف بشكل جيد من حيث السرعة لم يكن لديك التسلسل الهرمي تدهورت (ويقول، 1000 فئات، لكل طفل آخر)، أو عدد كبير من مجموع الفئات. في الحالة الثانية، هل يمكن أن تعمل دائما مع الجداول المؤقتة للحفاظ على نقل البيانات بين التطبيق وقاعدة بيانات صغيرة.
وربما شيء من هذا القبيل:
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)
[تحرير] إذا كان عمق فئة أكبر من واحد هذا من شأنه أن يشكل الاستعلام الأعمق الخاص بك. وأظن أنك يمكن أن تصميم الإجراء المخزن التي من شأنها أن انتقل لأسفل في الجدول حتى معرفات إرجاعها بواسطة الاستعلام الداخلي لم يكن لديهم أطفال - ربما الأفضل أن يكون سمة أن يمثل فئة عقدة طرفية في التسلسل الهرمي - ثم تنفيذ الاستعلام الخارجي على هذه المعرفات.
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 بسيط من ص
وهذا يجب أن لعنة أسفل كل فئات "الطفل" بدءا من فئة معينة.
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
أحب استخدام جدول مؤقت مكدس للبيانات الهرمية.هنا مثال تقريبي -
-- 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
وهناك بعض الأساليب في الكتاب الذي كنت مرتبطة التي يجب أن تغطي وضعك بشكل جيد.