Как расширить WP_Query для включения пользовательской таблицы в запрос?

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

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

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

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

Я искал большую попытку найти ответ. Ни, я видел любое учебное руководство, чтобы помочь мне понять, как расширить класс WP_Query. Я видел ответы Mike Schinkel (расширяющий WP_Query) к подобным вопросам, но я действительно не понял, как применить его к моим потребностям. Было бы замечательно, если кто-то мог бы выручить меня с этим.

Ссылки на ответ Mike's согласно просьбе: Свяжитесь 1, Ссылка 2

32
23.05.2017, 15:40
5 ответов

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

Большое спасибо @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 я намеревался тестировать обе реализации. Я импортировал вокруг 47K пользователей из файла CSV для регистрации их на новой тестовой установке WordPress. Установка работала Двадцать Одиннадцать тем. После этого я работал, чтобы цикл заставил приблизительно 50 пользователей читать любого пользователя. Различие во время запроса и для @kaiser и для решения @m0r7if3r колебалось. решение @kaiser обычно брало приблизительно 2 - 5 secs для каждого запроса. Изменение, которое я предполагаю, происходит как запросы кэшей WordPress для более позднего использования. С другой стороны, решение @m0r7if3r продемонстрировало время запроса 0,02 мс в среднем. Для тестирования обоих решений у меня была индексация НА для leader_id столбца. Не индексируя во время запроса было значительное увеличение.

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

Я поразил удар решением @m0r7if3r, когда я должен был передать идентификатор подписчика функции фильтра posts_where. По крайней мере согласно моему знанию WordPress не позволяет средств передачи переменной к функциям файлового сервера. Можно использовать Глобальные переменные, хотя, но я хотел избежать globals. Я закончил тем, что расширил 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 мс.

14
19.02.2020, 21:55
  • 1
    , которое я никогда не говорил Вам, насколько я ценил обсуждение этого вопроса. Теперь, когда я узнал, что пропустил его, я имею, добавляет родитель Использования upvote :) –  kaiser 28.02.2014, 23:44

Можно сделать это с полностью решение 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() так, чтобы это не влияло на другие запросы.

8
19.02.2020, 21:55
  • 1
    Отредактированный Ваш A и добавил prepare(). Надежда Вы не возражаете против редактирования. И да: Уровень должен быть измерен OP. Так или иначе: Я все еще думаю, что это должно быть просто usermeta и ничто иное. –  kaiser 26.04.2012, 16:53
  • 2
    @m0r7if3r спасибо за попытку решения. Я просто добавил комментарий в ответ на ответ кайзера с проблемами по возможным проблемам масштабируемости. Примите его во внимание. –  John 26.04.2012, 16:54
  • 3
    @kaiser не возражает ни в малейшей степени, на самом деле я скорее ценю его :) –  mor7ifer 26.04.2012, 16:55
  • 4
    @m0r7if3r Спасибо. Наличие парней как Вы в самом общественном горном :) –  kaiser 26.04.2012, 16:59
  • 5
    Необходимо вставить функцию Ваш functions.php и сделайте add_filter() прямо перед query() метод WP_Query назван. Сразу после этого Вы должны remove_filter() так, чтобы это не влияло на другие запросы. Я не уверен, какова проблема с перезаписью URL была бы, я использовал posts_where во многих случаях и никогда замечаемый, что... –  mor7ifer 26.04.2012, 23:21

Тег шаблона

Просто поместите обе функции в Ваш 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
}

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

6
19.02.2020, 21:55
  • 1
    JOIN является намного более дорогим. Плюс: Как я упомянул, у нас нет данных тестирования, поэтому протестируйте оба ответа и просветите нас с Вашими результатами. –  kaiser 26.04.2012, 16:51
  • 2
    WP_Query работает с СОЕДИНЕНИЯМИ между таблицей сообщений и postmeta при запросах. Я видел, что использование памяти PHP наталкивается к загрузке 70 МБ - 200 МБ за страницу. Выполнение чего-то как этот со многими одновременными пользователями потребовало бы экстремальной инфраструктуры. Мое предположение было бы то, что с тех пор, WordPress уже реализует подобную технику, СОЕДИНЕНИЯ должны быть менее налоговыми по сравнению с работой с массивом идентификатора. –  John 26.04.2012, 17:00
  • 3
    @John, хороший для слушания. действительно хочу знать исходящее. –  kaiser 26.04.2012, 17:57
  • 4
    Хорошо вот результаты испытаний. Для этого я добавил о 47K пользователях из файла CSV. Позже, работал, чтобы цикл заставил первые 45 пользователей читать любого пользователя. Это привело к 3 704 951 записи, сохраненной к моей пользовательской таблице. Первоначально, решение @m0r7if3r дало мне время запроса 95 secs, которые снизились до 0,020 мс после включения индексации на leader_id столбце. Общая использованная память PHP составляла приблизительно 20 МБ. С другой стороны, Ваше решение взяло приблизительно 2 - 5 secs для запроса с индексацией НА. Общая использованная память PHP составляла приблизительно 117 МБ. –  John 27.04.2012, 16:32
  • 5
    я добавляю другой ответ (мы можем обработать и изменять/редактировать на том) как форматирование кода в комментариях просто сосет :P –  kaiser 28.04.2012, 12:11

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

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

    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 О Тесте Для этого я добавил о 47K пользователях из файла CSV. Позже, работал, чтобы цикл заставил первые 45 пользователей читать любого пользователя.

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

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

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

далее обработайте здесь

3
19.02.2020, 21:55
  • 1
    Отметьте теми, кто следовал за этим Q: Я в процессе измерения уровня при различных условиях и отправлю результат через день или 3. Это было чрезвычайно трудоемкой задачей из-за масштаба данных тестирования, которые должны быть сгенерированы. –  John 30.04.2012, 09:47

Важная правовая оговорка: надлежащий способ сделать это не должно изменять Вашу структуру таблицы, но использовать wp_usermeta. Затем Вы не должны будете создавать пользовательский SQL для запросов сообщений (хотя Вам все еще будет нужен некоторый пользовательский SQL для получения списка всех, которые сообщают конкретному супервизору - в разделе Admin, например). Однако начиная с 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 или taxonomies), необходимо будет, вероятно, коснуться больше чем одного из этих пунктов, например, fields ("ИЗБРАННАЯ" часть SQL-оператора), join (все Ваши таблицы, кроме той в Вашем "ИЗ" пункта), и возможно orderby.

Изменение пунктов

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

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

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

Сохранение исходных пунктов

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

Так, для сохранения существующего SQL в пунктах не забудьте добавлять к пунктам, не присваивают им (т.е.: использовать $join .= ' {NEW SQL STUFF}'; нет $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-> префиксом}. 'user_relationship', и это - перекрестная таблица. (Между прочим, я рекомендую genericize эта структура таблицы и превратить его в надлежащую перекрестную таблицу со следующими полями: 'relationship_id', 'user_id', 'related_user_id', 'relationship_type'; это намного более гибко и мощно..., но я отступаю).

Если я понимаю то, что Вы хотите сделать, Вы хотите передать идентификатор Лидера и затем видеть только сообщения Подписчиков того Лидера. Я надеюсь, что разобрался в этом. Если это не правильно, необходимо будет взять то, что я говорю и адаптирую его к потребностям. Я буду придерживаться Вашей структуры таблицы: у нас есть a leader_id и a follower_id. Таким образом, СОЕДИНЕНИЕ будет идти {$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;
}
15
19.02.2020, 21:55