Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770
Форумы портала PHP.SU :: Версия для печати :: Задачка по SQL
Форумы портала PHP.SU » PHP » SQL и Архитектура БД » Задачка по SQL

Страниц (1): [1]
 

1. DeepVarvar - 12 Января, 2014 - 10:02:38 - перейти к сообщению
В каталоге есть два типа товаров: одежда и обувь.
Предусматривается фильтрация в условиях (WHERE IN/NOT IN) по брендам, размерам, категориям.
Заранее известно что в дизайне высота блока позиции с одеждой вдвое больше высоты блока позиции с обувью.
Необходимо выводить товары в последовательности: 1 одежда, 2 обуви, 1 одежда, 2 обуви и т.д..
Предположим, одежда это тип 1, а обувь это тип 2.
В классическом виде на одной странице каталога по лимиту нужно вывести
(две еденички как двойная высота блока с одеждой, а двойки - каждая как одна обувь) в три ряда сверху вниз:

1|2|1
1|2|1
2|1|2
2|1|2
1|2|1
1|2|1

Заранее не известно каких позиций в каталоге больше - одежды или обуви.
Возможно на некоторой странице пагинации может возникнуть момент, где не будет хватать какого-либо типа товара, тогда нужно забить место каким угодно товаром (или двумя) - лишь бы заполнилось пустое место.

В случае если больше не осталось одежды - подключается шаблон вывода только обуви, и наоборот.

Сам вопрос только о сортировке вида: 1 одежда, 2 обуви, 1 одежда, 2 обуви и т.д..
Остальное я описал для понимания того что вообще имеется.
2. Panoptik - 12 Января, 2014 - 10:31:43 - перейти к сообщению
отличная задачка вместо проснуться

насколько я помню NOT IN в мускуле не работает
ну в прочем это к делу не относится

извращениям заказчика нет предела

у меня была похожая задача, нужно было выбирать в панель новостей 3 записи из трех разных таблиц по одной, если в какой-то таблице нет записей то добавляем с имеющихся, посему если спроецировать на текущую задачу. мой (не очень красивый вариант)
делаем 2 выборки, заранее по количеству соответствующих нужному количеству элементов из по двум критериям отдельно

вот псевдо код
PHP:
скопировать код в буфер обмена
  1.  
  2. // for example items per page 15
  3. $clothes = (array) 'SELECT * FROM items WHERE category=1 LIMIT 15' // must be at least 5
  4. $shoes = (array) 'SELECT * FROM items WHERE category=2 LIMIT 15' // must be at least 10
  5.  
  6. $result = array();
  7. if($clothes >= 5 && $shoes >= 10) {
  8.  for($i = 0; $i < 5; $i++) {
  9.    $result[] = $clothes[$i];
  10.    $result[] = array_shift($shoes);
  11.    $result[] = array_shift($shoes);
  12.  }
  13. } else {
  14. // тут нужно подумать как делать когда не хватает определенных наименований
  15. }
  16.  


проблемы данного способа
- сложность расчета пагинации
- сложность компоновки элементов в случае когда элементов не хватает
- при всем этом нужно правильно рассчитывать offset чтобы все элементы отображались и ни один не был пропущенным (возможно прийдется делать дополнительные выборки на точное соответствие количеству элементов на странице, а потом довыбирать недостающие)

PHP:
скопировать код в буфер обмена
  1. $clothes = (array) 'SELECT * FROM items WHERE category=1 LIMIT 5' // must be at least 5
  2. $shoes = (array) 'SELECT * FROM items WHERE category=2 LIMIT 10' // must be at least 10


по какому критерию происходит базовая сортировка?
(Добавление)
вообще есть изящная идея но пока не знаю как реализовать

смысл в следующем

генерируем в мускуле последовательность 1,2,3,4,5,6 ... и т.д

считаем формулу в которой каждый вариант будет соответствовать порядку в последовательности своей категорией
к примеру одежда - категория 1, обувь - 2

