Простой и быстрый фильтр списка ресурсов для эво/рево. на php+sql

Сниппет выводящий очень быстро список товаров с фильтром и сортировкой по нес-ким полям и тв одновременно.
(писался под эво, на рево тоже должен работать.)

(техн реализация и подводные камни.)
Если товаров очень много, то стандартные и удобные средства модх начинают сильно тормозить и это критично.
Но можно сделать прямой запрос к базе и получить список нужных ид и сделать рендеринг шаблона и пагинацию.
Как же это сделать. Рассказываю здесь очень подробно.

Простейший случай — фильтр по осн. полям и сортировка по одному полю.
тут проблем никаких-таблица горизонтальная 1 товар-1 строка. поле это отдельный столбец.
«SELECT id FROM $t1 WHERE parent=10 ORDER BY pagetitle ASC»
дальше выполняем его через модх объект. если по нес-ким, то добавляем в (WHERE parent=1 AND template=2), (ORDER BY id DESC, pagetitle ASC)
AND/OR и запятую. Тут все просто, делаем в цикле по условию.

Проблемы начинаются с ТВ. а особенно с ТВ у которых значение не 0/пусто.
Таблица тв вертикальная — это экономит место, но плохо для быстрой выборки по значению тв. Присоединить ее можно, но 1 раз только для одного тв.
т.е если надо фильтровать или сортировать по 5 тв то это 5 запросов.
Но если это делать средствами модх то это 2 запроса на товар: 100т на 10т товаров. это ОЧЕНЬ МНОГО. т.к каждый запрос это TCP соед. по сокету. асинхронно. и лучше все циклы делать на SQL сервере или пхп.
В таблице значений ТВ — хранятся только значения отличные от значения по умолчанию.
Тут есть хитрость если присоединить простым JOIN то там где нули в выборку не попадут вообще. но LEFT JOIN $t2 as b ON a.id=v.contentid AND b.tmplid=$tv_id WHERE 1 — выдаст нам таблицу где в значении будет NULL или то что вбито. Если там число то пишем (WHERE b.value*1>10) (ORDER b.value*1) где *1 — это аналог (float) — убирает все пробелы и сортировка как чисел, текст по другому сортируется. 199 — ниже 22 т.к первая буква меньше.
Причем если написать так
LEFT JOIN $t2 as b ON a.id=v.contentid WHERE b.tmplid=$tv_id AND b.value*1>0…
то нули не добавятся и будет неправильно работать… хотя по смыслу одно и тоже с виду. такие вот причуды. т.е порядок такой: в цикле делаем сложный запрос для первой тв, для остальных простой но добавляем ..AND id IN ($m) где $m-массив ид от предыдущих фильтров или можно вставить подзапрос (SELECT...) который его делает чтобы массив туда-сюда не гонять, это экономит память и время.

Еще большая сложность возникает, если у тв ЕСТЬ знач по умолчанию, а оно лежит в др. таблице и нужен еще запрос туда, но он и так все равно нужен для проверки на NULL/0/знач. и имя_тв в ид_тв. если есть и не 0/NULL/'', то везде заменяем b.value на COALESCE(b.value,$tv_dv) (*1 — если колонка число) этот оператор заменяет NULL на значение которое мы получили отдельным запросом хотя и его можно вставить как
подзапрос, но LIMIT 1 не пишите-ошибка будет. По времени разница мизер так что как удобней, в сортировку аналогично. Есть еще ISNULL() но он работает по разному на разных БД и нам тут не подходит.
Еще один подводный камень — знач по умолч. не пусто а вбито руками NULL — случай невероятный, но возможный. и тогда он встанет в ряд с остальными NULL и заменится на знач по умолч. и это неправильно=ошибка. поэтому делаем проверку: отдельный запрос и ищем знач. NULL, если они есть, то делаем временную таблицу где NULL заменяем на пустую строку и дальше используем эту врем. таблицу, а не основную.

К сожалению присоединить тв как доп столбец к осн таблице можно только один, а нес-ко невозможно. или делать метровый запрос с подзапросами вложенными как матрешка.
поэтому будет короткий цикл по всем тв и дополнительно N запросов где N число тв в фильтре/сортировке. но их много не бывает. не критично.

В итоге получили список ид с фильтром по 0-100 полям и тв и сортировкой по 0-100 полям.
Хотя нам надо всего N штук для вывода на тек стр. мы не пишем LIMIT $start,$diplay
хотя в последнем можно, но тогда мы не узнаем сколько товаров вообще и не сделаем правильно ссылку на след стр, но можно правда выбрать на 1 больше и проверить на длину ответа, если больше display, то делаем, иначе эта последняя.
Отсортировать можно в конце в последнем запросе или отдельным запросом в конце.
Теперь цикл по ид и рендерим шаблон, тут можно через модх получать знач полей и тв — проще. товаров на тек стр мало. и тв можно получить с учетом виджета — например
<img src="tv_val" alt="tv-val">
Замену делаем реплейсом в цикле: $out .= str_replace('[+id+]',$id,$wablon)
если в шаблоне есть вызов сниппетов/тв/чанков то для этого есть мой рендер — шаблон можно не в чанке а в ресурсе как контент+шаблон и добавлять туда тв итд как в обычный документ. но это всего лишь кусок итогового хтмл который отдаст спиппет.(это отдельная моя разработка) пока простой хтмл.
Теперь надо сделать пагинацию(pager) пейдженацию правильней. считаем кол-во — делим на шт/стр =кол-во страниц. делаем ссылки. ловим гет (int)($_GET['start']) это предыдущая, ?start=мин(*+display, всего) это следующая, добавляем в out и выводим. это всё. быстрее уже не сделать, без доп. горизонтальных таблиц для тв.
есть уже готовые сниппет для эво=docLister, рево pdoResource — они тоже быстрые. но можно без них и добавить что-то нестандартное и сложное условие.

