解析 WordPress `WP_Tax_Query` 类的源码：它如何将 `$tax_query` 参数解析为 SQL `JOIN` 和 `WHERE` 子句。

admin
Fri, 15 Aug 2025 09:19:47 +0000
PHP, query, tax, 解析

各位观众老爷，早上好！今天咱们来聊聊 WordPress 里一个非常重要的类——WP_Tax_Query。这玩意儿就像个魔法师，能把咱们定义的分类、标签查询条件，变成数据库能理解的 SQL 代码，从而筛选出我们想要的文章。 一、 啥是 WP_Tax_Query？为什么要研究它？ 简单来说，WP_Tax_Query 就是 WordPress 用来处理分类法（Taxonomy）查询的类。当你用 WP_Query 查询文章，并且需要根据分类、标签、自定义分类法进行筛选时，WP_Tax_Query 就在背后默默工作。 研究它干啥？ 定制化查询： 深入理解 WP_Tax_Query，你就能写出更复杂、更精准的分类法查询，满足各种奇葩需求。 性能优化： 了解它如何生成 SQL，你可以避免写出低效的查询条件，提升网站速度。 调试问题： 当你的分类法查询出现问题时，理解 WP_Tax_Query 能帮你更快地找到问题根源。 二、 WP_Tax_Query 的基本结构 WP_Tax_Query 接收一个参数 $tax_query，这个参数是一个数组，用来描述你的分类法查询条件。 比如： $args = …

继续阅读“解析 WordPress `WP_Tax_Query` 类的源码：它如何将 `$tax_query` 参数解析为 SQL `JOIN` 和 `WHERE` 子句。”

The post 解析 WordPress `WP_Tax_Query` 类的源码：它如何将 `$tax_query` 参数解析为 SQL `JOIN` 和 `WHERE` 子句。 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位观众老爷，早上好！今天咱们来聊聊 WordPress 里一个非常重要的类——WP_Tax_Query。这玩意儿就像个魔法师，能把咱们定义的分类、标签查询条件，变成数据库能理解的 SQL 代码，从而筛选出我们想要的文章。

一、 啥是 WP_Tax_Query？为什么要研究它？

简单来说，WP_Tax_Query 就是 WordPress 用来处理分类法（Taxonomy）查询的类。当你用 WP_Query 查询文章，并且需要根据分类、标签、自定义分类法进行筛选时，WP_Tax_Query 就在背后默默工作。

研究它干啥？

定制化查询： 深入理解 WP_Tax_Query，你就能写出更复杂、更精准的分类法查询，满足各种奇葩需求。

性能优化： 了解它如何生成 SQL，你可以避免写出低效的查询条件，提升网站速度。

调试问题： 当你的分类法查询出现问题时，理解 WP_Tax_Query 能帮你更快地找到问题根源。

二、 WP_Tax_Query 的基本结构

WP_Tax_Query 接收一个参数 $tax_query，这个参数是一个数组，用来描述你的分类法查询条件。 比如：

$args = array(
    'tax_query' => array(
        array(
            'taxonomy' => 'category',
            'field'    => 'slug',
            'terms'    => array( 'news', 'featured' ),
            'operator' => 'IN',
        ),
    ),
);
$query = new WP_Query( $args );

上面的代码表示：查询属于 category 分类下 slug 为 news 或 featured 的文章。

$tax_query 数组可以包含多个子数组，每个子数组代表一个分类法查询条件。 还可以有关联关系，表示多个分类法条件之间的 AND，OR关系。

三、 WP_Tax_Query 的源码剖析

好，现在咱们开始扒 WP_Tax_Query 的源码，看看它到底是怎么工作的。

首先，找到 wp-includes/class-wp-tax-query.php 文件。

1. 构造函数 __construct( $tax_query )

构造函数主要负责初始化 WP_Tax_Query 对象，并解析传入的 $tax_query 参数。

