shopProductsCollection и динамические списки Не принято

1

Получаю коллекцию по хэшу 'set/bestsellers' (список динамический, стандартный Хиты продаж). Смотрю количество и состав - все верно: 8 товаров, такие-то id. Далее говорю

$collection->addWhere('p.id in('.$pids.')');

где $pids - порядка 50 id существующих товаров (можно и перечень id всех товаров), в числе которых и 8 id присутствующих в списке(коллекции) товаров. Исполнив далее

$collection->getProducts('id', 0, $collection->count())

получаем на выходе 8 товаров с абсолютно иными id. А ожидалось, в данном пусть и слегка утрированном примере, получить все те же 8 id которые и были изначально в списке и коллекции.

10 комментариев

  • +1
    Михаил Проектов Михаил Проектов 2 ноября 2016 05:44 #

    Динамические списки реализованы через ORDER BY и LIMIT.
    Когда в условиях появляется WHERE он лишь ограничивает выборку перед сортировкой.
    Вероятно тот список из 50 таки не содержит изначальные 8 ID.
    Проверил у себя и работает как и ожидалось.

    Создаем два стандартных динамических списка "Хиты продаж".
    В первом ограничение 8 - best8, во втором 50 - best50.
    По ним делаем коллекции:

    $collection = new shopProductsCollection('set/best8');
    $filter = new shopProductsCollection('set/best50');
    

    Посмотрим исходный список:

    wa_dumpc(
    	array_keys($collection->getProducts('id'))
    );
    Array
    (
      0 => 1891
      1 => 783
      2 => 1886
      3 => 1892
      4 => 1890
      5 => 1996
      6 => 1885
      7 => 1888
    )

    Получаем id из второго списка, перемешиваем для чистоты эксперимента и добавляем условие в коллекцию:

    $keys = array_keys($filter->getProducts('id'));
    shuffle($keys);
    $pids = implode(',',$keys);
    $collection->addWhere('p.id in ('.$pids.')');

    Смотрим что на выходе:

    wa_dumpc(
    	$collection->getSQL(),
    	array_keys($collection->getProducts('id'))
    );
    'FROM shop_product p WHERE p.id in (888,1043,1996,1892,2372,3094,2420,1884,708,3298,3300,3293,2119,3296,1105,1888,2422,1116,1886,524,710,2074,1887,2078,1885,1891,2530,3291,2024,1889,786,706,3297,783,3086,2472,3292,3348,1117,3299,2137,790,3294,1882,3091,1890,2594,2394,2595,1907)'
    
    Array
    (
      0 => 1891
      1 => 783
      2 => 1886
      3 => 1892
      4 => 1890
      5 => 1996
      6 => 1885
      7 => 1888
    )
    

    Получили те же 8 id.

    • +1
      Павел Трофимов Павел Трофимов 2 ноября 2016 13:12 #

      Михаил, отдельное спасибо за то, что не поленился и проверил :)

      Полез смотреть... И, о чудо, действительно все сработало. Немного копаний, и ошибка найдена. Список действительно не стандартный bestsellers. Видимо, менял условия и подзабыл. Список формируется по правилу "С высокой оценкой". При этом, т.к. все дело на тестовой установке, никаких оценок у товаров нет. Если вкратце, то вот код

              $collection = new shopProductsCollection('set/bestsellers');
              wa_dumpc(array_keys($collection->getProducts('id')));
              
              $filter = new shopProductsCollection();
              echo "Размер filter = ".$filter->count().'<br>';
              
              $keys = array_keys($filter->getProducts('id'));
              $pids = implode(',',$keys);
              $collection->addWhere('p.id in ('.$pids.')');
              
              wa_dumpc(array_keys($collection->getProducts('id')));

      и результат

      Array
      (
        0 => 33295
        1 => 33282
        2 => 33281
        3 => 33280
        4 => 33279
        5 => 33278
        6 => 33277
        7 => 33276
      )
      Размер filter = 788
      dumped from /var/www/debug/wa-apps/shop/plugins/bl/lib/actions/shopBlPluginBackendTest.controller.php line #16:
      
      Array
      (
        0 => 33541
        1 => 33527
        2 => 33526
        3 => 33525
        4 => 33524
        5 => 33523
        6 => 33522
        7 => 33521
      )

      Аналогичная картина по условиям С низкой оценкой, С зачеркнутой ценой, Самые дешевые.

      • +1
        Павел Трофимов Павел Трофимов 3 ноября 2016 11:50 #

        После уточнения тоже "не принято"? Тогда в чем мое заблуждение? А то некрасивых костылей понавтыкать пришлось...

        • +1
          Михаил Ушенин Михаил Ушенин Webasyst 3 ноября 2016 17:15 #

          Давайте тогда пока обсудим...

          Для экземпляра класса shopProductsCollection, созданного с хешем списка товаров вы вызываете метод addWhere(). В чём суть этого метода? Если переводить буквально, то "добавить условие WHERE" (с ключевым словом "AND"). Вот вы его и добавляете. Буквально — в SQL-запрос. А раз вы добавили дополнительное условие WHERE, то логично ожидать, что выбранный набор товаров может оказаться другим. Он и получается другим.

          Вы же не программист-новичок, который мог бы подумать, что, добавив "where", он получит постфильтрацию для уже выбранного зафиксированного набора товаров. Раз вы знаете, что такое WHERE в SQL, и явно добавляете условие WHERE, странно, что вы ожидаете не того, что сами же и делаете.

          Может быть, вам больше пригодится что-то вроде функции array_intersect_key()? Сначала получите список товаров из нужного списка и с помощью этой функции отфильтруйте те, которые не входят в ваш массив $pids.

          Или это у меня где-то заблуждение?.. Может, я не до конца понял вашу идею.

          • +1
            Михаил Проектов Михаил Проектов 4 ноября 2016 06:09 #

            Вероятно дело в том, как MySQL выдает результаты при одинаковых значениях полей сортировки.

            Простой тест демонстрирует, что не так как ожидает Павел.

            Сначала берем два значения без условия:

            CREATE TABLE order_test (id int, sort int);
            INSERT order_test VALUES (1,0),(2,0),(3,0),(4,0);
            SELECT id FROM order_test ORDER BY sort LIMIT 2;

            Получаем:

            +------+
            | id   |
            +------+
            |    4 |
            |    2 |
            +------+

            Теперь добавим условие:

            SELECT id FROM order_test WHERE id in (2,3,4)  ORDER BY sort LIMIT 2;

            И получаем:

            +------+
            | id   |
            +------+
            |    2 |
            |    3 |
            +------+
            • +1
              Павел Трофимов Павел Трофимов 7 ноября 2016 13:15 #
              А раз вы добавили дополнительное условие WHERE, то логично ожидать, что выбранный набор товаров может оказаться другим. Он и получается другим.

              Ну как бы логично, что я хочу получить другой но не набор, а состав товаров из исходного набора :) И запрос получается вполне корректным, т.е. идентичный исходному, но с дополнительными условиями. А дело-то, похоже, действительно в MySQL, как и говорит Михаил (Проектов)...

              Или это у меня где-то заблуждение?.. Может, я не до конца понял вашу идею.

              Нет, Михаил (Ушенин), вы все верно поняли. Это я погорячился на счет костылей. Все решается даже с этой заминкой от MySQL.

              Спасибо обоим Михаилам.

            • -3
              Николай Силаков Николай Силаков 8 ноября 2016 19:19 #

              Добрый день, (не знаю как писать в личку) можешь помочь лэндинг на сайт запилить?

            • +1
              Павел Трофимов Павел Трофимов 7 ноября 2016 14:40 #

              Мрак какой-то.... )) Нет, ребята, что-то тут не так... Дабы не быть многословным, просто приведу код


              $collection = new shopProductsCollection('set/bestsellers');
              $products = array_keys($collection->getProducts());
              $pids = array_keys($collection->getProducts('id'));
                      
              wa_dumpc($products, $pids);

              и результат

              Array
              (
                0 => 32740
                1 => 32739
                2 => 32756
                3 => 32757
                4 => 32758
                5 => 32759
                6 => 32760
                7 => 32761
              )
              
              Array
              (
                0 => 33295
                1 => 33282
                2 => 33281
                3 => 33280
                4 => 33279
                5 => 33278
                6 => 33277
                7 => 33276
              )

              WTF??? ))

            • +1
              Михаил Проектов Михаил Проектов 7 ноября 2016 22:11 #

              В церкви смрад и полумрак,
              Дьяки курят ладан. Нет!
              И в церкви все не так,
              Все не так, как надо.

              Путем несложных манипуляций достаем фактически отправляемые SQL запросы для обоих вариантов вызова метода getProducts(). Например, при настройках списка брать 8 товаров с самым низким рейтингом получаем

              $collection->getProducts()
              'SELECT p.* FROM shop_product p ORDER BY rating ASC LIMIT 8'

              $collection->getProducts('id')
              'SELECT p.id FROM shop_product p ORDER BY rating ASC LIMIT 8'

              А теперь Павел, если попробуете выполнить это в консоли MySQL, то опять убедитесь, что причина на стороне базы.

              По-прежнему неоднозначность результата вызвана одинаковыми значениями полей сортировки и неоднозначным поведением MySQL в таких ситуациях.

              Для получения "стабильных" результатов при одинаковых значениях полей сортировки, необходимо последним в ORDER BY добавить гарантированно уникальный ключ, например id.

              Изменение идеологическое и насколько оно необходимо в базовом коде приложения shop не готов спорить.

              В плагине можно пользоваться наследником класса shopProductsCollection с измененным методом getOrderBy()

              • +1
                Павел Трофимов Павел Трофимов 8 ноября 2016 01:46 #

                Михаил, как мне кажется, в этом случае это уже не столь важно. Я особо и не настаиваю теперь на том что это ошибка WA. Но, опять же, как мне кажется, до первых более или менее крупных разборок. Вот сложатся несколько факторов:

                1. Кто-то сделает плагин который будет брать товары из динамического списка с помощью рекомендуемых инструментов (читай getProducts('id'))
                2. Этот же кто-то даст возможность далее манипулировать результатом
                3. Пользователь воспользуется плагином
                4. Пользователь удалит товары полученные в результате работы плагина (только не те, которые ожидается, а те которые MySQL на душу положит).

                И если их будет 10-15 - ну чертыхнется и забудет. А если пара тысяч - тут уже другой коленкор... А казалось бы - все по инструкции, все штатно. В общем это такой рояль в кустах, который внезапно может целый концерт закатить.

                Ну и так уж, для разрядки: я уже доделал плагин который вполне мог пройтись по описанному выше сценарию. Но т.к. я имел счастье потоптаться на этом грабильном поле пару дней, то, ессно, выжег напалмом там где это было возможно все упоминания id. Но и осталось их достаточно... А кто даст гарантии что все такие чудеса вскрыты? Ну провел я пару сотен экспериментов, больше ничего в глаза не бросилось, вроде бы все в норме, без сюрпризов. А вдруг не все варианты учел? Что тогда? Пользователю же далеко по барабану с бубном какой модели тут пляски происходят, ему нужен ожидаемый результат. А как получить ожидаемый результат? А просто: надо не самокаты самокатить, а использовать предназначенные для этого велосипеды, в т.ч. getProduct('id').

                Все имхо, но, имхо, стоит задуматься.

                Добавить комментарий

                Чтобы добавить комментарий, зарегистрируйтесь или войдите