Использование WP_Query для выборки нескольких категорий с ограничением постов на категорию?

25 авг. 2010 г., 23:28:25
Просмотры: 14.3K
Голосов: 11

У меня есть 3 категории, в каждой по 15 постов. Мне нужно сделать ОДИН запрос к базе данных, который вернет только первые 5 постов из каждой категории. Как это можно реализовать?

$q = new WP_Query(array( 'post__in' => array(2,4,8), 'posts_per_page' => **ПЕРВЫЕ_5_ИЗ_КЖДОЙ_КАТЕГОРИИ** ));

Если это невозможно, что будет эффективнее: получить все посты родительской категории и фильтровать их в цикле или создать 3 отдельных запроса?

3
Комментарии

Как вы хотите их упорядочить?

MikeSchinkel MikeSchinkel
26 авг. 2010 г. 00:05:15

недавно опубликованные.. то есть 5 самых свежих из категории A, самые новые материалы из категории B и т.д.

Amit Amit
26 авг. 2010 г. 00:09:41

Хорошо, смотрите мой ответ ниже

MikeSchinkel MikeSchinkel
26 авг. 2010 г. 02:12:36
Все ответы на вопрос 3
11
17

То, что вам нужно, возможно, но потребует погружения в SQL, чего я стараюсь избегать, когда это возможно (не потому, что я не знаю SQL — я продвинутый SQL-разработчик, а потому что в WordPress лучше использовать API, когда это возможно, чтобы минимизировать будущие проблемы совместимости, связанные с потенциальными изменениями структуры базы данных).

SQL с оператором UNION — это вариант

Чтобы использовать SQL, вам понадобится оператор UNION в вашем запросе. Например, вот так (предполагая, что slugs ваших категорий — это "category-1", "category-2" и "category-3"):

SELECT * FROM wp_posts WHERE ID IN (
  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-1'
  LIMIT 5

  UNION

  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-2'
  LIMIT 5

  UNION

  SELECT tr.object_id
  FROM wp_terms t 
  INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  WHERE tt.taxonomy='category' AND t.slug='category-3'
  LIMIT 5
)

Можно использовать SQL UNION с фильтром posts_join

Используя вышеуказанное, вы можете просто выполнить запрос напрямую или использовать фильтр posts_join, как показано ниже. Обратите внимание: я использую PHP heredoc, поэтому убедитесь, что SQL; находится строго слева. Также обратите внимание, что я использовал глобальную переменную, чтобы вы могли определить категории вне хука, перечислив их slugs в массиве. Этот код можно поместить в плагин или в файл functions.php вашей темы:

<?php
global $top_5_for_each_category_join;
$top_5_for_each_category_join = array('category-1','category-2','category-3');
add_filter('posts_join','top_5_for_each_category_join',10,2);
function top_5_for_each_category_join($join,$query) {
  global $top_5_for_each_category_join;
  $unioned_selects = array();
  foreach($top_5_for_each_category_join as $category) {
    $unioned_selects[] =<<<SQL
SELECT object_id
FROM wp_terms t
INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy='category' AND t.slug='{$category}'
LIMIT 5
SQL;
  }
  $unioned_selects = implode("\n\nUNION\n\n",$unioned_selects);
  return $join . " INNER JOIN ($unioned_selects) categories ON wp_posts.ID=categories.object_id " ;
}

Но могут быть побочные эффекты

Конечно, использование хуков модификации запросов, таких как posts_join, может вызывать побочные эффекты, поскольку они действуют глобально на все запросы. Поэтому обычно нужно оборачивать модификации в условие if, чтобы применять их только при необходимости, и определить критерии для проверки может быть непросто.

Может, лучше сосредоточиться на оптимизации?

Однако, я предполагаю, что ваш вопрос связан скорее с оптимизацией, чем с возможностью выполнить запрос "топ-5 для 3 категорий", верно? Если это так, возможно, есть другие варианты с использованием WordPress API?

Лучше использовать Transients API для кэширования?

Я предполагаю, что ваши записи не меняются так часто, правильно? Что если вы примете три (3) запроса периодически и затем закэшируете результаты, используя Transients API? Вы получите поддерживаемый код и отличную производительность — даже лучше, чем с запросом UNION, потому что WordPress сохранит списки записей как сериализованный массив в одной записи таблицы wp_options.

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

<?php

$timeout = 4; // 4 часа
$categories = array('category-1','category-2','category-3');

