我已经在这个问题上待了几天。最初是如何在数据库中存储用户的关注者数据,在WordPress Answers上我得到了一些不错的建议。之后,按照建议,我添加了一个新表,如下所示:

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


在上表中,第一行的用户ID为2后跟一个ID为4的用户。在第二行中,一个ID为3的用户后跟一个ID为10的用户。相同的逻辑适用于第三行。 >现在,从本质上讲,我想扩展WP_Query,以便我可以将仅由用户负责人提取的帖子限制为该帖子。因此,考虑到上表,如果我要将用户ID 10传递给WP_Query,则结果应仅包含用户ID 2和用户ID 3的帖子。

我进行了大量搜索,找到答案。同样,我也没有看过任何教程可以帮助我了解如何扩展WP_Query类。我已经看到Mike Schinkel的答案(将WP_Query扩展到类似的问题),但是我真的不明白如何将其应用于我的需求。

按要求链接到Mike的答案:
链接1,链接2

评论

请添加指向Mikes答案的链接。

您能否举一个您要查询的内容的示例? WP_Query用于获取帖子,但我无法理解它与帖子的关系。

@kaiser我已经用迈克的答案链接更新了问题。

@ m0r7if3r»我想扩展WP_Query,以便可以限制仅由用户的领导者«提取的帖子,类似于“按作者获取帖子”。

@ m0r7if3r帖子正是我需要查询的。但是要获取的帖子应该由在定制表中被列为某个用户的领导者的用户完成。因此,换句话说,我想告诉WP_Query,去获取在自定义表中被列为ID为“ 10”的用户的领导者的所有用户的所有帖子。

#1 楼


重要免责声明:执行此操作的正确方法不是修改您的表结构,而是使用wp_usermeta。然后,您将不需要
创建任何自定义SQL来查询您的帖子(尽管您仍然需要
自定义SQL来获取向特定主管报告的每个人的列表-在例如“管理”部分)。但是,由于
OP询问编写自定义SQL的问题,因此,这是将自定义SQL注入到现有WordPress查询中的当前最佳实践。


要进行复杂的联接,您不能只使用posts_where过滤器,因为您将需要修改联接,选择以及查询的分组依据或顺序。

您最好的选择是使用“ posts_clauses”过滤器。这是一个非常有用的过滤器(请勿滥用!),它使您可以附加/修改由WordPress核心内的许多行代码自动生成的SQL的各个部分。过滤器回调签名为:
function posts_clauses_filter_cb( $clauses, $query_object ){ },希望您返回$clauses

子句

$clauses是一个包含以下键的数组;每个键都是一个SQL字符串,将在发送到数据库的最终SQL语句中直接使用:


其中
groupby
字段
限制

如果要向数据库添加表(仅当您绝对不能利用post_meta,user_meta或分类法时才这样做)您可能需要触摸多个这些子句之一,例如,fields(SQL语句的“ SELECT”部分),join(您的所有表,而不是“ FROM”子句中的一个)也许是orderby。 br />
$join = &$clauses['join'];


现在,如果您修改$clauses,则实际上将直接修改$join,以便在返回时所做的更改将在$clauses['join']中。不,请认真听讲),您将希望保留WordPress为您生成的现有SQL。如果没有,您可能应该改用$clauses过滤器-那是在将其发送到数据库之前的完整mySQL查询,因此您可以完全用自己的过滤器。你为什么想做这个?

