Fi1osof 13 ноября 2015 3 2
Вчера на modx.pro был найден довольно интересный вопрос. Пока я писал там ответ, понял, что его имеет смысл вынести в отдельный топик, так как он получился довольно объемный и раскрывает несколько тонких моментов. Крайне советую к изучению практикующим xPDO.

Топикстартер абсолютно прав:
у меня возникает ощущение, что я пытаюсь найти строки в которых значение ps_type одновременно равно и «Подвесной», и «Административный», что, закономерно, ни к чему не приводит.
Да, нельзя сделать выбоку с условием, которое требует разных значений от одной и той же строки. Но и условие OR тоже нельзя использовать, так как это приведет к выборке в том числе и тех товаров, у которых имеется только “Административный”, но нет других необходимых значений.

Предложенный Сергеем запрос годится, но только на выборку одного типа товаров, в данном случае «Подвесной, Административный». Но сразу выбрать еще и «Административный, Промышленный, Подвесной» он не позволит. (На самом деле в данном случае конечно же позволит, ибо вхождение “Подвесной, Административный” имеется и в “Административный, Промышленный, Подвесной”, но очевидно, что топикстартер просто не удачный пример привел, и что надо искать товары, у которых значения не пересекаются). Придется выполнять столько выборок, сколько типов товаров найти надо будет.

Если говорить о реализации запроса именно с этой структурой данных, сначала придется сгруппировать эти данные, а потом уже по ним выполнить поиск, а потом еще и подзапрос выполнить. А вот подзапросы на уровне xPDO в принципе только через хаки выполняются.

Вот попробуйте в консоли выполнить этот код:
$q = $modx->newQuery('msProduct');
$alias = $q->getAlias();

$q->select(array(
    "{$alias}.*",
));


$q2 = $modx->newQuery('msProductOption');
$alias2 = $q2->getAlias();
$q2->select(array(
    "product_id",
    "group_concat(value) as `values`",
));
$q2->groupby('product_id');

$q2->prepare();
$sql2 = $q2->toSQL();
// print "<br />" . $sql2;


$q->query['where'][] = new xPDOQueryCondition(
    array(
        'sql' => "EXISTS (
            SELECT NULL FROM ({$sql2}) as t 
                WHERE 
                    t.product_id = {$alias}.id
                    AND (
                        (FIND_IN_SET('Административный', `values`) AND FIND_IN_SET('Подвесной', `values`))
                        OR (FIND_IN_SET('Административный', `values`) AND FIND_IN_SET('Промышленный', `values`) AND FIND_IN_SET('Подвесной', `values`))
                    )
        )"
    )
);

$s = $q->prepare();
$sql = $q->toSQL();
print "<br />" . $sql . "\n";

// $s = $modx->prepare($sql);
$s->execute();
print_r($s->errorInfo());

while($row = $s->fetch(PDO::FETCH_ASSOC)){
    print_r($row);
}


Этот код как раз выполняет то, вам нужно. На выходе будет получен SQL типа такого:
SELECT msProduct.* FROM `modx_site_content` AS `msProduct` WHERE EXISTS (
            SELECT NULL FROM (SELECT `product_id`, group_concat(value) as `values` FROM `modx_ms2_product_options` AS `msProductOption` GROUP BY product_id ) as t 
                WHERE 
                    t.product_id = msProduct.id
                    AND (
                        (FIND_IN_SET('Административный', `values`) AND FIND_IN_SET('Подвесной', `values`))
                        OR (FIND_IN_SET('Административный', `values`) AND FIND_IN_SET('Промышленный', `values`) AND FIND_IN_SET('Подвесной', `values`))
                    )
        ) 

И если он найдет нужные товары, то ниже выведет их в результат.

К сожалению, упростить этот запрос с текущей структурой вряд ли получится существенно, и не знаю можно ли такое выполнить через msProducts (только если в него можно передавать объект xPDOQuery или чистый SQL). Вариантов спасения видится два:

1. Использовать TV «выпадающий список множественный выбор». Он в БД складывает значения вида Значение1||Значение2||Значение3… Тогда можно будет передать в вызов условие типа такого:

print '<pre>';
$q = $modx->newQuery('msProduct');
$alias = $q->getAlias();

$q->select(array(
    "{$alias}.*",
));

$tv_id = 1;

$q->innerJoin('modTemplateVarResource', 'ps_type', "ps_type.tmplvarid = {$tv_id} AND ps_type.contentid = msProduct.id AND (
        (FIND_IN_SET('Значение1', replace('||', ',', ps_type.value)) AND (FIND_IN_SET('Значение2', replace('||', ',', ps_type.value))))
        OR (FIND_IN_SET('Значение1', replace('||', ',', ps_type.value)) AND (FIND_IN_SET('Значение2', replace('||', ',', ps_type.value)) AND FIND_IN_SET('Значение3', replace('||', ',', ps_type.value))))
    )");
 

$s = $q->prepare();
$sql = $q->toSQL();
print "<br />" . $sql . "\n";
 
$s->execute();
print_r($s->errorInfo());

while($row = $s->fetch(PDO::FETCH_ASSOC)){
    print_r($row);
}


Вот этот вариант уже вполне годится, чтобы передать параметром в вызов msProducts.

2. Менее удобно, но тоже вполне подходящий вариант: делать выборку id-шников подходящих товаров на уровне чистого SQL-запроса (выдернув необходимое из примера выше) и эти id-шники уже передавать в вызов msProducts. Так получится гораздо меньше чистого SQL-ля, который все-таки сложнее в обслуживании, чем запросы на базе xPDO.

UPD: По умолчанию длина строки, формируемой методом GROUP_CONCAT(), ограничена 1024 символами. Из-за этого может резаться результирующее значение.
Чтобы увеличить на лету это значение, перед выполняемым запросом делаем так:
$s = $this->modx->prepare('SET SESSION group_concat_max_len = 1000000;');
$s->execute();
2 комментария
М
Искал по запросам в документации, толком не нашел, или искал криво что ли. Хорошо написал, понятно.
Fi1osof1
Fi1osof 13 ноября 2015г в 10:57 #
Такое в документации не описано. А FIND_IN_SET() вообще в списке разрешенных методов не числится.
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.