Как расширить WP_Query, чтобы включить пользовательскую таблицу в запрос?

У меня были дни по этой проблеме. Первоначально было, как хранить данные пользователя-последователя в базе данных, для чего я получил пару хороших рекомендаций здесь, в WordPress Answers. После, следуя рекомендациям, я добавил новую таблицу следующим образом:

id  leader_id   follower_id
1   2           4
2   3           10
3   2           10

В приведенной выше таблице первая строка имеет пользователя с идентификатором 2, за которым следует пользователь с идентификатором 4. Во второй строке пользователь с идентификатором 3 сопровождается пользователем с идентификатором 10. Эта же логика применяется для третьей строки.

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

Я много искал, пытаясь найти ответ. Кроме того, я видел какой-либо учебник, который поможет мне понять, как расширить класс WP_Query. Я видел ответы Майка Шинкеля (расширение WP_Query) на подобные вопросы, но я действительно не понял, как применить его к моим потребностям. Было бы здорово, если бы кто-то помог мне с этим.

Ссылки на ответ Майка по просьбе: Ссылка 1 , Ссылка 2

28 голосов | спросил John 26 PMpThu, 26 Apr 2012 16:49:13 +040049Thursday 2012, 16:49:13

5 ответов


8

Я отвечаю на этот вопрос очень поздно, и мои извинения за то же самое. Я был слишком занят крайними сроками, чтобы следить за этим.

Большое спасибо @ m0r7if3r и @kaiser в предоставлении базовых решений, которые я мог бы расширить и реализовать в своем приложении. Этот ответ содержит подробную информацию о моей адаптации решений, предлагаемых @ m0r7if3r и @kaiser.

Во-первых, позвольте мне объяснить, почему этот вопрос задавали в первую очередь. Из вопроса и его комментариев можно было собрать, что я пытаюсь заставить WP_Query вытащить сообщения всех пользователей (лидеров), которые следует за данным пользователем (последователем). Связь между последователем и лидером сохраняется в пользовательской таблице follow. Наиболее распространенное решение этой проблемы - вывести идентификаторы пользователей всех лидеров последователя из следующей таблицы и поместить их в массив. См. Ниже:

global $wpdb;
$results = $wpdb->get_results($wpdb->prepare('SELECT leader_id FROM cs_follow WHERE follower_id = %s', $user_id));

foreach($results as $result)
    $leaders[] = $result->leader_id;

Когда у вас есть массив лидеров, вы можете передать его как аргумент WP_Query. См. Ниже:

if (isset($leaders)) $authors = implode(',', $leaders); // Necessary as authors argument of WP_Query only accepts string containing post author ID's seperated by commas

$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'author'            => $authors
);

$wp_query = new WP_Query( $args );

// Normal WordPress loop continues

Вышеприведенное решение является самым простым способом достижения моих желаемых результатов. Однако он не масштабируется. В тот момент, когда у вас есть последователь десятков и тысяч лидеров, результирующий массив идентификаторов лидеров станет чрезвычайно большим и заставит ваш сайт WordPress использовать 100 МБ - 250 МБ памяти при каждой загрузке страницы и, в конечном счете, разрушить сайт. Решение проблемы - запустить SQL-запрос непосредственно в базе данных и получить соответствующие сообщения. Вот когда пришло решение @ m0r7if3r. Следуя рекомендации @ kaiser, я решил проверить обе версии. Я импортировал около 47 тыс. Пользователей из файла CSV, чтобы зарегистрировать их на новой тестовой установке WordPress. На установке была установлена ​​тема Twenty Eleven. После этого я запустил цикл for, чтобы около 50 пользователей следовали за каждым другим пользователем. Разница во времени запроса для решения @kaiser и @ m0r7if3r была ошеломляющей. Решение kaiser обычно занимало от 2 до 5 секунд для каждого запроса. Вариант, который я предполагаю, происходит, когда WordPress кэширует запросы для последующего использования. С другой стороны, решение @ m0r7if3r продемонстрировало в среднем время запроса 0,02 мс. Для тестирования обоих решений я проиндексировал ON для столбца leader_id. Без индексации произошел резкий рост времени запроса.

Использование памяти при использовании решения на основе массива составляло около 100-150 МБ и сбрасывалось до 20 МБ при запуске прямого SQL.

Я ударил рельеф с помощью решения @ m0r7if3r, когда мне нужно было передать идентификатор повторителя в функцию фильтра posts_where. По крайней мере, по моим знаниям WordPress не позволяет передавать переменную функции фильтрации. Однако вы можете использовать глобальные переменные, но я хотел избежать глобальных переменных. Я в конечном итоге расширил WP_Query, чтобы, наконец, решить эту проблему. Итак, вот окончательное решение, которое я реализовал (на основе решения @ m0r7if3r).