因此,为了保留子句中的现有SQL,请记住将其附加到子句中,而不要分配给它们(即:使用posts_request而不是$join .= ' {NEW SQL STUFF}';。请注意,因为每个$join = '{CLOBBER SQL STUFF}';数组的元素是字符串,如果要追加到字符串,则可能要在其他任何字符标记之前插入一个空格,否则可能会产生一些SQL语法错误。 >您可以假设每个子句中总会有东西,因此请记住,每个新字符串都以空格开头,例如:$clauses,或者,您总可以添加一行,仅在需要时添加空格to:

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


return $clauses;


这是一个风格方面的事情,要记住的重要一点是:如果要附加到子句,请始终在字符串之前留一个空格

将其放在一起

WordPress开发的第一条规则是尝试使用尽可能多的核心功能。可以这是将来证明您的工作的最佳方法。假设核心团队决定WordPress现在将使用SQLite或Oracle或其他某种数据库语言。任何手写的mySQL都可能无效并破坏您的插件或主题!最好让WP自己生成尽可能多的SQL,而只需添加所需的位。

因此,第一笔业务是利用$join .= ' my_table生成尽可能多的基本查询。我们用于执行此操作的确切方法在很大程度上取决于该帖子列表应该出现的位置。如果它是页面的一部分(不是您的主要查询),则应使用WP_Query;如果它是主要查询,我想您可以使用get_posts()并完成它,但是正确的方法是在到达数据库之前拦截主要查询(并消耗服务器周期),因此请使用query_posts()过滤器。

好,因此,您已经生成了查询,并且即将创建SQL。好吧,实际上,它已经创建,只是没有发送到数据库。通过使用request过滤器,您将要添加员工关系表。我们将此表称为{$ wpdb-> prefix}。 'user_relationship',它是一个交集表。 (顺便说一句,我建议您对这个表结构进行通用化处理,然后将其转换为具有以下字段的正确交集表:'relationship_id','user_id','related_user_id','relationship_type';这将更加灵活和强大。 ..但是我离题了。我希望我没错。如果不正确,您将不得不接受我所说的话,并使其适应您的需求。我会坚持使用您的表结构:我们有一个posts_clauses和一个leader_id。因此,JOIN将作为follower_id上的“ user_relationship”表上“ follower_id”的外键。

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;
}


#2 楼

我很晚才回答这个问题,对此我深表歉意。我太忙于截止日期了,无法参加这个会议。此答案详细说明了我对@ m0r7if3r和@kaiser提供的解决方案的适应情况。

首先,让我解释一下为什么首先提出这个问题。从问题及其评论中可以得出一个结论,我正在尝试让WP_Query提取给定用户(跟随者)所遵循的所有用户(领导者)的帖子。跟随者和领导者之间的关系存储在定制表follow中。解决此问题的最常见方法是从关注表中提取关注者的所有领导者的用户ID,并将其放置在数组中。参见下文:

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


上面的解决方案是实现我想要的结果的最简单方法。但是,它是不可扩展的。当您具有跟随成千上万名领导者的关注者时,领导者ID的结果数组将变得非常大,并迫使您的WordPress网站在每个页面加载时使用100MB-250MB的内存,最终使网站崩溃。该问题的解决方案是直接在数据库上运行SQL查询并获取相关帖子。那就是@ m0r7if3r的解决方案来解救的时候。按照@kaiser的建议,我着手测试这两种实现。我从CSV文件导入了约47K用户,以在全新的WordPress测试安装中注册他们。安装运行“二十一”主题。接下来,我运行了一个for循环,使大约50个用户跟随其他每个用户。 @kaiser和@ m0r7if3r的解决方案在查询时间上的差异是惊人的。 @kaiser的解决方案通常每个查询大约需要2到5秒。我认为这种变化是在WordPress缓存查询以供以后使用时发生的。另一方面,@ m0r7if3r的解决方案显示平均查询时间为0.02 ms。为了测试这两个解决方案,我为leader_id列的索引设置为ON。如果不建立索引,查询时间将急剧增加。 br />当我需要将关注者ID传递给posts_where过滤器功能时,我对@ m0r7if3r的解决方案感到震惊。据我所知,Atleast WordPress不允许将变量传递给文件管理器函数。您可以使用全局变量,但是我想避免使用全局变量。我最终扩展了WP_Query以最终解决该问题。所以这是我实现的最终解决方案(基于@ m0r7if3r的解决方案)。

表。平均查询时间约为0.060毫秒。

评论


我从未告诉过您我对这个问题的讨论有多满意。现在,我发现自己错过了,我添加了一个赞:)

– kaiser
2014年2月28日在21:44

#3 楼

您可以使用posts_where过滤器使用完全SQL解决方案来完成此操作。下面是一个例子:我会继续使用它,并在得到答案时更新答案。我感觉这可能不太有效,但是肯定是更容易理解的方法。您必须自己测试效率,以确定哪种方法更好,因为嵌套的SQL查询可能会变得非常慢。

从注释中进行注释:在您的JOIN中,并在调用functions.phpadd_filter()方法之前执行query()。紧随其后,您应该WP_Query,以免影响其他查询。