public function __construct( $tax_query ) {
    if ( isset( $tax_query['relation'] ) && in_array( strtoupper( $tax_query['relation'] ), array( 'AND', 'OR' ), true ) ) {
        $this->relation = strtoupper( $tax_query['relation'] );
    } else {
        $this->relation = 'AND';
    }

    if ( isset( $tax_query[0] ) && is_array( $tax_query[0] ) ) {
        $this->queries = $this->normalize_queries( $tax_query );
    } else {
        $this->queries = array( $tax_query );
    }

    $this->queries = $this->fill_query_defaults( $this->queries );
}

$this->relation： 记录查询条件之间的关系，默认为 AND。 就像SQL语句中的AND 和 OR。

$this->queries： 存储解析后的查询条件数组。

normalize_queries()： 标准化查询条件数组，确保每个条件都是一个数组。

fill_query_defaults()： 为每个查询条件填充默认值，比如默认的 operator 是 IN。

2. normalize_queries( $queries )

这个函数负责将 $tax_query 参数标准化，确保每个查询条件都是一个数组。 它的作用是处理嵌套的 $tax_query 数组。

private function normalize_queries( $queries ) {
    $normalized = array();

    foreach ( $queries as $key => $query ) {
        if ( 'relation' === $key ) {
            $normalized['relation'] = strtoupper( $query );
            continue;
        }

        if ( ! is_array( $query ) ) {
            continue;
        }

        $keys = array_keys( $query );
        sort( $keys );

        if ( $keys === array( 'field', 'taxonomy', 'terms' ) || $keys === array( 'field', 'operator', 'taxonomy', 'terms' ) ) {
            $normalized[] = $query;
        } elseif ( isset( $query[0] ) && is_array( $query[0] ) ) {
            // It's an embedded array, parse each query.
            $normalized[] = $this->normalize_queries( $query );
        }
    }

    return $normalized;
}

3. fill_query_defaults( $queries )

这个函数为每个查询条件填充默认值。 如果用户没有指定 operator，就默认为 IN。

private function fill_query_defaults( $queries ) {
    $defaults = array(
        'taxonomy' => '',
        'field'    => 'term_id',
        'terms'    => array(),
        'operator' => 'IN',
    );

    foreach ( $queries as $key => $query ) {
        if ( isset( $query['relation'] ) ) {
            continue;
        }

        $queries[ $key ] = array_merge( $defaults, $query );
    }

    return $queries;
}

4. get_sql( $table_prefix, $query, $type = 'AND' )

这个函数是 WP_Tax_Query 的核心，它负责生成 SQL 的 JOIN 和 WHERE 子句。

public function get_sql( $table_prefix, $query, $type = 'AND' ) {
    // 省略一部分代码，关注核心逻辑

    $sql = $this->get_sql_clauses( $table_prefix, $query );

    $sql['where'] = 'AND ' . $sql['where'];

    return $sql;
}

get_sql() 函数调用了 get_sql_clauses() 函数来生成 SQL 子句。

5. get_sql_clauses( $table_prefix, $query )

这个函数递归地处理查询条件，生成 SQL 的 JOIN 和 WHERE 子句。