class WP_Query_Posts_by_Leader extends WP_Query {
    var $follower_id;

    function __construct($args=array()) {
        if(!empty($args['follower_id'])) {
            $this->follower_id = $args['follower_id'];
            add_filter('posts_where', array($this, 'posts_where'));
        }

        parent::query($args);
    }

    function posts_where($where) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'follow';
        $where .= $wpdb->prepare(" AND post_author IN (SELECT leader_id FROM " . $table_name . " WHERE follower_id = %d )", $this->follower_id);
        return $where;
    }
}


$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'follower_id'       => $follower_id
);

$wp_query = new WP_Query_Posts_by_Leader( $args );

Примечание. В конечном итоге я попытался использовать вышеупомянутое решение с 1,2 миллионами записей в следующей таблице. Среднее время запроса составляло около 0,060 мс.

ответил John 22 Mayam12 2012, 11:31:26
11
  

Важная оговорка: правильный способ сделать это НЕ ИЗМЕНЕНИЯ   таблицу, но использовать wp_usermeta. Тогда вам не понадобится   создайте любой пользовательский SQL для запроса своих сообщений (хотя вам все равно понадобится   некоторые пользовательские SQL, чтобы получить список всех, кто сообщает об этом   супервизор - в разделе администратора, например). Однако, поскольку   OP спросил о написании пользовательского SQL, вот текущая лучшая практика   для ввода пользовательского SQL в существующий запрос WordPress.

Если вы выполняете сложные объединения, вы не можете просто использовать фильтр posts_where, потому что вам нужно будет изменить соединение, выбрать и, возможно, группу по или упорядочить по разделам запроса.

Лучше всего использовать фильтр posts_clauses. Это очень полезный фильтр (который не следует злоупотреблять!), Который позволяет добавлять /изменять различные части SQL, которые автоматически генерируются многими многими строками кода в ядре WordPress. Подпись обратного вызова фильтра: function posts_clauses_filter_cb( $clauses, $query_object ){ } и ожидает, что вы вернете $clauses.

Клаузы

$clauses - это массив, содержащий следующие ключи; каждый ключ представляет собой строку SQL, которая будет непосредственно использоваться в последнем операторе SQL, отправленном в базу данных:

  • , где
  • GroupBy
  • присоединиться к
  • OrderBy
  • отчетливый
  • поля
  • пределы литий>

Если вы добавляете таблицу в базу данных (это делается только в том случае, если вы абсолютно не можете использовать post_meta, user_meta или таксономии), вам, вероятно, придется коснуться более одного из этих предложений, например, fields (часть «SELECT» оператора SQL), join (все ваши таблицы, отличные от того, что указано в предложении FROM), и, возможно, orderby.

Изменение разделов

Лучший способ сделать это - выровнять соответствующий ключ из массива $clauses, который вы получили от фильтра:

$join = &$clauses['join'];

Теперь, если вы измените $join, вы фактически будете напрямую модифицировать $clauses['join'], чтобы изменения были в $clauses, когда вы его возвращаете.

Сохранение исходных статей

Скорее всего (нет, серьезно, слушайте), вы захотите сохранить существующий SQL, созданный для вас WordPress. Если нет, скорее всего, вы должны посмотреть на posts_request фильтр - это полный запрос mySQL непосредственно перед отправкой в ​​базу данных, чтобы вы могли полностью скрыть его. Зачем вам это делать? Вероятно, вы этого не сделаете.

