pdoFetch поиск в TV-полях с разделителем ||

Наверняка много кто использовал в своей практике TV-поля типа «Список» (множественный, одиночный, не важно) или «Тег». Здесь важно именно то, в каком виде в базе данных хранятся данные таких полей, ведь они сохраняют множественные значения. А хранят они их одной общей строкой в той же самой таблице ТВшек (естественно, по одной записи на один документ). К примеру, если мы отметили три значения 100, 1005, 10, в БД это запишется 100||1005||10. И здесь возникает вопрос как потом выполнить поиск по таким полям? Простой LIKE здесь не поможет, так как LIKE %100%, к примеру, найдет и '100||1000' и '1001||1005'. Данного вопроса мы уже касались здесь и подобный вопрос опять возник здесь.

В подобных ситуациях нам в помощь SQL-метод FIND_IN_SET(). Он как раз и позволяет выполнять поиск с точным вхождениям в строках с разделителем-запятой. Правда, в нашем случае придется выполнить еще одну операцию — замену разделителя || на запятую. В этом нам поможет SQL-метод REPLACE.

Посмотрев код pdoFetch, на сколько я понял, через параметр tvFilters этого сделать нельзя, так как у него там ограниченный набор элементов сравнения (по умолчанию LIKE). А здесь нужен FIND_IN_SET(). Мы этот вопрос разбирали еще здесь. Здесь нам в помощь параметр where. В него можно передавать дополнительные условия поиска массивом или JSON-строкой.
Примерный запрос выглядит так:
[[!pdoPage?
&limit=`18`
&includeTVs=`categories`
&where=`["1 = 1 AND FIND_IN_SET('[[*id]]', replace(categories, '||', ','))"]`
]]
В данном случае replace(categories, '||', ',') заменит все разделители || на, (зпт), что позволит искать четкие вхождения в составной строке методом FIND_IN_SET().
1 = 1 необходимо, чтобы обойти ограничения xPDO, который имеет определенный набор разрешенных методов. Если мы этого не сделаем, то получим примерно такую ошибку: Error parsing condition with key 0: FIND_IN_SET(…
Fi1osof
06 декабря 2015, 10:14
modx.pro
37
6 139
+16

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

Василий Наумкин
06 декабря 2015, 14:07
+3
Отличный способ!

А &tvFilters нужен исключительно для совместимости с getResources. Никому не рекомендую его использовать.
    Fi1osof
    06 декабря 2015, 14:10
    +3
    Рад, что не напрасно писал :)
      Ник
      23 января 2017, 16:46
      0
      Николай, очень нужная информация, долго искал, но с выборкой из migx не работает.
      У меня запрос такой:
      [[!pdoPage?
                &element=`getImageList`
                &tvname=`catalogs_pdf`
                &tpl=`tpl_catalogs_pdf`
                &docid=`7`
      	  &includeTVs=`id_pages`
                &where=`["1 = 1 AND FIND_IN_SET('[[*id]]', replace(id_pages, '||', ','))"]`
              ]]
      id_pages представляет собой простую строку, куда я вручную прописываю id ресурсов (пример — 5||20||13).
      Третий день бьюсь. Испробовал кучу вариантов, но нет.
        Fi1osof
        23 января 2017, 20:48
        1
        0
        Не пользуюсь getImageList. Но уверен на 98%, он формирует SQL-запросы стандартным xPDO-механизмом, а не как у Василия. И на сколько я могу судить по документации, getImageList не умеет принимать параметр includeTVs и ему в принципе пофиг на &includeTVs=`id_pages`. Таким образом колонки id_pages в запросе просто не будет. Но повторюсь, могу ошибаться, так как ни с pdoPage, ни с getImageList не работаю. Может кто другой подскажет.
          Ник
          23 января 2017, 22:30
          0
          Да, &includeTVs я на всякий случай). Спасибо за оперативный ответ.
            Fi1osof
            23 января 2017, 22:31
            0
            Не за что
          Алексей Смирнов
          24 января 2017, 14:43
          0
          getImageList принимает и выводит исключительно TV.
          Как вариант сделать следующее:
          [[!getImageList?
                    &tvname=`catalogs_pdf`
                    &tpl=`@CODE:[[+item:replace=`||==,`]]` // Вроде бы так. - заменяем ваш разделитель на зпт.
                    &docid=`7`
          &toPlaceholder=`asdasd`
                  ]]
          [[!pdoPage? 
          &parents=`0` // или ваша вложенность
          &resources=`[[!+asdasd]]`
          &element=`pdoResources`
          ]]
          Надеюсь смысл понятен. :) Синтаксис не проверял. Показал принцип.
            Ник
            24 января 2017, 15:50
            0
            Alex, если не трудно, разжуйте поподробнее.
            Я в свою очередь тоже, т.к. боюсь меня не правильно поняли.
            В базе Migx хранятся каталоги с описанием, картинкой и т.д.
            Помимо родного ресурса эти каталоги нужно вывести и на многих других.
            Соответственно, на этом этапе нужна выборка определенных каталогов для определенного ресурса.
            Задачу пытался решить так:
            создал в Migx базе еще одно поле, где вручную указываю id ресурсов (пример — 5||20||13).
            А на странице определенного ресурса уже вызов сниппета, который написал выше, только рабочий вызов where пока такой:
            &where=`{"id_pages:=":"[[*id]]"}`
            Проблема в том, что в id_pages иногда приходится вставлять несколько id, тогда не работает.
              Алексей Смирнов
              24 января 2017, 17:18
              0
              Так, наверное тут нужно сначала разобраться со структурой.
              У вас Есть Родитель, У него Ресурсы, у ресурсов TV (MIGX) с Описаниями, картинками? MIGX настроен обычным способом.
              Вам нужно вывести на любой странице определенные ресурсы, которые вы указываете в другом родителе через TV поле?
              Скиньте снипет ваш который рабочий, т.к. сложно понять что сейчас у вас не так. :)
                Ник
                24 января 2017, 17:33
                0
                У меня есть ресурс — страница со всеми каталогами. Все каталоги собсна хранятся в Migx базе. Моя задача выводить некоторые каталоги на других ресурсах помимо родного. Для этого я создал доп поле в этой же базе migx, куда указываю нужные id страницы для каждого каталога.
                Вызов сниппета на других ресурсах такой:
                [[!pdoPage?
                          &element=`getImageList`
                          &tvname=`catalogs_pdf`
                          &tpl=`tpl_catalogs_pdf`
                          &docid=`7`
                          &where=`{"id_pages:in":"[[*id]]"}`
                        ]]
                Но он работает только если в этом поле указан 1 id. А надо бы, чтобы сниппет мог отыскать id необходимого ресурса среди нескольких значений.
      Ник
      24 января 2017, 18:44
      0
      Спасибо всем, кто отозвался. Проблема решилась банально и просто, как и должно было быть. Только сколько пришлось попотеть…

      Кому будет тоже нужно:

      В поле id_pages, id ресурсов нужно прописать так, чтобы их однозначно можно было определить.
      Например, так:
      2#3#
      Условие where переписать на следующее:
      &where=`{"id_pages:contains":"[[*id]]#"}`
      в итоге рабочий вызов будет таким:
      [[!pdoPage?
                &element=`getImageList`
                &tvname=`catalogs_pdf`
                &tpl=`tpl_catalogs_pdf`
                &docid=`7`
                &where=`{"id_pages:contains":"[[*id]]#"}`
              ]]
      Подсказали тут: itchief.ru/lessons/modx-revo/modx-migx
        Максим
        10 августа 2017, 18:17
        0
        Здравствуйте, подскажите пожалуйста как применить такой запрос [«1 = 1 AND FIND_IN_SET('[[*id]]', replace(categories, '||', ','))»] в своем сниппете?

        Пробовал так:

        $param['parent'] = 6;
        $param['where'] = ["1 = 1 AND FIND_IN_SET('Москва', from_city)"];
        
        $params = array_diff($param, array(''));
            
        $q = $modx->newQuery('modResource', $params);
        $q->limit(1000);
        
        $q->prepare();
        $q->stmt->execute();
        $res = $q->stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach ($res as $v) {
        	echo $v['modResource_pagetitle'];
        }
        Но результата нет
          Fi1osof
          10 августа 2017, 18:56
          0
          AND FIND_IN_SET('[[*id]]', работать не будет, так как это MODX-плейсхолдер, а не конечное значение. Формируя таким образом запрос, вы в БД ищете не id текущего документа, а конкретно строчное значение '[[*id]]'.
          Надо так:
          [«1 = 1 AND FIND_IN_SET({$modx->resource->id}, replace(categories, '||', ','))»]
          или
          [«1 = 1 AND FIND_IN_SET({$modx->getPlaceholder('id')}, replace(categories, '||', ','))»]
          Это если в сниппете. Если вы в феном или еще где-то пишете, может чуть отличаться синтаксис, но смысл, думаю, вы уловили.
            Сергей Шлоков
            10 августа 2017, 22:12
            0
            Вместо
            "1 = 1 AND FIND_IN_SET('Москва', from_city)"
            можно (правильней) написать
            "FIND_IN_SET('Москва', from_city) > 0"
              Fi1osof
              10 августа 2017, 22:26
              0
              1 = 1 AND вводилось для обхода проверки xPDO на метод FIND_IN_SET (раньше он его не знал и обламывал, не знаю как он сейчас на него реагирует. Если положительно, то можно 1=1 и не писать).

              можно (правильней) написать
              «FIND_IN_SET('Москва', from_city) > 0»
              Тогда уж и вовсе «FIND_IN_SET('Москва', from_city)», так как результат этой функции проверяется на true|false.
                Максим
                11 августа 2017, 01:36
                0
                скажите правильно ли я понял, должны быть так?
                $param['where'] = ["FIND_IN_SET('Москва', from_city) > 0"];
              Максим
              22 января 2019, 17:49
              0
              А как переписать это условие на fenom? Я всю голову сломал, пытаясь это сделать.
                Fi1osof
                22 января 2019, 19:45
                0
                Я не знаю как правильней всего в феном, но можете там использовать объект $_modx. Вот изучите вот эти примеры: modxclub.ru/topics/eshhe-odin-primer-slozhnyix-sql-zaprosov-1922.html
                Только там нативный объект $modx, в феноме вам надо использовать $_modx, вроде должно работать.
                Илья
                29 июня 2022, 17:26
                0
                Здравствуйте!
                Подскажите, у меня вот такая конструкция:
                'where' => '{"portfolio_name:LIKE":"%[[*menutitle]]%","video.value:!=":1 }'
                Я вывожу видео из портфолио в товарах проверяя совпадения по названию товара. Но как и ожидалось, выводятся видео с похожими названиями. К примеру: — Калуга||Луга
                Пытаюсь вывести по вашему примеру:
                'where' => ["1 = 1 AND FIND_IN_SET('[[*portfolio_name]]', replace(menutitle, '||', ','))"]
                Но не пойму, как сделать ещё одни запрос «video.value:!=»:1

                Извините, я не спец…
                Спасибо!
                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                  19