простой пример с одним тв ниже. если надо много то вызываем для каждого, потом разницу — AND или объединить -OR
///===evo====
function tv_id_dv_by_name($tv_name){global $modx; //dv=default_value
 $ftn=$modx->getFullTableName('site_tmplvars');
 $v=(is_numeric($tv_name))? 'id': 'name'; //уже ид?								
 $q="SELECT * FROM $ftn WHERE $v='$tv_name' LIMIT 1";
 $q=$modx->db->query($q);
 $k = $modx->db->getrow($q); 
 $id=isset($k['id'])? $k['id']: '';
 $dv=isset($k['default_text'])? $k['default_text']: '';
 return [$id,$dv];
}
//получить список ид ресурсов по значению тв=/in[]/=/>=/<=/ и отсортировать по нему
function get_ids_res_by_tv_val_and_sort($tv_name='blok_num',$tv_val='',$min='',$max='',$sort=''){global $modx;
 $t1=$modx->getFullTableName('site_content');
 $t2=$modx->getFullTableName('site_tmplvar_contentvalues');
 $m=tv_id_dv_by_name($tv_name); $tv_id=$m[0]; $tv_dv=$m[1];
 $tval=($tv_dv)? "COALESCE(b.value,$tv_dv)" : 'b.value';
 $where='a.template=9 AND (a.parent=3 OR a.parent=6)'; //сюда добавляем простые поля ресурса.
 $q="SELECT a.id,a.pagetitle, $tval AS v FROM $t1 AS a LEFT JOIN $t2 AS b ON a.id=b.contentid AND b.tmplvarid=$tv_id WHERE $where ";
 
 if(is_array($tv_val))  $q .= " AND v IN (" . implode(',',$tv_val) . ") ";
 else {
  if(trim($tv_val)!='') $q .= is_numeric($tv_val)? " AND b.value = $tv_val":" AND b.value = '$tv_val'";
  if(trim($min)!='')    $q .= " AND COALESCE(b.value,$tv_dv)*1 >= $min";
  if(trim($max)!='')    $q .= " AND COALESCE(b.value,$tv_dv)*1 <= $max";	
 }
 if($sort){; //ASC/DESC
  if($sort =='ASC' or $sort=='+') $q .=' ORDER BY v*1 ASC '; else {$q .=' ORDER BY v*1 DESC ';}	   
 }
																					 
 $q=$modx->db->query($q);
 $out=[]; while($m=$modx->db->getRow($q)){$out[]=$m['id'] . '|' . $m['pagetitle'] . '|'. $m['v'];}
 return $out;
}
//использование
print_r (get_ids_res_by_tv_val_and_sort('blok_num','',1,200,'+'));
print_r (get_ids_res_by_tv_val_and_sort('blok_num',[1,3,9],'','','-'));
телеграм @ArturPlugin
июнь 2019г

*это для раздела- готовые решения, для разработчиков или др.
Artur Plugin
14 июня 2019, 18:40
265
0

Комментарии: 6

Николай Савин
14 июня 2019, 18:48
+6
Ты бы хоть код в теги оформил
    Андрей
    15 июня 2019, 15:55
    +8
    Просто фильтр очень быстрый, даже подсветка не срабатывает.
Artur Plugin
15 июня 2019, 16:57
0
В первый раз пишу сюда, поправил.
Что по тексту непонятно? На рево другой вызов sql запроса, нет там $modx->db
можно расширить класс ядра чтобы команды эво тоже понимал. Или делать развилки
function sql_query($q){
global $modx; return($modx->db)? $modx->db->query($q): return $modx->query($q);
}
    Artur Plugin
    15 июня 2019, 17:12
    0
    Не знаю как у вас, у меня подсветка кода есть, но хвостов не видно — не переносит и гориз. скрола нет :( и визуально непонятно есть там продолжение или всё. Если в фокусе, то можно стрелками двигать. С моб. свайпом.
Павел Гвоздь
Вчера в 08:31
+4
Не хочу осуждать, но вот эта каша в тексте, в коде… насколько это нормально по вашему писать такое, чтобы потом люди или последующие разработчики его пытались читать? На мой взгляд, это как минимум неуважение к другим.
Я не спорю, может ваше решение и «очень быстрое», но при попытке прочесть хотя бы описание, у меня был рвотный позыв, я не осилил.
Павел Голубев
Вчера в 16:54
0
Может что-то не разглядел, но не понял что в нём быстрого то?

Получилось 2 вещи:
1. Отказ от объектной модели Modx. Когда товаров много — нужно отказываться. Это общепринятая практика т.к. дорого проверять права, значения по умолчанию, мапинг объектов и т. д.
2. Билдер sql запроса. Если глянем внутрь pdoResources увидим тоже самое.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.