public function get_sql_clauses( $table_prefix, $query ) {
    $sql = array(
        'where'  => 'AND 1=1',
        'join'   => '',
    );

    $sql_chunks = array(
        'relation' => 'AND',
        'where'  => array(),
        'join'   => array(),
    );

    $taxonomies = array();

    $i = 0;
    foreach ( $this->queries as $tax_query ) {
        $i++;

        if ( ! is_array( $tax_query ) ) {
            continue;
        }

        if ( isset( $tax_query['relation'] ) ) {
            $sql_chunks['relation'] = $tax_query['relation'];
            continue;
        }

        $taxonomy = $tax_query['taxonomy'];
        $field    = $tax_query['field'];
        $terms    = $tax_query['terms'];
        $operator = $tax_query['operator'];

        if ( ! taxonomy_exists( $taxonomy ) ) {
            continue;
        }

        if ( empty( $terms ) ) {
            continue;
        }

        $taxonomies[] = $taxonomy;

        $base_table = "{$table_prefix}term_relationships";
        $alias = 'tr' . $i; // 别名
        $terms_table = "{$table_prefix}terms";
        $term_taxonomy_table = "{$table_prefix}term_taxonomy";

        $sql_chunks['join'][] = "INNER JOIN {$base_table} AS {$alias} ON ($query.ID = {$alias}.object_id)";
        $sql_chunks['join'][] = "INNER JOIN {$term_taxonomy_table} AS tt{$i} ON ({$alias}.term_taxonomy_id = tt{$i}.term_taxonomy_id)";
        $sql_chunks['join'][] = "INNER JOIN {$terms_table} AS t{$i} ON (tt{$i}.term_id = t{$i}.term_id)";

        switch ( $field ) {
            case 'term_id':
                $terms_placeholder = implode( ',', array_map( 'intval', $terms ) );
                break;
            case 'name':
            case 'slug':
                $terms_placeholder = "'" . implode( "', '", array_map( 'esc_sql', $terms ) ) . "'";
                break;
            case 'term_taxonomy_id':
                $terms_placeholder = implode( ',', array_map( 'intval', $terms ) );
                break;
            default:
                continue 2; // Skip this query.
        }

        switch ( $operator ) {
            case 'IN':
                $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} IN ($terms_placeholder)";
                break;
            case 'NOT IN':
                $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} NOT IN ($terms_placeholder)";
                break;
            case 'AND':
                $terms_placeholder = "'" . implode( "' AND t{$i}.{$field} = '", array_map( 'esc_sql', $terms ) ) . "'";
                $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} = $terms_placeholder";
                break;
            case 'EXISTS':
                $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy'";
                break;
            case 'NOT EXISTS':
                $sql_chunks['where'][] = "tt{$i}.taxonomy <> '$taxonomy'";
                break;
            default:
                continue 2; // Skip this query.
        }
    }

    $sql['join'] = array_unique( $sql_chunks['join'] );
    $sql['join'] = implode( "n", $sql['join'] );

    $sql['where'] = implode( " {$sql_chunks['relation']} ", $sql_chunks['where'] );

    return $sql;
}

这个函数做了以下几件事：

循环处理每个查询条件： 遍历 $this->queries 数组，处理每个分类法查询条件。

生成 JOIN 子句： 根据分类法和文章的关系，生成 JOIN 子句，连接 wp_term_relationships, wp_term_taxonomy, wp_terms 表。 关键在于给每个JOIN的表定义别名，例如 tr1、tt1、t1， 以便在WHERE子句中引用。

生成 WHERE 子句： 根据 field、terms 和 operator，生成 WHERE 子句，筛选出符合条件的文章。 这里会根据不同的field 和 operator 生成不同的SQL语句。

处理 AND 和 OR 关系： 如果 $tax_query 中有 relation 字段，则根据 AND 或 OR 关系，将多个 WHERE 子句连接起来。

四、 举例说明

咱们还是用最开始的例子：

$args = array(
    'tax_query' => array(
        array(
            'taxonomy' => 'category',
            'field'    => 'slug',
            'terms'    => array( 'news', 'featured' ),
            'operator' => 'IN',
        ),
    ),
);
$query = new WP_Query( $args );

经过 WP_Tax_Query 的处理，最终生成的 SQL 如下（简化版）：

SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_term_relationships AS tr1 ON (wp_posts.ID = tr1.object_id)
INNER JOIN wp_term_taxonomy AS tt1 ON (tr1.term_taxonomy_id = tt1.term_taxonomy_id)
INNER JOIN wp_terms AS t1 ON (tt1.term_id = t1.term_id)
WHERE 1=1
AND tt1.taxonomy = 'category'
AND t1.slug IN ('news', 'featured')
AND wp_posts.post_type = 'post'
AND ((wp_posts.post_status = 'publish'))
ORDER BY wp_posts.post_date DESC

可以看到，WP_Tax_Query 成功地将分类法查询条件转换成了 SQL 的 JOIN 和 WHERE 子句。