评论


编辑您的A并添加prepare()。希望您不介意编辑。是的:性能必须由OP来衡量。无论如何:我仍然认为这应该仅仅是usermeta,而没有别的。

– kaiser
2012年4月26日在13:53

@ m0r7if3r Thx,尝试解决方案。我刚刚发表了评论,以回应kaiser的回答,并担心可能的可扩展性问题。请考虑一下。

–约翰
2012年4月26日13:54

@kaiser别介意,事实上,我很感激:)

–mor7ifer
2012年4月26日13:55

@ m0r7if3r谢谢。在社区摇滚中有像你这样的人:)

– kaiser
2012年4月26日13:59

您应该将函数放在您的functions.php中,并在调用WP_Query的query()方法之前执行add_filter()。紧随其后的是,您应该remove_filter(),以免影响其他查询。我不确定URL重写的问题是什么,我在很多场合都使用posts_where,却从未见过...

–mor7ifer
2012年4月26日20:21在

#4 楼

模板标记

将这两个函数都放在您的functions.php文件中。然后调整第一个功能并添加您的自定义表名称。然后,您需要进行一些尝试/错误操作才能消除结果数组中的当前用户ID(请参见注释)。

/**
 * 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
}



注意我们没有测试数据等,因此以上内容有点有点猜谜游戏。确保使用对您有用的内容来编辑此答案,以便为以后的读者提供满意的结果。如果您的代表人数过少,我将批准编辑。然后,您也可以删除此注释。谢谢。


评论


JOIN要贵得多。另外:正如我提到的,我们没有测试数据,因此请测试两个答案并用您的结果启发我们。

– kaiser
2012年4月26日13:51



WP_Query本身在查询时与posts表和postmeta之间的JOIN一起使用。我已经看到PHP内存使用量猛增到70MB-每页加载200MB。与许多同时用户一起运行类似的程序将需要一个极端的基础架构。我的猜测是,由于WordPress已经实现了类似的技术,因此与使用ID数组相比,JOIN的负担应该更少。

–约翰
2012年4月26日14:00

@John很高兴听到。真的很想知道结果。

– kaiser
2012年4月26日14:57



好的,这是测试结果。为此,我从一个csv文件中添加了约47K用户。后来,运行一个for循环以使前45个用户跟随其他每个用户。这导致3,704,951条记录保存到我的自定义表中。最初,@ m0r7if3r的解决方案为我提供了95秒的查询时间,在对leader_id列启用索引后,查询时间降至0.020 ms。消耗的PHP总内存约为20MB。另一方面,在索引为ON的情况下,您的解决方案花费了大约2到5秒的时间进行查询。消耗的PHP总内存约为117MB。

–约翰
2012年4月27日13:32

我添加了另一个答案(我们可以对此进行处理和修改/编辑),因为注释中的代码格式很烂:P

– kaiser
2012年4月28日上午9:11

#5 楼


注意:此处的答案是为了避免在注释中进行进一步的讨论。第一组测试用户。我必须修改为真实示例。

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关于测试为此,我从一个csv文件中添加了约47K用户。后来,运行for循环,使前45个用户跟随其他每个用户。


这导致3,704,951条记录保存到我的自定义表中。
最初,@ m0r7if3r的解决方案为我提供了95秒的查询时间,在对leader_id列启用索引后,查询时间降至0.020 ms。消耗的PHP总内存约为20MB。
另一方面,在索引为ON的情况下,您的解决方案花费了大约2到5秒的时间进行查询。 PHP消耗的内存总量约为117MB。

我对这个↑测试的回答: “真实生活”测试:让每个用户关注$leader_amount = rand( 0, 5 );,然后为每个用户添加$leader_amount x $random_ids = rand( 0, 47000 );的数量。到目前为止,我们所知道的是:如果一个用户彼此关注,那么我的解决方案将非常糟糕。此外:您将展示如何进行测试以及在何处添加了计时器。因为一起计算循环也要花费时间。最好在第二个循环中循环遍历所得的ID。


此处进一步处理

评论


对于那些一直关注以下问题的人请注意:我正在测量各种条件下的性能,并将结果在一天或三天内发布。由于需要大量的测试数据,因此这是非常耗时的任务产生。

–约翰
2012年4月30日6:47