منبع اصلی نوشتار زیر در این لینک قرار دارد

MySQL , Menu قسمت دوم

این پست رو برای اضافه کردن یه سری قابلیت جدید و رفع یه باگ ویرایش کردم. :D

قبل از شروع بگم اصل این ایده رو یکی از دوستام داد، اونم از وب خونده بود. منتها من هیچ چیز مدوّنی از این مطلب نتونستم پیدا کنم هنوز (نگشتم اگه راستشو بخواید) خواستم پیشاپیش بگم، چون حقیقتا به این مساله اهمیت میدم، احترام به حق دیگران.
توی پست قبل، روشی رو بررسی کردم که توی اون روش، از یه سری کوئری ساده استفاده میشد که یه جور منو از دیتابیس بارگذاری بشه (منظور نمایش نیست، فقط بارگذاری منو از دیتابیس به آرایه ، از MySQL به PHP ) ولی مشکل اساسی این بود که توی اون روش، زیادی قابلیت انعطافی وجود نداشت، درجه ها هم نمیشد از یه حدی بیشتر باشه و برای درجه های بالاتر باید کوئری تغییر میکرد که اینکار مستلزم زمان و کد بیشتره.

اما این دفعه روش طور دیگه ای هست. به جای روش قبلی، منو رو به این صورت در نظر بگیرید. (چیزی تغییر نکرده، فقط طرز نمایش عوض شده و به جای یه نمودار، یه سری آیتم تو در تو وجود داره)

\"menu\"

دید جدید منو


جدولی که این دفعه قراره ساخته بشه، ساختار یه کم متفاوتی داره، به این صورت :

