Cake-PHP.ru
Форум программистов CakePHP
(на сайт)
Watched Topics
FAQ
Поиск
Пользователи
Группы
Регистрация
Профиль
Войти и проверить личные сообщения
Вход
Список форумов Cake-PHP.ru
->
Общий
Ответить
Имя
Тема
Сообщение
Смайлики
Дополнительные смайлики
Цвет шрифта:
По умолчанию
Тёмно-красный
Красный
Оранжевый
Коричневый
Жёлтый
Зелёный
Оливковый
Голубой
Синий
Тёмно-синий
Индиго
Фиолетовый
Белый
Чёрный
Размер шрифта:
Размер шрифта
Очень маленький
Маленький
Обычный
Большой
Огромный
Закрыть теги
[quote="Vlad"]По ссылке - не совсем то, что нужно конкретно в моей задаче. Но в целом скажу - тут join-ы, они, конечно, правильнее (красивее). Но с точки зрения быстродействия - проигрывают LIKE-варианту - как первоклассники.[/quote]
Настройки
HTML
ВЫКЛЮЧЕН
BBCode
ВКЛЮЧЕН
Смайлики
ВКЛЮЧЕНЫ
Отключить в этом сообщении BBCode
Отключить в этом сообщении смайлики
Если у вас плохое зрение или вы не можете прочесть этот код по какой-то другой причине, то обратитесь за помощью к
Администратору
.
Код подтверждения: *
Введите код в точности так, как вы его видите. Код является регистро-зависимым, а символ нуля имеет косую линию внутри цифры.
Часовой пояс: GMT + 3
Перейти:
Выберите форум
CakePHP Форум
----------------
Общий
Установка и настройка
Творчество
Вопросы и пожелания
Комментарии к главам руководства по CakePHP 1.1
Обзор темы
Автор
Сообщение
Vlad
Добавлено: 16 Янв 2011 20:41:29
Заголовок сообщения:
По ссылке - не совсем то, что нужно конкретно в моей задаче. Но в целом скажу - тут join-ы, они, конечно, правильнее (красивее). Но с точки зрения быстродействия - проигрывают LIKE-варианту - как первоклассники.
Vlad
Добавлено: 13 Янв 2011 00:13:30
Заголовок сообщения:
Спасибо за полезную ссылку, завтра на работе посмотрим - сколько времени оно будет колдовать над 50к товаров.
Боюсь, что тут правильный метод - через джойны - то это правильно, но долго
Завтра доложусь об испытаниях
))
evilbloodydemon
Добавлено: 12 Янв 2011 23:03:32
Заголовок сообщения:
особо не вдавался в подробности, но судя по всему проблему в том числе и хабтм паджинации решит вот этот плагин https://github.com/Terr/linkable/
Vlad
Добавлено: 10 Янв 2011 11:42:12
Заголовок сообщения: Pagination в CakePHP с таблицами HABTM связями
Основная проблема для меня в CakePHP — это осуществление pagination штатными средствами для таблиц связанных HABTM.
Не знаю как лучше — назвать эту заметку переводом, или расширенным переводом. Я использую две статьи, плюс свои плюшки. Поэтому ссылки я проставлю, а перевод это, или компиляция — не столь важно, как мне кажется.
После недолгого шуршания по Интернету я нашёл решение вот по этому адресу: http://cakebaker.42dh.com/2007/10/17/pagination-of-data-from-a-habtm-relationship/. Хочу отметить что решение работает и для последней (на текущий момент) версии CakePHP 1.3.4.
Однако тут есть нюанс, который очень сильно портит малину. Если элемент связан с несколькими другими, то штатный педжинатор неправильно считает количество элементов, потому как они дублируются. Но и для этого есть решение http://debuggable.com/posts/how-to-paginate-a-search-using-the-cakephp-framework:48fc5f77-38d0-41e0-b711-77c64834cda3 .
Итак, переходим к задаче.
Что у нас есть на входе?
CakePHP 1.3.4 версии, MySQL 5.1.37
Есть категория товара (В«Принтеры лазерныеВ», В«КартриджиВ» и т.д.), есть производитель (В«HPВ», В«XeroxВ»), есть виды характеристик (В«Формат печатиВ», В«ЦветностьВ»), есть сами эти характеристики — (В«А4В», В«А3В» и, соответственно В«ЦветнойВ», В«МонохромныйВ»), и есть сам товар, который лежит в одной из категорий, имеет одного производителя, может иметь характеристику (принтеры имеют формат печати в списке характеристик, а телефоны — нет), а характеристика, соответственно имеет свой список (для формта — В«А4В», В«А3В»).
Что нам необходимо? Необходимо чтобы делалась выборка (с работающей педжинацией) по следующему запросу (например): показать категорию товара 1, выбрать только производителя 2, показать товар, у которого есть в параметрах что-то из списка атрибутов (В«А4В» или В«А4В» и В«А3В»).
Для начала, посмотрим как выглядят таблицы. (я сознательно опустил огромное количество полей, которые не влияют на результат — чтобы не загромождать и без того долгую статью)
Таблица производителей
Код:
CREATE TABLE `produces` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `produces` VALUES(1, 'Xerox');
INSERT INTO `produces` VALUES(2, 'Hewlett Packard');
INSERT INTO `produces` VALUES(3, 'Alcatel');
Таблица категорий
Код:
CREATE TABLE `categories` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `categories` VALUES(1, 'Принтеры лазерные');
INSERT INTO `categories` VALUES(2, 'Принтеры струйные');
INSERT INTO `categories` VALUES(3, 'Принтеры матричные');
INSERT INTO `categories` VALUES(4, 'Копировальные аппараты');
Таблица товаров
Код:
CREATE TABLE `goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`created` datetime NOT NULL,
`model` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category_id` bigint(20) NOT NULL,
`produce_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `goods` VALUES(8, '2010-12-14 00:29:33','model1', 1, 1);
INSERT INTO `goods` VALUES(7, '2010-12-14 00:21:59', 'model2', 1, 2);
INSERT INTO `goods` VALUES(9, '2010-12-14 13:50:51', 'model3', 2, 2);
Таблица типов характеристик
Код:
CREATE TABLE `orders` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `orders` VALUES(1, 'Цветопередача');
INSERT INTO `orders` VALUES(2, 'Формат');
INSERT INTO `orders` VALUES(3, 'Тип печати');
И таблица собственно характеристик
Код:
CREATE TABLE `ordernames` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`order_id` bigint(20) NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `ordernames` VALUES(1, 1, 'цветные');
INSERT INTO `ordernames` VALUES(2, 1, 'монохромные');
INSERT INTO `ordernames` VALUES(3, 2, 'A3');
INSERT INTO `ordernames` VALUES(4, 2, 'A4');
INSERT INTO `ordernames` VALUES(5, 2, '10x15 cm');
INSERT INTO `ordernames` VALUES(6, 2, '42”');
INSERT INTO `ordernames` VALUES(7, 3, 'на термобумаге');
INSERT INTO `ordernames` VALUES(8, 3, 'на термоплёнке');
Есть ещё, табличка, которая связывает категории и order — потому как не у каждой категории товара есть уточняющий список характеристик. Эта табличка служит для формирования странички выдачи товара и сортировочного меню, поэтому считаю её вспомогательной и не требующей разъяснения или публикации.
Итак, таблички созданы, теперь необходимо связать товар с характеристиками. Для этого создаётся вспомогательная табличка:
Код:
CREATE TABLE `goods_ordernames` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`good_id` bigint(20) NOT NULL,
`ordername_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Для связи конкретного товара с характеристикой (и не одной) из таблицы ordernames. Связываем таблички напрямую, без участия orders — зачем нам посредник?
Теперь пришла пора строить выборку
Отобрать товар по категории и производителю не представляет труда, ибо id-шник присутствует в таблице товара и нет необходимости делать какие-либо join-ы дополнительные. Тут всё просто.
Нас же интересует — как выбрать товар, обладающий характеристиками A4 и A3 одновременно, или только А3 (например).
Берём решение из первой ссылки, и прикручиваем внутрь контроллера:
Код:
public $paginate = array('Good' => array('limit' => 1, 'joins' => array(
array(
'table' => 'goods_ordernames',
'alias' => 'GoodsOrdername',
'type' => 'inner',
'conditions'=> array('GoodsOrdername.good_id = Good.id')
),
array(
'table' => 'ordernames',
'alias' => 'Ordername',
'type' => 'inner',
'conditions'=> array(
'Ordername.id = GoodsOrdername.ordername_id',
'Ordername.id' => 3
)
)),'contain' => array('GoodsOrdername','Ordername','Produce'),'conditions' => array('produce_id' => 1)));
Затем мы далее переопределим эту переменную, в зависимости от потребности. А потребностей у нас может быть две — первая нас интересуют характеристики товара, и вторая — нас не интересуют характеристики товара.
($named — массив характеристик).
Код:
if ($named != array()) {
$order = array('Ordername.id' => $named);
$this->paginate = array('Good' => array('limit' => 10, 'joins' => array(
array(
'table' => 'goods_ordernames',
'alias' => 'GoodsOrdername',
'type' => 'inner',
'conditions'=> array('GoodsOrdername.good_id = Good.id')
),
array(
'table' => 'ordernames',
'alias' => 'Ordername',
'type' => 'inner',
'conditions'=> array(
'Ordername.id = GoodsOrdername.ordername_id',
$order
)
)),'order' => $sort,'fields' => array('DISTINCT Good.id','Good.*'),'conditions' => array('and' => array($produce,'Good.category_id' => $id))));
}
else {
$this->paginate = array('Good' => array('limit' => 10, 'order' => $sort,'fields' => array('DISTINCT Good.id','Good.*'),'conditions' => array('and' => array($produce,'Good.category_id' => $id))));
}
Обратите внимание на две вещи, во первых нам не нужны дубли, и используем DISTINCT для отсекания повторов (а повторы будут, если товар обладает и В«А4В» и В«А3В» а мы выбираем обе характеристики). Второе — если массив характеристик не задан — мы не городим огород с join-ами. Логично? Логично. Параметр 'order' — тут вроде всё понятятно, $poduce — тоже ('Good.produce_id' => array(1,3,4...));
Считаем страницы
Всё? Не всё. Дело в том, что мы выбираем нужный нам товар — без дублирования. Но, при этом счётчик педжинации считает всё (если товар обладает характеристикой A3 и A4 — то, он будет посчитан дважды) — то есть имея на самом деле 4 единицы товара со множественными характеристиками, счётчик страниц покажет нам 2-3-4-5… страниц для листания, хотя на самом деле товара значительно меньше. Что делать? Открываем вторую ссылку приведенную в начале статьи, и дополняем модель good следующим:
Код:
/**
* Return count for given pagination
*
* @param string $paginator Pagination name
* @param array $conditions Conditions to use
* @return mixed Count, or false
* @access public
*/
function paginatorCount($paginator, $conditions = array(), $contain = array()) {
$Db = ConnectionManager::getDataSource($this->useDbConfig);
if (!empty($contain)) {
$related = ClassRegistry::init($contain[0]);
}
$sql = 'SELECT
COUNT(DISTINCT ' . $this->alias . '.' . $this->belongsTo['Game']['foreignKey'] . ') count
FROM ' . $Db->fullTableName($this->table) . ' ' . $Db->name($this->alias) . ' ';
if (!empty($contain)) {
$sql .= ' INNER JOIN ' . $Db->fullTableName($related->table) . ' ' . $Db->name($related->alias) . ' ';
}
$sql .= $Db->conditions($this->paginatorConditions($paginator, $conditions, 'count'));
$count = $this->query($sql);
if (!empty($count)) {
$count = $count[0][0]['count'];
}
return $count;
}
/**
* Build conditions for given pagination
*
* @param string $paginator Pagination name
* @param array $extraConditions Extra conditions to use
* @param string $method 'count', or 'find'
* @return array Conditions
* @access public
*/
function paginatorConditions($paginator, $extraConditions = array(), $method = null) {
$Db = ConnectionManager::getDataSource($this->useDbConfig);
$conditions = null;
if (empty($extraConditions)) {
$extraConditions = array('1=1');
}
switch (strtolower($paginator)) {
case 'game_categories_games':
if ($method != 'count') {
$conditions = array_merge($extraConditions, array('1=1 GROUP BY ' . $this->alias . '.' . $this->belongsTo['Game']['foreignKey']));
} else {
$conditions = $extraConditions;
}
break;
}
return $conditions;
}
/**
* Executed by the paginator to get the count. Overriden to allow
* forcing a count (through var $forcePaginateCount)
*
* @param array $conditions Conditions to use
* @param int $recursive Recursivity level
* @return int Count
* @access public
*/
function paginateCount($conditions, $recursive) {
if (isset($this->forcePaginateCount)) {
$count = $this->forcePaginateCount;
unset($this->forcePaginateCount);
} else {
$count = $this->find('count', compact('conditions', 'recursive'));
}
return $count;
}
Всё. Теперь всё работает так, как надо. В комментариях ко второй статье было указано — DISTINCT — зло. Но увы, я не знаю как по другому решить эту задачу. Если вам помогла эта статья — я рад, если вы поможете мне решить задачу ещё более простым способом — я буду счастлив.
И теперь дополнение, как это лучше сделать в реальности. Дело в том, что приведенный выше способ работает по методу "мягкого И" ну или точнее - "ИЛИ". Т.е. если вы задаёте "выбери товар с характеристикой 3,4,5 - оно выберет все товары у которых есть 3, 4 ИЛИ 5 или комбинации этих параметров. Т.е. вы не получите выборку где товар обладает и 3 и 4 и 5 характеристиками.
Есть решение такое, в момент записи HABTM а в табличку, в таблице товара заполняем поле "param" которое может быть текстовым, параметрами (отсортированными по возрастанию).
Т.е. у товара есть поле param " 1 3 4 5 "
(по два пробела вокруг цифры).
Теперь выборка будет выглядеть так:
LIKE "% 1 % 3 % 4 %" - выберутся ВСЕ товары, у которыех есть ВСЕ три параметра - 1,3 и 4. И не надо выкаблучиваться с подсчётом в педжинации.
В чём минусы? Решение не совсем красивое. В чём плюсы - огромная скорость выборки. На тестовой базе в 50 тысяч товаров - прирост составил - в 50 раз, по сравнению с первым решением.
Проблема, которая немного утяжеляет - это если вы решите удалить какой-то из параметров, то придётся "пробежаться" по всей базе товаров, и вычеркнуть ненужный номер из поля param. - Но это делает администратор, и делает не часто.
Вопросы?
Ах да, адрес сайта bcp.kiev.ua[/url]
Powered by phpBB © 2001, 2005 phpBB Group
Русская поддержка phpBB
Если ничего интересного не нашли, можно рекламу почитать, а рекламируется у нас сегодня «».