五、 复杂查询示例

假设我们要查询同时属于 category 分类下的 news 和 featured，并且属于 post_tag 标签下的 important 的文章。

$args = array(
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'category',
            'field'    => 'slug',
            'terms'    => array( 'news', 'featured' ),
            'operator' => 'AND',
        ),
        array(
            'taxonomy' => 'post_tag',
            'field'    => 'slug',
            'terms'    => array( 'important' ),
            'operator' => 'IN',
        ),
    ),
);
$query = new WP_Query( $args );

最终生成的 SQL 如下（简化版）：

SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_term_relationships AS tr1 ON (wp_posts.ID = tr1.object_id)
INNER JOIN wp_term_taxonomy AS tt1 ON (tr1.term_taxonomy_id = tt1.term_taxonomy_id)
INNER JOIN wp_terms AS t1 ON (tt1.term_id = t1.term_id)
INNER JOIN wp_term_relationships AS tr2 ON (wp_posts.ID = tr2.object_id)
INNER JOIN wp_term_taxonomy AS tt2 ON (tr2.term_taxonomy_id = tt2.term_taxonomy_id)
INNER JOIN wp_terms AS t2 ON (tt2.term_id = t2.term_id)
WHERE 1=1
AND ( tt1.taxonomy = 'category' AND t1.slug = 'news' AND t1.slug = 'featured' )
AND tt2.taxonomy = 'post_tag' AND t2.slug IN ('important')
AND wp_posts.post_type = 'post'
AND ((wp_posts.post_status = 'publish'))
ORDER BY wp_posts.post_date DESC

注意：

'relation' => 'AND' 指定了多个分类法条件之间的关系。

category 的 operator 为 AND，表示文章必须同时属于 news 和 featured 这两个分类。

post_tag 的 operator 为 IN，表示文章属于 important 这个标签。

六、 WP_Tax_Query 的高级用法

1. 使用 EXISTS 和 NOT EXISTS

EXISTS 和 NOT EXISTS 可以用来判断文章是否属于某个分类法。

$args = array(
    'tax_query' => array(
        array(
            'taxonomy' => 'category',
            'operator' => 'EXISTS', // 查询属于 category 分类的文章
        ),
    ),
);
$query = new WP_Query( $args );

2. 使用嵌套的 tax_query

WP_Tax_Query 支持嵌套的 tax_query，可以实现更复杂的查询逻辑。

$args = array(
    'tax_query' => array(
        'relation' => 'OR',
        array(
            'taxonomy' => 'category',
            'field'    => 'slug',
            'terms'    => array( 'news' ),
        ),
        array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'post_tag',
                'field'    => 'slug',
                'terms'    => array( 'important' ),
            ),
            array(
                'taxonomy' => 'custom_taxonomy',
                'field'    => 'term_id',
                'terms'    => array( 123 ),
            ),
        ),
    ),
);
$query = new WP_Query( $args );

上面的代码表示：查询属于 category 分类下的 news，或者同时属于 post_tag 标签下的 important 并且属于 custom_taxonomy 分类下的 term_id 为 123 的文章。

七、 性能优化建议

避免使用 name 作为 field： name 字段没有索引，查询效率较低。 尽量使用 term_id 或 slug。

尽量使用 IN 操作符： IN 操作符的效率通常比多个 OR 操作符要高。

避免过度复杂的查询： 复杂的查询会导致 SQL 语句过于庞大，影响性能。 可以考虑将复杂的查询拆分成多个简单的查询。

利用缓存： 对查询结果进行缓存，可以减少数据库的访问次数，提高网站速度。

八、 总结

WP_Tax_Query 是 WordPress 中一个非常强大的类，它能让我们灵活地控制分类法查询。 理解 WP_Tax_Query 的工作原理，可以帮助我们写出更高效、更精准的查询代码。

记住，掌握了 WP_Tax_Query，就像掌握了一门魔法，能让你的 WordPress 网站更加强大！ 今天就到这里，希望大家有所收获！ 下课！