CREATE TABLE IF NOT EXISTS `adv_menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(60) NOT NULL,
  `data` varchar(255) NOT NULL,
  `lside` int(11) NOT NULL,
  `rside` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;

INSERT INTO `adv_menu` (`id`, `name`, `data`, `lside`, `rside`) VALUES
(1, \'Root\', \'Root Data\', 1, 24),
(2, \'Home\', \'Home Data\', 2, 3),
(3, \'Pages\', \'Pages Data\', 4, 13),
(4, \'Articles\', \'Articles Data\', 14, 23),
(5, \'PHP\', \'PHP Data\', 5, 10),
(6, \'Delphi\', \'Delphi Data\', 11, 12),
(7, \'SQL\', \'SQL Data\', 15, 16),
(8, \'Ajax\', \'Ajax Data\', 17, 22),
(9, \'Part2\', \'Part2 Data\', 18, 19),
(10, \'Part1\', \'Part1 Data\', 20, 21),
(11, \'OS\', \'OS Data\', 6, 7),
(12, \'GTrans\', \'GTrans Data\', 8, 9);


اگه دقت کنید میبینید که اینبار به جای یه منوی پدر دو تا شماره به عنوان چپ و راست در نظر گرفته شده. این شماره ها چی هستن؟ ببینید :

\"menu

منو و اعداد چپ و راست


یعنی اولین و آخرین شماره مربوط هستن به آیتم ریشه و باقی هم اگه دقت کنید خیلی ساده از چپ به راست شماره خوردن.(یه شماره سمت راست هر آیتمه rside و یه شماره هم سمت چپ خورده به اسم lside ) این روش شماره گذاری مزایای زیادی داره که سعی میکنم اونا رو نشون بدم. اول از همه اینکه چطوری میتونید کل آیتمها رو به ترتیب انتخاب کنید (فعلا فقط آیتمها رو همینطوری بدون هیچگونه درجه بندی ولی به ترتیب )

SELECT item.name,item.data
FROM adv_menu AS item, adv_menu AS parent
WHERE item.lside
BETWEEN parent.lside
AND parent.rside
AND parent.id =1
ORDER BY item.lside;
+----------+---------------+
| name     | data          |
+----------+---------------+
| Root     | Root Data     |
| Home     | Home Data     |
| Pages    | Pages Data    |
| PHP      | PHP Data      |
| OS       | OS Data       |
| GTrans   | GTrans Data   |
| Delphi   | Delphi Data   |
| Articles | Articles Data |
| SQL      | SQL Data      |
| Ajax     | Ajax Data     |
| Part2    | Part2 Data    |
| Part1    | Part1 Data    |
+----------+---------------+
12 rows in set (0.00 sec)

این اطلاعات بدون درجه بندی بدرد نمیخورن. میخوایم «عمق» هر آیتم هم مشخص بشه:

SELECT item.name,item.data,(COUNT(parent.name) - 1) AS depth
FROM adv_menu AS item, adv_menu AS parent
WHERE item.lside
BETWEEN parent.lside
AND parent.rside
GROUP BY item.name
ORDER BY item.lside;
+----------+---------------+-------+
| name     | data          | depth |
+----------+---------------+-------+
| Root     | Root Data     |     0 |
| Home     | Home Data     |     1 |
| Pages    | Pages Data    |     1 |
| PHP      | PHP Data      |     2 |
| OS       | OS Data       |     3 |
| GTrans   | GTrans Data   |     3 |
| Delphi   | Delphi Data   |     2 |
| Articles | Articles Data |     1 |
| SQL      | SQL Data      |     2 |
| Ajax     | Ajax Data     |     2 |
| Part2    | Part2 Data    |     3 |
| Part1    | Part1 Data    |     3 |
+----------+---------------+-------+
12 rows in set (0.00 sec)

اینجوری نتیجه جالبتره. یعنی خیلی ساده میتونید اونو به آرایه تبدیل کنید، البته با خود کوئری هم میشه کارهایی انجام داد مثلا :

SELECT CONCAT( REPEAT(\'-\', COUNT(parent.name) - 1), item.name) AS name,item.data
FROM adv_menu AS item, adv_menu AS parent
WHERE item.lside
BETWEEN parent.lside
AND parent.rside
GROUP BY item.name
ORDER BY item.lside;
+-----------+---------------+
| name      | data          |
+-----------+---------------+
| Root      | Root Data     |
| -Home     | Home Data     |
| -Pages    | Pages Data    |
| --PHP     | PHP Data      |
| ---OS     | OS Data       |
| ---GTrans | GTrans Data   |
| --Delphi  | Delphi Data   |
| -Articles | Articles Data |
| --SQL     | SQL Data      |
| --Ajax    | Ajax Data     |
| ---Part2  | Part2 Data    |
| ---Part1  | Part1 Data    |
+-----------+---------------+
12 rows in set (0.00 sec)

نتیجه میتونه کمک کنه برای جاهایی که منوهای ساده میخوایم، مثلا برای یه چیزی مثل منوهای بالای هر صفحه تو ویکی. خوب، شاید مشکل اساسی شما این باشه که اگه بخوایم یه آیتم به این مجموعه اضافه کنیم چکار کنیم؟ مثلا یه آیتم به منوی SQL با محتوای Test. برخلاف دفعه قبلی که خیلی راحت میشد با یه کوئری INSERT ساده یه آیتم جدید به دیتابیس اضافه کرد اینبار باید علاوه بر اون یه کوئری هم آیتمهای بعدی رو به روز کنه. یعنی اگه ما بخوایم یه آیتم به زیر منوی SQL اضافه کنیم اینطوری باید کار انجام بشه (همه قدمها پشت سر هم اورده میشن، به روشی که بشه تو کنسول نوشتشون ولی برای PHP یه کم تغییر لازمه) :
یک باگ اینجا کشفیده شد توسط خودم، که حلش کردم این کد درسته :دی

SET @pos= (SELECT rside FROM adv_menu WHERE name=\'SQL\');
UPDATE adv_menu  SET lside=lside+2 WHERE lside>[email protected];
UPDATE adv_menu  SET rside=rside+2 WHERE rside>[email protected];
INSERT INTO adv_menu (name,data,lside,rside) VALUES (\'Test\',\'Test Data\',@pos,@pos+1);

و این هم همین کد برایPHP

<?php
	$result=mysql_query("SELECT rside FROM adv_menu WHERE name=\'SQL\'");
	$pos=mysql_fetch_assoc($result);
	$p=$pos[\'rside\'];
	mysql_query("UPDATE adv_menu  SET  lside=lside+2 WHERE lside>=$p");
	mysql_query("UPDATE adv_menu  SET  rside=rside+2 WHERE rside>=$p");
	mysql_query("INSERT INTO adv_menu (name,data,lside,rside) VALUES (\'Test\',\'Test Data\',$p,".($p+1).")");
?>

این یعنی اینکه برای اضافه کردن یک آیتم جدید، نیاز داریم به دونستن اینکه اون آیتم قراره توی کدوم آیتم دیگه قرار بگیره، تو این مثال ما میخواستیم آخر باشه ولی خوب راههای دیگه ای هم هست اگه بخوایم اول بیفته یا … به هر صورت اول باید شماره مربوط به جایی که این آیتم قراره قبلترش ایجاد بشه پیدا کنیم، با کمک همون کوئری اولی (که توی PHP به جای اینکار میتونیم خیلی ساده از یه متغیر PHP استفاده کنیم) بعد با یه UPDATE ساده همه منوهای بعدی رو دو تا شیفت میدیم به جلو و بعد هم در گام آخر، منو رو توی جای خالی به وجود اومده میذاریم. برای حذف هم دقیقا همین کارو برعکس انجام میدیم.
این باگ اینجا هم بود :دی

SET @pos=(SELECT rside FROM adv_menu WHERE name=\'Test\');
DELETE FROM adv_menu WHERE [email protected];
UPDATE adv_menu SET  lside=lside-2 WHERE lside > @pos;
UPDATE adv_menu SET rside=rside-2 WHERE rside > @pos;

تو این روش، هزینه اضافه و کم کردن منو بیشتره. ولی وقت نمایش منو کار سادست. و اگه توجه کنید اضافه کردن و کم کردن به منو کاری نیست که بخواد هر ثانیه یه بار انجام بشه ولی نمایش ممکنه بارها و بارها در یه ثانیه (حتی یه ثانیه) اتفاق بیفته. علاوه بر اون تعداد درجه های تو در تو نامحدوده. نه مثل قبل و اصلا نیازی به تغییر کوئری نداره.

امروز داشتم مطالعم میکردم که برخوردم به GROUP_CONCAT دیدم که برای این مساله خیلی میتونه مفید باشه، البته یه کم محدودیت میاره ببینید :

SELECT item.name,item.data,
(COUNT(parent.name) - 1) AS depth,
GROUP_CONCAT(parent.name ORDER BY parent.lside ASC SEPARATOR \'|\') as path
FROM adv_menu AS item, adv_menu AS parent
WHERE item.lside BETWEEN parent.lside AND parent.rside
GROUP BY item.name
ORDER BY item.lside;

و نتیجه :

+----------+---------------+-------+--------------------------+
| name     | data          | depth | path                     |
+----------+---------------+-------+--------------------------+
| Root     | Root Data     |     0 | Root                     |
| Home     | Home Data     |     1 | Root|Home                |
| Pages    | Pages Data    |     1 | Root|Pages               |
| PHP      | PHP Data      |     2 | Root|Pages|PHP           |
| OS       | OS Data       |     3 | Root|Pages|PHP|OS        |
| GTrans   | GTrans Data   |     3 | Root|Pages|PHP|GTrans    |
| Delphi   | Delphi Data   |     2 | Root|Pages|Delphi        |
| Articles | Articles Data |     1 | Root|Articles            |
| SQL      | SQL Data      |     2 | Root|Articles|SQL        |
| Ajax     | Ajax Data     |     2 | Root|Articles|Ajax       |
| Part2    | Part2 Data    |     3 | Root|Articles|Ajax|Part2 |
| Part1    | Part1 Data    |     3 | Root|Articles|Ajax|Part1 |
+----------+---------------+-------+--------------------------+

همونطور که میبینید مسیر هر منو هم کاملا قابل دسترسه به یه جداساز که تو این مثال کاراکتر “|” هست. این خیلی میتونه کمک کنه ولی باید یادتون باشه این تابع محدودیت داره (پیشفرضش ۱۰۲۴ کاراکتر) که اگه مسیر بیشتر بشه بدون هیچ اخطاری بریده میشه. به هر حال اینم واسه خودش راهیه :))

پستهای مرتبط :

  1. Mysql Menu قسمت سوم چند وقت پیش در باره منو و طریقه ایجاد آن...
  2. Menu، MySQL و کلا هر چی با M شروع میشه اولین بار،‌برای ایجاد کردن یک منوی تو در تو،‌ از...
  3. باز هم آژاکس – قسمت سوم توی دو پست قبلی توضیح دادم که چطوری میشه بدون...



برچسب ها : , ,