последовательность назовем s

значит получается что мы должны джойнить одежду к последовательности как s%3 = 1 and category = 1
и обувь к последовательности как s%3 <> 1 and category = 2
и сделать order by s, но как быть с другими сортировками нужно еще подумать, может у кого-то будут более светлые мысли по этому поводу
3. DeepVarvar - 12 Января, 2014 - 11:11:05 - перейти к сообщению
Цитата:
по какому критерию происходит базовая сортировка?

Первичная всегда по дате добавления - новые в начале.
Цитата:
мы должны джойнить одежду к последовательности как s%3 = 1 and category = 1
и обувь к последовательности как s%3 <> 1 and category = 2
и сделать order by s
Я так примерно до тогго-же щас допетрил, с предварительной выборкой. Но пока конечного варианта не написано, все находится только в домыслах в голове.
4. Мелкий - 12 Января, 2014 - 11:32:52 - перейти к сообщению
Поскольку SQL как таковой ничего не знает о соседних строках, только об обрабатываемой в этот конкретный момент, то надо или использовать специфичные для субд средства или перенести сортировку на приложение.

Для mysql могу такое предложить:
CODE (SQL):
скопировать код в буфер обмена
  1. SET @sortacounter:=-1, @sortbcounter:=0, @rowbcounter:=-1;
  2. SELECT id, FIELD, IF(FIELD='a',@sortacounter:=@sortacounter+1,IF((@rowbcounter:=@rowbcounter+1)=2,@sortbcounter:=@sortbcounter+1 + (@rowbcounter:=0), @sortbcounter)) AS sort FROM `splits` ORDER BY sort, FIELD


С прочими сортировками просто, но безрадостно, надо уходить в подзапрос:
CODE (SQL):
скопировать код в буфер обмена
  1. SET @sortacounter:=-1, @sortbcounter:=0, @rowbcounter:=-1;
  2. SELECT id, FIELD, IF(FIELD='a',@sortacounter:=@sortacounter+1,IF((@rowbcounter:=@rowbcounter+1)=2,@sortbcounter:=@sortbcounter+1 + (@rowbcounter:=0), @sortbcounter)) AS sort FROM (SELECT id, FIELD FROM `splits` /**/ ORDER BY другое_поле) AS dataset ORDER BY sort, FIELD
5. DeepVarvar - 14 Января, 2014 - 00:29:48 - перейти к сообщению
Добавил спасибки обоим. Сделал некий симбиоз обоих предложенных вариантов.
Описывать долго, некогда и лень, задача решена Закатив глазки
6. DeepVarvar - 14 Января, 2014 - 04:01:01 - перейти к сообщению
Отпишусь, и для себя и для других.
Вдруг надо будет такое же чудо наковыривать.
Да и критика всегда к месту:

CODE (php):
скопировать код в буфер обмена
  1. <?php
  2. // получаем все товары
  3. $allProducts = dbQuery("
  4.  
  5.    SELECT DISTINCT
  6.        innerovp.id,
  7.        IF(ammpv.view = 'default', 0, 1) view
  8.    FROM innerovp
  9.    LEFT JOIN ammpv ON ammpv.idproduct = innerovp.id
  10.    WHERE innerovp.published = 1
  11.    ORDER BY innerovp.created_on DESC
  12.  
  13. ");
  14.  
  15. // разбираем кто есть одежда, а кто обувь
  16. $clothes = array();
  17. $shoes = array();
  18. foreach ($allProducts as $item) {
  19.     if ($item['view']) {
  20.         array_push($clothes, $item['id']);
  21.     } else {
  22.         array_push($shoes, $item['id']);
  23.     }
  24. }
  25.  
  26. // больше это нам не нужно
  27. unset($allProducts);
  28.  
  29. // подсчет товаров
  30. $sizeOfClothes = sizeof($clothes);
  31. $sizeOfShoes   = sizeof($shoes);
  32. $sizeOfAllProducts = $sizeOfClothes + $sizeOfShoes;
  33.  
  34. // переменные для постранички, число 18 выбрано не случайно
  35. // оно прекрасно делится на 3, а 6 позиций
  36. // максимально корректно располагаются в одном ряду
  37. $itemsPerPage = 18;
  38. $maxPageNumber = ceil($sizeOfAllProducts / $itemsPerPage);
  39. $currentPage = (int) $_GET['page'];
  40. $currentPage = $currentPage > $maxPageNumber
  41.     ? $maxPageNumber : ($currentPage < 1 ? 1 : $currentPage);
  42. $offset = ($currentPage - 1) * $itemsPerPage;
  43.  
  44. // под айдишники для выборки на страницу
  45. $productsID = array();
  46.  
  47. // пока забиваем порядок айдишников,
  48. // составим "карту" вывода товаров на странице,
  49. // в классическом виде это три колонки
  50. // с порядком следования на НЕчётных страницах:
  51. //
  52. // для первой колонки  - одежда, 2 обуви, одежда, 2 обуви
  53. // для второй колонки  - 2 обуви, одежда, 2 обуви, одежда
  54. // для третьей колонки - аналогично первой
  55. //
  56. // и порядком следования на чётных страницах:
  57. //
  58. // для первой колонки  - 2 обуви, одежда, 2 обуви, одежда
  59. // для второй колонки  - одежда, 2 обуви, одежда, 2 обуви
  60. // для третьей колонки - аналогично первой
  61. //
  62. // т.е. вывод с чередованием
  63.  
  64. $cs = $sizeOfClothes;
  65. $ss = $sizeOfShoes;
  66.  
  67. while ($cs > 0 || $ss > 0) {
  68.  
  69.     $cStep = 2;
  70.     while ($cStep > 0) {
  71.         if ($cs > 0) {
  72.             array_push($productsID, array_shift($clothes));
  73.             $cs = sizeof($clothes);
  74.         }
  75.         if ($ss > 0) {
  76.             array_push($productsID, array_shift($shoes));
  77.             $ss = sizeof($shoes);
  78.         }
  79.         if ($ss > 0) {
  80.             array_push($productsID, array_shift($shoes));
  81.             $ss = sizeof($shoes);
  82.         }
  83.         $cStep -= 1;
  84.     }
  85.  
  86.     $sStep = 2;
  87.     while ($sStep > 0) {
  88.         if ($ss > 0) {
  89.             array_push($productsID, array_shift($shoes));
  90.             $ss = sizeof($shoes);
  91.         }
  92.         if ($ss > 0) {
  93.             array_push($productsID, array_shift($shoes));
  94.             $ss = sizeof($shoes);
  95.         }
  96.         if ($cs > 0) {
  97.             array_push($productsID, array_shift($clothes));
  98.             $cs = sizeof($clothes);
  99.         }
  100.         $sStep -= 1;
  101.     }
  102.  
  103. }
  104.  
  105. // выбираем срез из массива
  106. $productsID = array_slice($productsID, $offset, $itemsPerPage);
  107.  
  108. // если что-нибудь есть
  109. $products = array();
  110. if ($productsID) {
  111.  
  112.     // получаем товары для текущей страницы
  113.     $productsID = join(',', $productsID);
  114.     $products = dbQuery("
  115.  
  116.        SELECT
  117.            ovp.id,
  118.            IF(ammpv.view = 'default', 0, 1) view,
  119.            oapc.cena,
  120.            ovpp.product_price
  121.        FROM ovp
  122.        /* тип товара */
  123.        LEFT JOIN ammpv
  124.            ON ammpv.idproduct = ovp.id
  125.        /* старый прайс, его может не быть */
  126.        LEFT JOIN oapc ON oapc.idproduct = ovp.id
  127.        /* основной прайс, или скидочный в контексте старого прайса */
  128.        INNER JOIN ovpp ON ovpp.product_id = ovp.id
  129.        WHERE ovp.id IN({$productsID})
  130.        ORDER BY FIELD(ovp.id, {$productsID})
  131.  
  132.    ");
  133.  
  134.     // добавляем к товарам их основные картинки,
  135.     // чота этот запрос слишком прожорлив,
  136.     // но только он корректно достает главные картинки,
  137.     // запросы в цикле это моветон,
  138.     // картинка с наименьшим значением сортировки и является главной,
  139.     // а наименьшее значение может быть любым, и NULL, и 0 и больше нуля
  140.     $productImages = dbQuery("
  141.  
  142.        SELECT
  143.            ovm.file_url,
  144.            ovpmj.product_id
  145.        FROM (
  146.            SELECT product_id, MIN(ordering) min_ordering
  147.            FROM iovpm
  148.            WHERE product_id IN({$productsID})
  149.            GROUP BY product_id
  150.        ) ovpm
  151.        INNER JOIN ovpmj ON ovpmj.product_id = ovpm.product_id
  152.            AND ovpmj.ordering = ovpm.min_ordering
  153.        LEFT JOIN ovm
  154.            ON ovm.media_id = ovpmj.media_id
  155.  
  156.    ");
  157.  
  158.     $pImages = array();
  159.     foreach ($productImages as $item) {
  160.         $pImages[$item['product_id']] = $item['file_url'];
  161.     }
  162.     unset($productImages);
  163.     foreach ($products as $k => $item) {
  164.         if (isset($pImages[$item['id']])) {
  165.             $products[$k]['file_url'] = $pImages[$item['id']];
  166.         }
  167.     }
  168.     unset($pImages);
  169.  
  170.     // для расчета как размещать товары на странице
  171.     // одежда по высоте в два раза больше обуви
  172.     $productPlaces = 0;
  173.     foreach ($products as $item) {
  174.         $productPlaces += ($item['view'] ? 2 : 1);
  175.     }
  176.  
  177.     // для расчета как размещать товары на странице
  178.     // сколько товаромест есть в каждой из трёх колонок
  179.     $sliceSize = ceil($productPlaces / 3);
  180.  
  181. }
  182.  
  183. // поехали выводить товары
  184. ?>
  185. <div class="maincatalog span9">
  186.  
  187.     <div class="span3">
  188.     <?php $i = 0; ?>
  189.     <?php $sliceCnt = 1; ?>
  190.     <?php foreach ($products as $k => $item) { ?>
  191.  
  192.         <?php $i += ($item['view'] ? 2 : 1); ?>
  193.  
  194.         <div>ТУТ ВЫВОД ОДНОГО ТОВАРА, НИЧЕГО ИНТЕРЕСНОГО</div>
  195.  
  196.         <?php if ($i < $productPlaces && $i >= ($sliceSize * $sliceCnt)) { ?>
  197.             <?php $sliceCnt += 1; ?>
  198.             </div><div class="span3">
  199.         <?php } ?>
  200.  
  201.     <?php } ?>
  202.     </div>
  203.  
  204. </div>
  205. <div class="clear"></div>
  206. <!-- дальше вывод постранички, у нас все расчитано выше -->
  207.  

(Добавление)
Ну и для полноты картины покажу что там было до меня.
Наслаждайтесь:
Спойлер (Отобразить)
7. tuareg - 14 Января, 2014 - 13:59:54 - перейти к сообщению
Это конечно хорошо, а если товаров будет 500 000? Не жирно будет каждый дергать все товары?
8. DeepVarvar - 14 Января, 2014 - 14:15:11 - перейти к сообщению
tuareg пишет:
Не жирно будет каждый дергать все товары?
Хм.., а есть конкретное предложение реализации, хотябы на уровне "мне подумалось вот именно так вот будет лучше"?
Товаров там сейчас 2100, ну чутка меньше.
9. Champion - 14 Января, 2014 - 14:26:52 - перейти к сообщению
Не смотрел код, поленился. Но такое решение будет удовлетворительно работать на больших объемах:
1 - Посчитать общее количество одежды (КОд) и общее количество обуви(КОб), подходящие под условия.
2 - Исходя из этого определить, на какой странице заканчивается первый то, чего меньше.
3 - Если мы просматриваем более раннюю страницу, то два селекта с одинаковым limit и фетчить оба по очереди.
4 - Если мы на странице, на которой закончился один из товаров, то LIMIT законченный товар делается по старым правилам, а новый товар - плюс разницу, необходимую для заполнения дыр.
5 - Если мы на странице, на которой товар уже закончился, то фетчим двойное количество незаконченного товара.

Только надо учесть, что размер страницы для того товара, который больше не одинаковый на каждой странице, а сначала N, потом N + K, потом 2*N, где К - количество недостающих товаров, чтобы заткнуть дыры там, где закончился первый тип товаров, а N обычное количество товара на странице, и поэтому вычисление первой цифры в LIMIT получится чуть сложнее, чем хотелось бы
10. DeepVarvar - 14 Января, 2014 - 14:34:24 - перейти к сообщению
Champion пишет:
Не смотрел код
А зря, я оттуда еще и фильтры повырезал дабы не заострять на них внимания.
Там по сути два главных запроса: один выбирает все айдишники (+ тип) которые соответствуют условиям выборки, затем после распихивания на "карту" выбирается срез из массива и делается второй основной запрос, который подтягивает все нужные данные для группы в срезе (по 18 штук на страницу).
Champion пишет:
заполнения дыр
При 18 могут случиться только либо одна большая дырка (одежда), либо маленькая (обувь), и то только в момент когда то или это "внезапно" закончится. Остальные распальцовки делает счетчик товаромест - по сколько в вертикальный ряд напихать.
11. Champion - 14 Января, 2014 - 14:41:05 - перейти к сообщению
DeepVarvar пишет:
один выбирает все айдишники
Ну да, это я заметил. Но, как заметил tuareg, а если товаров будет многомного? Поэтому предложил вариант без стягивания всех айдишек.
12. DeepVarvar - 14 Января, 2014 - 14:46:05 - перейти к сообщению
Champion пишет:
без стягивания всех айдишек
Если учесть что подсчет всеравно бежит по выборке с условиями, то два подсчета по типам - это две полноценные транзакции с доп-условиями.
Согласен - при многомного это сыграет в плюс.
Но как тогда строить "карту"? Схема сложнее чем было предложено Мелким.
13. Champion - 14 Января, 2014 - 15:14:55 - перейти к сообщению
count можно закешировать на некотрое время, чтоб не считать при каждом переходе на страницу.
Про строить карту я не очень увидел проблему. Когда в массивах с объувью и одеждой элементов поровну, тут всё понятно. А когда в одном больше, перекидываешь часть из одного в другой и опять же делаешь по-старому.
14. tuareg - 14 Января, 2014 - 15:59:22 - перейти к сообщению
DeepVarvar пишет:
Хм.., а есть конкретное предложение реализации, хотябы на уровне "мне подумалось вот именно так вот будет лучше"?
Я на работе был.
По поводу решения. С решением Мелкий надо смотреть, тут на вскидку я не скажу.
А так уже все сказано Улыбка Мое ИМХО.
Общее движение:
Сделать как предложил Panoptik, т.е 2 простых запроса. И смотрим есть ли рез-ты для Одежды и Обуви. Есть все хорошо, запомнили текущую страницу, и последние id.
При переходе на следующую, запрос будет простым where id>(запомненного) + фильтры + limit
Это я думаю достаточно простое решение пока есть то и другое.
Крайности: Закончилось что-то, можно сделать довыборку того что еще осталось. Как-то так.
P.S сейчас почитал, что написал Champion я действовал также.
15. DeepVarvar - 14 Января, 2014 - 16:50:46 - перейти к сообщению
Champion, дал спасибку, но поковыряю потом, щас уже другая задача в приоритете.

 

Powered by ExBB FM 1.0 RC1