Дмитрий Сергеевич (axshavan) wrote,
Дмитрий Сергеевич
axshavan

Про SQL и виртуальные URL

Прежде всего, я хотел бы чуть-чуть прояснить терминологию. Я в ней не очень разбираюсь, если честно, поэтому надо уточнить, что именно я называю виртуальными URL здесь.

Вот представьте себе некий сайт www.foo.local и какую-то страницу не нём, скажем, www.foo.local/bar/archive/123 - но если посмотреть на то, как выглядит сайт на сервере, никаких папок bar и archive там и в помине нет. Вот это я и называю (в этой статье) виртуальным URL - когда сервер отдаёт какой-то контент по какому-то URL, но на самом деле такого URL не существует. Обычно это реализуется с помощью подключенного к веб-серверу Apache модулю mod_rewrite, который, руководствуясь набором правил из конфигурационного файла, обращается к тому или иному файлу в зависимости от URL. Однако, я встречал и более хитрый (читай: через жопу) способ, когда подобный реврайт сделан с помощью директивы ErrorDocument 404 в конфиге Apache. Эта директива указывает на какой-то файл, который является роутером и выдаёт тот или иной контент в зависимости от запрошенного несуществующего URL.

Так вот, одним из способов хранения виртуальных URL (наверное, самый популярный способ) - это хранить их в базе данных в виде графа. Вот так:
+----+-----------+---------+
| id | parent_id | name    |
+----+-----------+---------+
| 1  | 0         |         |
+----+-----------+---------+
| 2  | 1         | bar     |
+----+-----------+---------+
| 3  | 2         | archive |
+----+-----------+---------+
| 4  | 3         | 123     |
+----+-----------+---------+
Для того, чтоб однозначно определить необходимую точку в графе, можно в цикле делать несколько запросов к базе.

$res = mysql_query("select * from table where parent_id=0");
$row = mysql_fetch_assoc($res);
$res = mysql_query("select * from table where parent_id = ".$row['id']." and name = 'bar'");
$row = mysql_fetch_assoc($res);
$res = mysql_query("select * from table where parent_id = ".$row['id']." and name = 'archive'");
...


Но это не comme il faut. Настоящие джедаи всё выковыривают одним запросом, который собирают в цикле, джойня таблицу с виртуальным деревом саму к себе несколько раз:

select t0.*, t1.*, ... from table t0 join table t1 on (t0.id = t1.parent_id and t1.name = 'bar') join table t2 on (t1.id = t2.parent_id and t2.name = 'archive') ... where t0.parent_id = 0

Всё круто. Но вот одна проблема - если у нас в каком-то месте графа может быть просто дохерища ветвей, исходящих из одной точки, как тут быть? Например, если у нас есть несколько тысяч статей в папке archive, а для каждой лень делать соответствующую запись? Мисата acidjazz в первой версии своего движка Бало придумал оригинальную штуку - если имя равно звёздочке, то это может быть любое имя. То есть кусочек джойна в том длинном запросе должен выглядеть так: ... join table tN on (tN.id = tN-1.parent_id and (tN.name = 'name' or tN.name = '*')) ...

Единственный минус - на одном уровне не могут находиться папки с именем-звёздочкой и именем-незвёздочкой, потому что может быть неопределённость в том, какую папку вынимать (там же OR).
+----+-----------+--------+
| id | parent_id | name   |
+----+-----------+--------+
| 6  | 0         | test   |
+----+-----------+--------+
| 7  | 0         | *      |
+----+-----------+--------+
| 8  | 6         | submit |
+----+-----------+--------+
| 9  | 7         | submit |
+----+-----------+--------+
При поиске подходящей записи при обращении к www.foo.local/test/submit будут найдены сразу две записи, удовлетворяющие данному URL. Блин, я много лет думал над тем, как решить проблему с этой коллизией, и только недавно придумал - приписывать ... ORDER BY tN.name DESC (по-моему, всё-таки DESC) ORDER BY tN+1.name DESC ORDER BY tN+2.name DESC ... в конце того большого запроса, чтоб папки с именем, отличным от звёздочки, были первее. И брать потом первую строку из подходящего набора.

Наверное, я сказал всем известную истину. Тем не менее, я чувствую себя, как Архимед, который сел в наполненную до краёв ванну с водой и понял то, что все прекрасно и без него понимали - если налить ванную до краёв и сесть туда, то часть воды выплеснется.

P.S. Этимология foo bar изложена в RFC 3092.
Tags: web
Subscribe

  • Настроение

    Выпил граммов двадцать крепкого спиртного для общего расслабления нервной системы. Потом поиграл на гитаре, напевая самым противным голосом, какой…

  • Размышления про материалы

    Восседая на троне в специальной комнате, я призадумался о свойствах туалетной бумаги и о том, чем можно её заменить. Очень часто бывает, знаете, что…

  • Dracula

    Вчера вечером наконец-то дочитал до конца «Дракулу» на английском. Правильнее будет сказать «продрался до конца». Чтоб я да ещё да какую-нибудь…

  • Post a new comment

    Error

    Comments allowed for friends only

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 3 comments