Итак, чтобы сохранить существующий SQL в предложениях, не забудьте добавить к предложениям, а не назначать им (например: use $join .= ' {NEW SQL STUFF}'; not $join = '{CLOBBER SQL STUFF}';. Обратите внимание, что поскольку каждый элемент массива $clauses является строкой, если вы хотите добавить к ней, вы Вероятно, вы захотите вставить пробел перед любыми другими жетонами символов, иначе вы, вероятно, создадите синтаксическую ошибку SQL.

Вы можете просто предположить, что в каждом из предложений всегда будет что-то, и поэтому не забудьте запустить каждую новую строку с пробелом, как в: $join .= ' my_table, или вы всегда может добавить небольшую строку, которая добавляет только пространство, если вам нужно:

$join = &$clauses['join'];
if (! empty( $join ) ) $join .= ' ';
$join .= "JOIN my_table... "; // <-- note the space at the end
$join .= "JOIN my_other_table... ";


return $clauses;

Это стилистическая вещь больше всего. Важный бит для запоминания: всегда оставлять пробел перед вашей строкой, если вы добавляете к предложению, в котором уже есть SQL-код!

Совмещение

Первое правило разработки WordPress - попытаться использовать как можно больше функциональных возможностей ядра. Этолучший способ будущего доказательства вашей работы. Предположим, что основная команда решает, что WordPress теперь будет использовать SQLite или Oracle или какой-либо другой язык базы данных. Любой написанный вручную mySQL может стать недействительным и сломать ваш плагин или тему! Лучше позволить WP генерировать как можно больше SQL, и просто добавлять нужные вам биты.

Итак, первый порядок ведения бизнеса - это использование WP_Query, чтобы генерировать как можно больше вашего базового запроса. Точный метод, который мы используем для этого, во многом зависит от , где должен отображаться этот список сообщений. Если это часть раздела (а не основной запрос), вы должны использовать get_posts(); если это основной запрос, я полагаю, вы могли бы использовать query_posts() и выполняться с ним, но правильный способ сделать это - перехватить основной запрос до того, как он попадет в базу данных (и потребляет серверные циклы ), поэтому используйте фильтр request.

Хорошо, поэтому вы создали свой запрос, и SQL будет создан. Ну, на самом деле, он был создан, просто не отправлен в базу данных. Используя фильтр posts_clauses, вы собираетесь добавить таблицу отношений своих сотрудников в микс. Назовем эту таблицу {$ wpdb-> prefix}. 'user_relationship', и это таблица пересечений. (Кстати, я рекомендую вам обобщить эту структуру таблиц и превратить ее в соответствующую таблицу пересечений со следующими полями: «relationship_id», «user_id», «related_user_id», «relationship_type», это гораздо более гибкий и мощный. .. но я отвлекся).

Если я понимаю, что вы хотите сделать, вы хотите передать идентификатор лидера, а затем просмотреть только записи от подписчиков Лидера. Надеюсь, я прав. Если это не так, вам придется взять то, что я говорю, и адаптировать его к вашим потребностям. Я придерживаюсь вашей структуры таблицы: у нас есть leader_id и follower_id. Таким образом, JOIN будет на {$wpdb->posts}.post_author в качестве внешнего ключа для 'follower_id' в вашей таблице user_relationship.

add_filter( 'posts_clauses', 'filter_by_leader_id', 10, 2 ); // we need the 2 because we want to get all the arguments

function filter_by_leader_id( $clauses, $query_object ){
  // I don't know how you intend to pass the leader_id, so let's just assume it's a global
  global $leader_id;

  // In this example I only want to affect a query on the home page.
  // This is where the $query_object is used, to help us avoid affecting
  // ALL queries (since ALL queries pass through this filter)
  if ( $query_object->is_home() ){
    // Now, let's add your table into the SQL
    $join = &$clauses['join'];
    if (! empty( $join ) ) $join .= ' '; // add a space only if we have to (for bonus marks!)
    $join .= "JOIN {$wpdb->prefix}employee_relationship EMP_R ON EMP_R.follower_id = {$wpdb->posts}.author_id";

    // And make sure we add it to our selection criteria
    $where = &$clauses['where'];
    // Regardless, you always start with AND, because there's always a '1=1' statement as the first statement of the WHERE clause that's added in by WP/
    // Just don't forget the leading space!
    $where .= " AND EMP_R.leader_id={$leader_id}"; // assuming $leader_id is always (int)

    // And I assume you'll want the posts "grouped" by user id, so let's modify the groupby clause
    $groupby = &$clauses['groupby'];
    // We need to prepend, so...
    if (! empty( $groupby ) ) $groupby = ' ' . $groupby; // For the show-offs
    $groupby = "{$wpdb->posts}.post_author" . $groupby;
  }

  // Regardless, we need to return our clauses...
  return $clauses;
}
ответил Tom Auger 2 Mayam12 2012, 01:49:43
8

Вы можете сделать это с помощью полностью SQL-решения, используя фильтр posts_where. Вот пример этого:

if( some condition ) 
    add_filter( 'posts_where', 'wpse50305_leader_where' );
    // lol, question id is the same forward and backward

function wpse50305_leader_where( $where ) {
    $where .= $GLOBALS['wpdb']->prepare( ' AND post_author '.
        'IN ( '.
            'SELECT leader_id '.
            'FROM custom_table_name '.
            'WHERE follower_id = %s'.
        ' ) ', $follower_id );
    return $where;
}

Я думаю, что может быть способ сделать это с помощью JOIN, но я не могу придумать это. Я буду продолжать играть с ним и обновлять ответ, если я его получу.

В качестве альтернативы, предлагаемый @kaiser , вы можете разделить его на две части: получение лидеров и выполнение запроса. У меня такое чувство, что это может быть менее эффективным, но это, безусловно, более понятный путь. Вам нужно будет проверить эффективность для себя, чтобы определить, какой метод лучше, поскольку вложенные SQL-запросы могут быть довольно медленными.

ОТ КОММЕНТАРИЙ:

Вы должны поместить функцию в свой functions.php и сделать add_filter() прямо перед методом query() WP_Query. Сразу после этого вы должны remove_filter(), чтобы он не влиял на другие запросы.

ответил mor7ifer 26 PMpThu, 26 Apr 2012 17:40:48 +040040Thursday 2012, 17:40:48
5

Тег шаблона

Просто разместите обе функции в файле functions.php. Затем отрегулируйте 1-ю функцию и добавьте свое собственное имя таблицы. Затем вам понадобится попытка /ошибка, чтобы избавиться от текущего идентификатора пользователя внутри результирующего массива (см. Комментарий).

/**
 * Get "Leaders" of the current user
 * @param int $user_id The current users ID
 * @return array $query The leaders
 */
function wpse50305_get_leaders( $user_id )
{
    global $wpdb;

    return $wpdb->query( $wpdb->prepare(
        "
            SELECT `leader_id`, `follower_id`
            FROM %s
                WHERE `follower_id` = %s
            ORDERBY `leader_id` ASC
        ",
        // Edit the table name
        "{$wpdb->prefix}custom_table_name"
        $user_id
    ) );
}

/**
 * Get posts array that contain posts by 
 * "Leaders" the current user is following
 * @return array $posts Posts that are by the current "Leader
 */
function wpse50305_list_posts_by_leader()
{
    get_currentuserinfo();
    global $current_user;

    $user_id = $current_user->ID;

    $leaders = wpse5035_get_leaders( $user_id );
    // could be that you need to loop over the $leaders
    // and get rid of the follower ids

    return get_posts( array(
        'author' => implode( ",", $leaders )
    ) );
}

Внутри шаблона

Здесь вы можете делать все, что хотите, с результатами.

foreach ( wpse50305_list_posts_by_leader() as $post )
{
    // do something with $post
}
  

ПРИМЕЧАНИЕ У нас don'tt есть testdata и т. д., поэтому выше это немного угадающая игра. Убедитесь, что отредактируйте этот ответ с тем, что сработало для вас, поэтому у нас есть удовлетворительный результат для более поздних читателей. Я одобрю редактирование, если у вас слишком низкая репутация. Затем вы можете удалить эту заметку. Спасибо.

ответил kaiser 26 PMpThu, 26 Apr 2012 17:28:29 +040028Thursday 2012, 17:28:29
3
  

Примечание. Этот ответ заключается в том, чтобы избежать расширенного обсуждения в комментариях

  1. Вот код OPs из комментариев, чтобы добавить первый набор тестовых пользователей. Я должен быть изменен в реальном мире.

    for ( $j = 2; $j <= 52; $j++ ) 
    {
        for ( $i = ($j + 1); $i <= 47000; $i++ )
        {
            $rows_affected = $wpdb->insert( $table_name, array( 'leader_id' => $i, 'follower_id' => $j ) );
        }
    }
    
      

    OP About Test . Для этого я добавил около 47K пользователей из файла csv. Позже вы запустили цикл for, чтобы первые 45 пользователей следовали за каждым другим пользователем.

         
    • Это привело к тому, что в мою пользовательскую таблицу было сохранено 3,704,951 записей.
    •   
    • Первоначально решение @ m0r7if3r давало мне время запроса 95 секунд, которое снизилось до 0,020 мс после включения индексации в столбце leader_id. Общая потребляемая память PHP составляла около 20 МБ.
    •   
    • С другой стороны, ваше решение заняло от 2 до 5 секунд для запроса с индексированием ON. Общая потребляемая память PHP составляла около 117 МБ.
    •   
  2. Мой ответ на этот тест:

      

    более «реальная жизнь»: пусть каждый пользователь следует $leader_amount = rand( 0, 5 );, а затем добавляет число $leader_amount x $random_ids = rand( 0, 47000 ); каждому пользователю. Пока что мы знаем: мое решение было бы крайне плохим, если пользователь будет следовать друг за другом. Далее: вы увидите, как вы выполнили тест и где именно вы добавили таймеры.

    Я также должен сказать, что отслеживание времени выше, не может быть действительно измерено, так как также потребуется время, чтобы вычислить цикл вместе. Лучше было бы перебрать результирующий набор идентификаторов во втором цикле.

дальнейший процесс здесь

ответил kaiser 28 PMpSat, 28 Apr 2012 13:14:37 +040014Saturday 2012, 13:14:37

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132