問題描述
我現在已經是這個問題了。最初是,如何將用户的跟隨者數據存儲在數據庫中,我在 WordPress 答案中有幾個很好的建議。之後,按照建議,我添加了一個這樣的新表:
id leader_id follower_id
1 2 4
2 3 10
3 2 10
在上表中,第一行的 ID 為 2 的用户正在跟着 ID 為 4 的用户。在第二行中,ID 為 3 的用户正在跟隨有 ID 的用户相同的邏輯適用於第三行。
現在,本質上我想擴展 WP_Query,以便我可以限制只能由一個用户的領導者提取的帖子。所以,考慮到上述表格,如果我將用户 ID 10 傳遞給 WP_Query,結果應該只包含用户 ID 2 和用户 ID 3 的帖子。
我搜索了很多試圖找到答案。也沒有看到任何教程來幫助我瞭解如何擴展 WP_Query 類。我已經看到 Mike Schinkel 的答案 (擴展 WP_Query) 到類似的問題,但我真的不明白如何應用於我的需要。如果有人可以幫助我,這將是巨大的。
鏈接到 Mike’s answer’s 請求:Link 1,Link 2
最佳解決方案
Important disclaimer: the proper way to do this is NOT to modify your table structure, but to use wp_usermeta. Then you will not need to create any custom SQL to query your posts (though you’ll still need some custom SQL to get a list of everyone that reports to a particular supervisor – in the Admin section, for instance). However, since the OP asked about writing custom SQL, here is the current best practice for injecting custom SQL into an existing WordPress Query.
如果您正在進行復雜的連接,則不能僅僅使用 posts_where 過濾器,因為您還需要修改連接,選擇,可能的組,或按照查詢的各個部分進行排序。
你最好的辦法是使用’posts_clauses’ 過濾器。這是一個非常有用的過濾器 (不應該被濫用!),允許您附加/修改由 WordPress 核心中許多行代碼自動生成的 SQL 的各個部分。過濾器回調簽名是:function posts_clauses_filter_cb( $clauses, $query_object ){ },它期望您返回 $clauses 。
條款
$clauses 是一個包含以下鍵的數組; 每個鍵是一個 SQL 字符串,將直接用於發送到數據庫的最終 SQL 語句:
-
where
-
group by
-
join
-
order by
-
distinct
-
fields
-
limits
如果您在數據庫中添加表 (只有在絕對不能使用 post_meta,user_meta 或分類法的情況下才可以),您可能需要觸摸這些子句中的一個以上,例如 fields(“SELECT” 部分的 SQL 語句),join(所有的表,除了”FROM” 子句中的表),也可能是 orderby 。
修改條款
執行此操作的最佳方法是從過濾器獲取的 $clauses 數組中引用相關的鍵:
$join = &$clauses['join'];
現在,如果修改 $join,您將直接修改 $clauses['join'],以便在返回 $clauses 時進行更改。
保留原條
有可能 (不,認真,傾聽),您將要保留 WordPress 為您生成的現有 SQL 。如果沒有,你應該看看 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 或其他數據庫語言。任何 hand-written mySQL 可能會變得無效,破壞你的插件或主題!更好地讓 WP 自己生成儘可能多的 SQL,只需添加所需的位。
所以第一個業務順序是利用 WP_Query 生成儘可能多的基本查詢。我們使用的確切方法在很大程度上取決於這個帖子列表應該顯示在哪裏。如果是頁面的 sub-section(不是主要查詢),您將使用 get_posts(); 如果是主要查詢,我想您可以使用 query_posts()並完成它,但正確的方法是在查詢數據庫之前攔截主查詢 (並消耗服務器週期),以便使用 request 過濾器。
好的,所以你已經生成了你的查詢,而 SQL 即將被創建。那麼事實上,它已經被創建,只是沒有發送到數據庫。通過使用 posts_clauses 過濾器,您將將員工關係表添加到組閤中。我們來調用這個表 {$ wpdb-> 前綴} 。 ‘user_relationship’,它是一個交叉表。 (順便説一下,我建議您將此表格結構進行泛型,並將其轉換為具有以下字段的適當交集表:’relationship_id’,’user_id’,’related_user_id’,’relationship_type’; 這樣更靈活和強大。 .. 但我離題) 。
如果我明白你想做什麼,你想傳遞一個領導者的 ID,然後只看到該領導者的追隨者的帖子。我希望我有這個權利。如果不正確,您將不得不採取我所説的,並適應您的需要。我會堅持你的表結構:我們有一個 leader_id 和一個 follower_id 。所以 JOIN 將在 {$wpdb->posts}.post_author 上作為您的’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;
}
次佳解決方案
您可以使用 posts_where 過濾器完全使用 SQL 解決方案。這是一個例子:
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 查詢可能會變得相當慢。
從評論:
你應該把函數放在你的 function.php 中,然後在調用 WP_Query 的 query() 方法之前先做一下 add_filter() 。緊接着,你應該是 remove_filter(),所以它不影響其他查詢。
第三種解決方案
我回答這個問題太遲了,我對此表示歉意。我一直很忙於期限,以滿足這一點。
非常感謝 @ m0r7if3r 和 @kaiser 提供了我可以在我的應用程序中擴展和實現的基礎解決方案。這個答案提供了關於我對 @ 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
上述解決方案是實現我想要的結果的最簡單的方法。但是,它是 non-scalable 。當您擁有數以千計的領導者後,隨之而來的領導者 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 。沒有索引,查詢時間顯着增加。
當使用基於數組的解決方案時,內存使用量大約在 100-150 MB 之間,並在運行直接 SQL 時下降到 20 MB 。
當我需要將跟隨者的 ID 傳遞給 posts_where 過濾器功能時,我碰到了 @ m0r7if3r 的解決方案。至少,根據我的知識,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 );
注意:我最後嘗試了上述解決方案,下表中有 120 萬條。平均查詢時間約為 0.060 ms 。
第四種方案
模板標籤
只需將這兩個功能放在您的 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
}
NOTE We don´t have testdata, etc. so the above is a little bit of a guessing game. Make sure that you edit this answer with what worked for you, so we have a satisfying result for later readers. I´ll approve the edit in case you got too low rep. You then can also delete this note. Thanks.
第五種方案
Note: This answer here is to avoid extended discussion in the comments
-
這裏的 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 關於測試為此,我從 csv 文件中添加了大約 47K 個用户。之後,運行 for 循環使前 45 個用户跟隨每個其他用户。這導致我的自定義表格中保存了 3,704,951 條記錄。最初,@ m0r7if3r 的解決方案給了我 95 秒的查詢時間,在 leader_id 列上打開索引後,下降到 0.020 ms 。消耗的 PHP 內存大約在 20MB 左右。另一方面,您的解決方案大約需要 2 到 5 秒的時間來查詢索引。所消耗的 PHP 內存總數約為 117MB 。
-
我對這個↑測試的答案:更多”real life” 測試:讓每個用户遵循
$leader_amount = rand( 0, 5 );,然後將$leader_amount x $random_ids = rand( 0, 47000 );的數量添加到每個用户。到目前為止,我們知道的是:如果用户正在跟蹤對方的用户,我的解決方案將會非常糟糕。進一步:你會看到你做了什麼測試,你在哪裏添加了計時器。我也必須指出,↑以上的時間跟蹤不能真正測量,因為它也需要時間來一起計算循環。更好的是在第二個循環中循環遍歷結果集。
在這裏進一步處理
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。