Vitaphoto: Облака тегов — различия между версиями
(→SQL) |
(Массовая правка: замена Категория:Разработка на Категория:Архив, замена Category:Разработка на Категория:Архив) |
||
(не показано 17 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
'''[[rupedia:Облако тегов|Облака тегов]]''' — набор ссылок, расположенных кластерно или хаотически с размером каждой ссылки, зависящим от её «важности». Конкретнее, в случае «тегов» (меток), каждая ссылка ведёт на все элементы, имеющие данный тег, а размер ссылки зависит от количества таких элементов. Таким образом, наиболее популярные метки оказываются крупными, а непопулярные — мелкими. | '''[[rupedia:Облако тегов|Облака тегов]]''' — набор ссылок, расположенных кластерно или хаотически с размером каждой ссылки, зависящим от её «важности». Конкретнее, в случае «тегов» (меток), каждая ссылка ведёт на все элементы, имеющие данный тег, а размер ссылки зависит от количества таких элементов. Таким образом, наиболее популярные метки оказываются крупными, а непопулярные — мелкими. | ||
− | Однако, классические, «тупые» облака тегов — обычно свалка в духе «фигпоймёшь», что и где, особенно, если тегов много. (в одном моём [[Vitaphoto]] их больше 250…) Но сама-то идея — замечательная! И в [[Vitaphoto]] хочется заменить облаками тегов навигацию по альбомам. | + | Однако, классические, «тупые» облака тегов — обычно свалка в духе «фигпоймёшь», что и где, особенно, если тегов много. (в одном моём [[Vitaphoto]] их больше 250…) Но сама-то идея — замечательная! И в [[Vitaphoto]] хочется заменить облаками тегов навигацию по альбомам. |
− | + | Как же это сделать? Как оптимизировать облака тегов для удобства навигации? Какие теги исключить из облака? | |
− | + | == Идеи == | |
− | + | '''Идея 1''': нужно показывать облако только из тегов, встречающихся хотя бы один раз в просматриваемом множестве фотографий (коим может быть, например, множество фотографий, имеющих заданный набор тегов). | |
− | + | '''Идея 2''': часто бывает, что какие-то теги являются «подтегами» других. Например, все фотографии с людьми помечены тегом ''«люди»'', а некоторые из них в то же время помечены именем конкретного человека. Другой пример: все фотографии с какой-нибудь тусовки-выезда помечены тегом ''«Тусовка-выезд 2009»'', при этом у поездки были «подмероприятия», например, ''«Экскурсия на Фиолетовые Холмы»'', и соответствующие подмножества фотографий помечены, кроме тега ''«Тусовка-выезд 2009»'', тегами подмероприятия. | |
− | А теперь вопрос: как | + | «Подтеги» каких-либо из отображаемых тегов отображать не нужно, так как они скорее всего имеют смысл только в контексте своего родителя. |
+ | |||
+ | Правда, возможна ситуация, когда два тега A и B являются «подтегами» друг друга, то есть — встречаются на фотографиях исключительно вместе. Возникает, конечно, вопрос, нафига ж их тогда действительно два, а не один? Вообще-то с моей точки зрения эта ситуация находится за гранью разумного. Встречаться она, скорее всего, будет, но редко и в большинстве случаев — по ошибке или совпадению (что-то вроде: ''«ну да, ну получилось, что из фотографий людей в галерее только фотографии моей девушки… ну ревнивая она…»''). | ||
+ | |||
+ | Так что в ситуации «тождественности» тегов A и B в облаке их нужно отображать оба. | ||
+ | |||
+ | '''Идея 3''': разумно разбить одно большое облако на несколько меньших по различным диапазонам популярности тегов — скорее всего, трём: одно облако очень популярных тегов, второе — средних по популярности, третье — совсем разовых. Отображать по умолчанию, естественно, первое. | ||
+ | |||
+ | '''Идея 4''': все предыдущие идеи относились к облакам, сопутствующим каждому множеству фотографий и отображаемых на странице наряду с другими элементами управления. Однако можно создать отдельную страницу с большим облаком, содержащим '''все теги''' системы вообще — чтобы иметь «общий вид». | ||
+ | |||
+ | '''Идея 5''': некоторые теги, как уже было сказано выше, с большой вероятностью отражают название какого-нибудь действа, будь то SECR, день рождения Пифа или поездка в Буревестник производства года этак 2007-го. Важно, что эти теги — теги некой отдельной категории. Вот и задача: как бы выделить их в отдельную категорию? Подумаем: по сути, их основное свойство в том, что они ставятся единожды на некоторое множество фотографий, снятых, скорее всего, за относительно небольшой промежуток времени, и после этого становятся «неживыми» — фотографии в них больше не добавляются. Формально выразить это проще всего через разброс дат фотографий, имеющих заданный тег — предположим, фотографий в теге должно быть минимум 10, а разброс дат должен быть меньше месяца. | ||
+ | |||
+ | == SQL для реализации идеи № 2 == | ||
+ | |||
+ | А теперь вопрос: как реализовать средствами MySQL идею № 2? :) | ||
Есть таблица-отношение <tt>'''tag_image'''</tt> с двумя полями «ID тега» (<tt>'''ti_tag'''</tt>) и «ID изображения» (<tt>'''ti_image'''</tt>). Каждая строчка означает, что фотография с соответствующим ID имеет соответствующий тег. | Есть таблица-отношение <tt>'''tag_image'''</tt> с двумя полями «ID тега» (<tt>'''ti_tag'''</tt>) и «ID изображения» (<tt>'''ti_image'''</tt>). Каждая строчка означает, что фотография с соответствующим ID имеет соответствующий тег. | ||
− | Так вот, чтобы отвечать на наш вопрос, сначала нужно создать таблицу, содержащую сочетания тегов (A, B), если существует хотя бы одна фотография, имеющая теги A и B, и сочетание (A, 0), если существует фотография, имеющая только тег A и ни одного тега | + | Так вот, чтобы отвечать на наш вопрос, сначала нужно создать таблицу <tt>'''tag_pair'''</tt>, содержащую сочетания тегов (A, B), если существует хотя бы одна фотография, имеющая теги A и B, и сочетание (A, 0), если существует фотография, имеющая только тег A и ни одного тега кроме него: |
<code-sql>CREATE TABLE tag_pair AS | <code-sql>CREATE TABLE tag_pair AS | ||
Строка 40: | Строка 54: | ||
<code-sql>SELECT DISTINCT t0.tp_tag1 FROM tag_pair t0 | <code-sql>SELECT DISTINCT t0.tp_tag1 FROM tag_pair t0 | ||
− | LEFT JOIN tag_image t1 ON tp_tag2!=0 AND t1.ti_tag=tp_tag1 | + | LEFT JOIN tag_image t1 ON t0.tp_tag2!=0 AND t1.ti_tag=tp_tag1 |
− | LEFT JOIN tag_image t2 ON tp_tag2!=0 AND t2.ti_image=t1.ti_image AND t2.ti_tag=tp_tag2 | + | LEFT JOIN tag_image t2 ON t0.tp_tag2!=0 AND t2.ti_image=t1.ti_image AND t2.ti_tag=tp_tag2 |
− | WHERE tp_tag2=0 | + | WHERE t0.tp_tag2=0 |
OR t2.ti_tag IS NULL | OR t2.ti_tag IS NULL | ||
OR NOT EXISTS (SELECT * FROM tag_pair t3 | OR NOT EXISTS (SELECT * FROM tag_pair t3 | ||
WHERE t3.tp_tag1=t0.tp_tag1 AND t3.tp_tag2!=t0.tp_tag2 | WHERE t3.tp_tag1=t0.tp_tag1 AND t3.tp_tag2!=t0.tp_tag2 | ||
OR t3.tp_tag1=t0.tp_tag2 AND t3.tp_tag2!=t0.tp_tag1)</code-sql> | OR t3.tp_tag1=t0.tp_tag2 AND t3.tp_tag2!=t0.tp_tag1)</code-sql> | ||
+ | |||
+ | Соответственно, чтобы ограничить отдаваемое множество набором тегов, встречающихся вместе с определённым тегом (например, с ID=123), нужно просто добавить ещё одно соединение с таблицей <tt>'''tag_pair'''</tt>: | ||
+ | |||
+ | <code-sql>SELECT DISTINCT t0.tp_tag1 FROM tag_pair t0 | ||
+ | JOIN tag_pair t_123 ON t_123.tp_tag1=123 AND t_123.tp_tag2=t0.tp_tag1 | ||
+ | LEFT JOIN tag_image t1 … и так далее</code-sql> | ||
== 3D-облака тегов == | == 3D-облака тегов == | ||
− | Для [http://www.wordpress.org/ WordPress] существует плагин, отображающий в виде Flash-вставки трёхмерное вращающееся облако тегов. | + | Для [http://www.wordpress.org/ WordPress] существует плагин [http://wordpress.org/extend/plugins/wp-cumulus/ WP-Cumulus], отображающий в виде Flash-вставки трёхмерное вращающееся облако тегов. Flash-вставку можно утащить и приспособить для отображения облаков тегов в [[Vitaphoto]]. :-) |
Пример: | Пример: | ||
Строка 63: | Строка 83: | ||
</html> | </html> | ||
− | [[Категория: | + | == Выборка по тегам == |
+ | |||
+ | Между прочим, следует задать и ещё один вопрос, кажущийся на первый взгляд тривиальным: а как вообще выбирать из базы фотографии, имеющие заданный набор тегов? | ||
+ | |||
+ | Можно выделить два способа — первый выполняется быстрее на относительно небольших (меньше 8 тегов) наборах, второй лучше масштабируется относительно количества тегов в наборе. | ||
+ | |||
+ | === По JOIN’у на тег === | ||
+ | |||
+ | Добавляется по соединению на каждый тег набора, а так как все соединения — внутренние, они и фильтруют всё множество фотографий: | ||
+ | |||
+ | <code-sql>SELECT i1.* FROM image i1 | ||
+ | JOIN tag_image ti1 ON ti1.ti_image=i1.img_id AND ti1.ti_tag=132 | ||
+ | JOIN tag_image ti2 ON ti2.ti_image=i1.img_id AND ti2.ti_tag=113 | ||
+ | JOIN tag_image ti3 ON ti3.ti_image=i1.img_id AND ti3.ti_tag=115 | ||
+ | …и так далее…</code-sql> | ||
+ | |||
+ | === Через группировку === | ||
+ | |||
+ | <code-sql>SELECT i2.* FROM image i2, tag_image ti2 | ||
+ | WHERE ti2.ti_image=i2.img_id AND ti2.ti_tag IN (132, 113, 115) # набор тегов | ||
+ | GROUP BY i2.img_id | ||
+ | HAVING COUNT(i2.img_id)=3 # количество тегов в наборе</code-sql> | ||
+ | |||
+ | [[Категория:Архив]] | ||
+ | [[Категория:Sway]] |
Текущая версия на 15:43, 20 июня 2016
Облака тегов — набор ссылок, расположенных кластерно или хаотически с размером каждой ссылки, зависящим от её «важности». Конкретнее, в случае «тегов» (меток), каждая ссылка ведёт на все элементы, имеющие данный тег, а размер ссылки зависит от количества таких элементов. Таким образом, наиболее популярные метки оказываются крупными, а непопулярные — мелкими.
Однако, классические, «тупые» облака тегов — обычно свалка в духе «фигпоймёшь», что и где, особенно, если тегов много. (в одном моём Vitaphoto их больше 250…) Но сама-то идея — замечательная! И в Vitaphoto хочется заменить облаками тегов навигацию по альбомам.
Как же это сделать? Как оптимизировать облака тегов для удобства навигации? Какие теги исключить из облака?
Содержание
Идеи
Идея 1: нужно показывать облако только из тегов, встречающихся хотя бы один раз в просматриваемом множестве фотографий (коим может быть, например, множество фотографий, имеющих заданный набор тегов).
Идея 2: часто бывает, что какие-то теги являются «подтегами» других. Например, все фотографии с людьми помечены тегом «люди», а некоторые из них в то же время помечены именем конкретного человека. Другой пример: все фотографии с какой-нибудь тусовки-выезда помечены тегом «Тусовка-выезд 2009», при этом у поездки были «подмероприятия», например, «Экскурсия на Фиолетовые Холмы», и соответствующие подмножества фотографий помечены, кроме тега «Тусовка-выезд 2009», тегами подмероприятия.
«Подтеги» каких-либо из отображаемых тегов отображать не нужно, так как они скорее всего имеют смысл только в контексте своего родителя.
Правда, возможна ситуация, когда два тега A и B являются «подтегами» друг друга, то есть — встречаются на фотографиях исключительно вместе. Возникает, конечно, вопрос, нафига ж их тогда действительно два, а не один? Вообще-то с моей точки зрения эта ситуация находится за гранью разумного. Встречаться она, скорее всего, будет, но редко и в большинстве случаев — по ошибке или совпадению (что-то вроде: «ну да, ну получилось, что из фотографий людей в галерее только фотографии моей девушки… ну ревнивая она…»).
Так что в ситуации «тождественности» тегов A и B в облаке их нужно отображать оба.
Идея 3: разумно разбить одно большое облако на несколько меньших по различным диапазонам популярности тегов — скорее всего, трём: одно облако очень популярных тегов, второе — средних по популярности, третье — совсем разовых. Отображать по умолчанию, естественно, первое.
Идея 4: все предыдущие идеи относились к облакам, сопутствующим каждому множеству фотографий и отображаемых на странице наряду с другими элементами управления. Однако можно создать отдельную страницу с большим облаком, содержащим все теги системы вообще — чтобы иметь «общий вид».
Идея 5: некоторые теги, как уже было сказано выше, с большой вероятностью отражают название какого-нибудь действа, будь то SECR, день рождения Пифа или поездка в Буревестник производства года этак 2007-го. Важно, что эти теги — теги некой отдельной категории. Вот и задача: как бы выделить их в отдельную категорию? Подумаем: по сути, их основное свойство в том, что они ставятся единожды на некоторое множество фотографий, снятых, скорее всего, за относительно небольшой промежуток времени, и после этого становятся «неживыми» — фотографии в них больше не добавляются. Формально выразить это проще всего через разброс дат фотографий, имеющих заданный тег — предположим, фотографий в теге должно быть минимум 10, а разброс дат должен быть меньше месяца.
SQL для реализации идеи № 2
А теперь вопрос: как реализовать средствами MySQL идею № 2? :)
Есть таблица-отношение tag_image с двумя полями «ID тега» (ti_tag) и «ID изображения» (ti_image). Каждая строчка означает, что фотография с соответствующим ID имеет соответствующий тег.
Так вот, чтобы отвечать на наш вопрос, сначала нужно создать таблицу tag_pair, содержащую сочетания тегов (A, B), если существует хотя бы одна фотография, имеющая теги A и B, и сочетание (A, 0), если существует фотография, имеющая только тег A и ни одного тега кроме него:
CREATE TABLE tag_pair AS SELECT DISTINCT t1.ti_tag tp_tag1, IFNULL(t2.ti_tag,0) tp_tag2 FROM tag_image t1 LEFT JOIN tag_image t2 ON t2.ti_image=t1.ti_image AND t2.ti_tag!=t1.ti_tag
А дальше начинается полёт мысли:
«Выбрать все теги A из пар (A, B), для которых существует изображение, имеющее A, но не имеющее B; а также все теги A из пар (A, B), для которых не существует ни одной пары (A, C) или (B, C), где C ≠ A и C ≠ B.»
SELECT DISTINCT tp_tag1 FROM tag_pair LEFT JOIN tag_image t1 ON tp_tag2!=0 AND t1.ti_tag=tp_tag1 LEFT JOIN tag_image t2 ON tp_tag2!=0 AND t2.ti_image=t1.ti_image AND t2.ti_tag=tp_tag2 WHERE t2.ti_tag IS NULL UNION SELECT DISTINCT t1.tp_tag1 FROM tag_pair t1 LEFT JOIN tag_pair t2 ON t2.tp_tag1=t1.tp_tag1 AND t2.tp_tag2!=t1.tp_tag2 LEFT JOIN tag_pair t3 ON t3.tp_tag1=t1.tp_tag2 AND t2.tp_tag2!=t1.tp_tag1 WHERE t2.tp_tag1 IS NULL AND t3.tp_tag1 IS NULL
При использовании MySQL здесь мы наталкиваемся на феномен выполнения UNION — каждый запрос по отдельности выполняется меньше сотых долей секунды, а объединённый запрос — почти полсекунды. Поэтому представляем оптимизированный вариант:
SELECT DISTINCT t0.tp_tag1 FROM tag_pair t0 LEFT JOIN tag_image t1 ON t0.tp_tag2!=0 AND t1.ti_tag=tp_tag1 LEFT JOIN tag_image t2 ON t0.tp_tag2!=0 AND t2.ti_image=t1.ti_image AND t2.ti_tag=tp_tag2 WHERE t0.tp_tag2=0 OR t2.ti_tag IS NULL OR NOT EXISTS (SELECT * FROM tag_pair t3 WHERE t3.tp_tag1=t0.tp_tag1 AND t3.tp_tag2!=t0.tp_tag2 OR t3.tp_tag1=t0.tp_tag2 AND t3.tp_tag2!=t0.tp_tag1)
Соответственно, чтобы ограничить отдаваемое множество набором тегов, встречающихся вместе с определённым тегом (например, с ID=123), нужно просто добавить ещё одно соединение с таблицей tag_pair:
SELECT DISTINCT t0.tp_tag1 FROM tag_pair t0 JOIN tag_pair t_123 ON t_123.tp_tag1=123 AND t_123.tp_tag2=t0.tp_tag1 LEFT JOIN tag_image t1 … и так далее
3D-облака тегов
Для WordPress существует плагин WP-Cumulus, отображающий в виде Flash-вставки трёхмерное вращающееся облако тегов. Flash-вставку можно утащить и приспособить для отображения облаков тегов в Vitaphoto. :-)
Пример:
Выборка по тегам
Между прочим, следует задать и ещё один вопрос, кажущийся на первый взгляд тривиальным: а как вообще выбирать из базы фотографии, имеющие заданный набор тегов?
Можно выделить два способа — первый выполняется быстрее на относительно небольших (меньше 8 тегов) наборах, второй лучше масштабируется относительно количества тегов в наборе.
По JOIN’у на тег
Добавляется по соединению на каждый тег набора, а так как все соединения — внутренние, они и фильтруют всё множество фотографий:
SELECT i1.* FROM image i1 JOIN tag_image ti1 ON ti1.ti_image=i1.img_id AND ti1.ti_tag=132 JOIN tag_image ti2 ON ti2.ti_image=i1.img_id AND ti2.ti_tag=113 JOIN tag_image ti3 ON ti3.ti_image=i1.img_id AND ti3.ti_tag=115 …и так далее…
Через группировку
SELECT i2.* FROM image i2, tag_image ti2 WHERE ti2.ti_image=i2.img_id AND ti2.ti_tag IN (132, 113, 115) # набор тегов GROUP BY i2.img_id HAVING COUNT(i2.img_id)=3 # количество тегов в наборе