include "wp-load.php";
$category_posts = get_transient('top5_posts_per_category');
if (!is_array($category_posts) || count($category_posts)==0) {
  echo "Привет каждые {$timeout} часа!";
  $category_posts = array();
  foreach($categories as $category) {
    $posts = get_posts("post_type=post&numberposts=5&taxonomy=category&term={$category}");
    foreach($posts as $post) {
      $category_posts[$post->ID] = $post;
    }
  }
  set_transient('top5_posts_per_category',$category_posts,60*60*$timeout);
}
header('Content-Type:text/plain');
print_r($category_posts);

Итог

Хотя да, вы можете сделать то, о чем просили, используя SQL-запрос с UNION и фильтр posts_join, вероятно, лучше использовать кэширование с Transients API.

Надеюсь, это поможет?

26 авг. 2010 г. 01:23:23
Комментарии

Кеширование через транзиенты — отличный способ снизить нагрузку на сайт.

hakre hakre
26 авг. 2010 г. 02:01:48

@Mike, отличная идея использовать транзиенты.

Chris_O Chris_O
26 авг. 2010 г. 03:41:30

@Mike, если бы я жил на вашем континенте, я бы записался к вам на курсы! еще раз спасибо!

Amit Amit
26 авг. 2010 г. 17:13:21

@Amit: ЛОЛ! :-)

MikeSchinkel MikeSchinkel
26 авг. 2010 г. 21:21:14

Разве нет способа ограничить хук join только для одного объекта WP_query?

Manny Fleurmond Manny Fleurmond
18 мая 2011 г. 01:15:51

@Manny Fleurmond - Обычно я советую задать отдельный вопрос (что вам в любом случае стоит сделать), но один из способов - создать подкласс WP_Query, добавить свойство и затем проверять его наличие.

MikeSchinkel MikeSchinkel
19 мая 2011 г. 19:44:34

можешь также сохранить transient на неопределённый срок и затем сбросить его при save_post? есть хороший пример (с использованием хука edit_term) в codex

helgatheviking helgatheviking
27 февр. 2012 г. 19:22:33

@helgatheviking - Конечно, если хочешь.

MikeSchinkel MikeSchinkel
7 сент. 2014 г. 01:25:15

@MikeSchinkel спустя 2 года, и я постоянно использую transients!

helgatheviking helgatheviking
7 сент. 2014 г. 01:40:19

@helgatheviking Мой ответ помог тебе начать?!?

MikeSchinkel MikeSchinkel
7 сент. 2014 г. 03:27:31

@MikeSchinkel Возможно? Я не могу точно сказать. Ты проверяешь пределы моей памяти. :)

helgatheviking helgatheviking
7 сент. 2014 г. 13:11:46
Показать остальные 6 комментариев
4

WP_Query() не поддерживает функциональность типа Первые X записей для каждой категории. Вместо WP_Query() вы можете использовать get_posts() для каждой из ваших категорий, то есть три раза:

<?php
$posts      = array();
$categories = array(2,4,8);
foreach($categories as $cat) {
  $posts[] = get_posts('numberposts=5&offset=1&category='.$cat);
}
?>

Теперь $posts содержит первые пять записей для каждой категории.

25 авг. 2010 г. 23:54:06
Комментарии

разве это не 3 запроса к базе данных? Мне хотелось узнать, можно ли сделать это за 1 запрос, потому что я думал, что это эффективнее.

Amit Amit
25 авг. 2010 г. 23:56:36

но ведь для этого и нужна база данных, верно? Запрашивать данные из неё. База данных создана для таких вещей. Вероятно, это быстрее, чем выполнять сложный SQL-запрос, о котором ты просишь. Не бойся использовать эти возможности. Если это сломает твой сайт, сообщи об этом здесь.

hakre hakre
26 авг. 2010 г. 00:05:45

хм.. понял, я не очень разбираюсь в эффективности php/mysql/баз данных (хотел бы почитать больше), был уверен, что меньше запросов — лучше, а затем уже сам разбирать результат.

Amit Amit
26 авг. 2010 г. 00:11:26

ну, вообще-то, конечно, больше — это больше, а меньше — это меньше. Но сначала попробуй и протестируй. Чем "хуже" твое программное обеспечение, тем больше у него потенциал для оптимизации. Так что лучше учиться на практике, потому что в итоге ты будешь писать лучшее ПО быстрее и лучше писать более быстрое ПО.

hakre hakre
26 авг. 2010 г. 00:15:11
0

Я не знаю способа получить первые пять записей для каждой из категорий в одном запросе. Если у вас когда-либо будет всего 45 записей, то обращение к базе данных один раз и получение пяти записей для каждой категории, вероятно, будет наиболее эффективным подходом. Однако обращение к базе данных три раза и объединение результатов — это не так уж плохо.

25 авг. 2010 г. 23:38:26