問題描述

我正在編寫一個插件,使搜索僅在特定的自定義帖子類型上工作,並根據用户在其後端配置中選擇的分類法對結果進行排序。

管理員可以選擇最多 4 個自定義帖子類型的分類:每個都是必須應用於搜索結果的排序標準。然後,管理員可以在搜索結果中選擇使其他人的帖子顯示在每個分類法中的條款。第一選擇的分類法是最重要的分類標準; 其他將按順序應用。

我需要一個查詢:

SELECT * FROM wp_posts ORDER BY choosen_terms_of_taxonomy1, choosen_terms_of_axonomy2, ...

現在我們來使用一些樣本數據。我們有 course 帖子類型:

+----+------------+
| ID | post_title |
+----+------------+
| 12 | Cooking    |
+----+------------+
| 13 | Surfing    |
+----+------------+
| 14 | Building   |
+----+------------+
| 15 | Hacking    |
+----+------------+

那麼我們有兩個這樣的自定義帖子類型的分類。一個是 place,另一個是 pricetagplace 分類法有以下術語:

+---------+------------+
| term_id |    slug    |
+---------+------------+
|      21 |  downtown  |
+---------+------------+
|      22 |  abroad    |
+---------+------------+

pricetag 分類法具有以下術語:

+---------+------------+
| term_id |    slug    |
+---------+------------+
|      31 |  expensive |
+---------+------------+
|      32 |  cheap     |
+---------+------------+

最後我們有 wp_term_relationships,通過這種方式將課程鏈接到分類術語:

+-----------+------------+---------+
| object_id | post_title | term_id |
+-----------+------------+---------+
|        12 | Cooking    |      21 | (downtown)
+-----------+------------+---------+
|        12 | Cooking    |      32 | (cheap)
+-----------+------------+---------+
|        13 | Surfing    |      22 |    (abroad)
+-----------+------------+---------+
|        13 | Surfing    |      31 |    (expensive)
+-----------+------------+---------+
|        14 | Building   |      21 |       (downtown)
+-----------+------------+---------+
|        14 | Building   |      31 |       (expensive)
+-----------+------------+---------+
|        15 | Hacking    |      22 |          (abroad)
+-----------+------------+---------+
|        15 | Hacking    |      32 |          (cheap)
+-----------+------------+---------+

(請注意:我知道這不是 wp_term_relationships 表的真實結構,但是為了簡化這個問題,我想簡化一下。)

我們假設管理員已經選擇了 place 作為第一個分類標準,作為一個排序標準,他選擇首先顯示 downtown 課程 (該插件的管理屏幕已經完成,它已經向管理員提供了 UI 進行這樣的選擇) 。

然後説管理員已經選擇 pricetag 作為第二個分類用作分類標準,他希望 expensive 課程首先顯示。請注意,作為第二個標準,排序優先級低於第一個標準,因此管理員首先需要市中心課程,然後在市中心課程組中首先選擇昂貴的課程。

現在,前端用户搜索網站上的所有課程,他應該按照這個順序看到這些結果:

  1. 建築課程 (因為它是市中心和昂貴的)

  2. 烹飪課程 (因為它是市中心和便宜)

  3. 衝浪課程 (因為它在國外和昂貴)

  4. 黑客課程 (因為它在國外和便宜)

我的問題是在 Wordpress 查詢對象中編寫正確的 JOINORDER BY 子句。我已經掛接了 posts_clauses,這裏是我生成的 SQL 查詢:

SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts
  LEFT JOIN (wp_term_relationships, wp_term_taxonomy, wp_terms)
    ON (wp_term_relationships.object_id = wp_posts.ID
        AND wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id
        AND wp_terms.term_id = wp_term_taxonomy.term_id)
  WHERE 1=1 AND (((wp_posts.post_title LIKE '%%') OR (wp_posts.post_excerpt LIKE '%%') OR (wp_posts.post_content LIKE '%%')))
            AND wp_posts.post_type IN
                ('post', 'page', 'attachment', 'course')
            AND (wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
            AND wp_posts.post_type='course'
  ORDER BY (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
           (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
           (wp_terms.slug LIKE 'expensive' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
           (wp_terms.slug LIKE 'cheap' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
           wp_posts.post_title LIKE '%%' DESC, wp_posts.post_date DESC
 LIMIT 0, 300

但這個查詢至少有兩個問題:

  1. 它返回兩倍的結果,我不明白為什麼,因為 LEFT JOIN 不應該在指定的表之間產生笛卡爾乘積

  2. 結果排序順序不清楚 (似乎只是 post_date DESC),但很明顯,這不是我期待的。

我嘗試通過刪除 Wordpress 生成的子句來簡化查詢:

SELECT wp_posts.* FROM wp_posts
  LEFT JOIN (wp_term_relationships, wp_term_taxonomy, wp_terms)
     ON (wp_term_relationships.object_id = wp_posts.ID
        AND wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id
        AND wp_terms.term_id = wp_term_taxonomy.term_id)
  WHERE 1=1 AND wp_posts.post_type='course'
  ORDER BY (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
       (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
       (wp_terms.slug LIKE 'expensive' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
       (wp_terms.slug LIKE 'cheap' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC

這一個有完全相同的問題,但它有點容易理解,它返回數據,就好像沒有 ORDER BY 一樣。

你能幫我嗎?

最佳解決方案

不幸的是,雖然 WP_Query 支持'tax_query'參數,但它不支持基於後期的排序。因此,您將需要像現在所做的那樣修改查詢 SQL 。但是,您正在錯誤地構建 ORDER BY 子句,這就是為什麼由 post_date 進行排序。您需要做的是使用 CASE 語句,如下所示:

CASE
    WHEN (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') THEN 1
    WHEN (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') THEN 0
END

這將根據您分配給每個術語的優先級 (10 等),更高的優先級,除非您使用 ASC 而不是 DESC 進行排序。

因為你想獨立訂購這兩個分類,你需要有兩個連接和兩個 case 語句。 (例如見下文)

您還需要在帖子 ID 上導出 GROUP BY,以避免重複的結果:

    $clauses['groupby'] = 'wptests_posts.ID';

所以你的最終查詢最終會看起來像這樣 (格式化以便於閲讀):

   SELECT SQL_CALC_FOUND_ROWS  wptests_posts.ID FROM wptests_posts
            LEFT JOIN (
                wptests_term_relationships tr_place,
                wptests_term_taxonomy tt_place,
                wptests_terms t_place
            ) ON (
                tr_place.object_id = wptests_posts.ID
                AND tt_place.term_taxonomy_id = tr_place.term_taxonomy_id
                AND tt_place.taxonomy = 'place'
                AND t_place.term_id = tt_place.term_id
            )

            LEFT JOIN (
                wptests_term_relationships tr_pricetag,
                wptests_term_taxonomy tt_pricetag,
                wptests_terms t_pricetag
            ) ON (
                tr_pricetag.object_id = wptests_posts.ID
                AND tt_pricetag.term_taxonomy_id = tr_pricetag.term_taxonomy_id
                AND tt_pricetag.taxonomy = 'pricetag'
                AND t_pricetag.term_id = tt_pricetag.term_id
            )
   WHERE 1=1  AND wptests_posts.post_type = 'course' AND (wptests_posts.post_status = 'publish')
   GROUP BY wptests_posts.ID
   ORDER BY
        (CASE
            WHEN (t_place.slug LIKE 'downtown') THEN 1
            WHEN (t_place.slug LIKE 'abroad') THEN 0
        END) DESC, (CASE
            WHEN (t_pricetag.slug LIKE 'expensive') THEN 1
            WHEN (t_pricetag.slug LIKE 'cheap') THEN 0
        END) DESC,
        wptests_posts.post_date DESC
   LIMIT 0, 10

這是一個示例 PHPUnit 測試,演示了這個工作,包括生成連接和 orderbys 的示例代碼 (用於生成上面的查詢):

class My_Test extends WP_UnitTestCase {

    public function test() {

        // Create the post type.
        register_post_type( 'course' );

        // Create the posts.
        $cooking_post_id = $this->factory->post->create(
            array( 'post_title' => 'Cooking', 'post_type' => 'course' )
        );
        $surfing_post_id = $this->factory->post->create(
            array( 'post_title' => 'Surfing', 'post_type' => 'course' )
        );
        $building_post_id = $this->factory->post->create(
            array( 'post_title' => 'Building', 'post_type' => 'course' )
        );
        $hacking_post_id = $this->factory->post->create(
            array( 'post_title' => 'Hacking', 'post_type' => 'course' )
        );

        // Create the taxonomies.
        register_taxonomy( 'place', 'course' );
        register_taxonomy( 'pricetag', 'course' );

        // Create the terms.
        $downtown_term_id = wp_create_term( 'downtown', 'place' );
        $abroad_term_id = wp_create_term( 'abroad', 'place' );

        $expensive_term_id = wp_create_term( 'expensive', 'pricetag' );
        $cheap_term_id = wp_create_term( 'cheap', 'pricetag' );

        // Give the terms to the correct posts.
        wp_add_object_terms( $cooking_post_id, $downtown_term_id, 'place' );
        wp_add_object_terms( $cooking_post_id, $cheap_term_id, 'pricetag' );

        wp_add_object_terms( $surfing_post_id, $abroad_term_id, 'place' );
        wp_add_object_terms( $surfing_post_id, $expensive_term_id, 'pricetag' );

        wp_add_object_terms( $building_post_id, $downtown_term_id, 'place' );
        wp_add_object_terms( $building_post_id, $expensive_term_id, 'pricetag' );

        wp_add_object_terms( $hacking_post_id, $abroad_term_id, 'place' );
        wp_add_object_terms( $hacking_post_id, $cheap_term_id, 'pricetag' );

        $query = new WP_Query(
            array(
                'fields'    => 'ids',
                'post_type' => 'course',
            )
        );

        add_filter( 'posts_clauses', array( $this, 'filter_post_clauses' ) );

        $results = $query->get_posts();

        $this->assertSame(
            array(
                $building_post_id,
                $cooking_post_id,
                $surfing_post_id,
                $hacking_post_id,
            )
            , $results
        );
    }

    public function filter_post_clauses( $clauses ) {

        global $wpdb;

        $clauses['orderby'] = "
            (CASE
                WHEN (t_place.slug LIKE 'downtown') THEN 1
                WHEN (t_place.slug LIKE 'abroad') THEN 0
            END) DESC, (CASE
                WHEN (t_pricetag.slug LIKE 'expensive') THEN 1
                WHEN (t_pricetag.slug LIKE 'cheap') THEN 0
            END) DESC,
            " . $clauses['orderby'];

        foreach ( array( 'place', 'pricetag' ) as $taxonomy ) {

            // Instead of interpolating directly here, you should use $wpdb->prepare() for $taxonomy.
            $clauses['join'] .= "
                LEFT JOIN (
                    $wpdb->term_relationships tr_$taxonomy,
                    $wpdb->term_taxonomy tt_$taxonomy,
                    $wpdb->terms t_$taxonomy
                ) ON (
                    tr_$taxonomy.object_id = $wpdb->posts.ID
                    AND tt_$taxonomy.term_taxonomy_id = tr_$taxonomy.term_taxonomy_id
                    AND tt_$taxonomy.taxonomy = '$taxonomy'
                    AND t_$taxonomy.term_id = tt_$taxonomy.term_id
                )
                ";
        }

        $clauses['groupby'] = 'wptests_posts.ID';

        return $clauses;
    }
}

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。