Pure SQL approach
Recently, I did the similar query for WooCommerce category (including it children's) for Tags using pure SQL query.
The structure of the query is identical in both cases. The only thing that change is the value of post_type and value of taxonomy that we query for.
Therefore, you can use the below example to also query tags for posts.
Recursion - Get categories IDs (parent and children's)
To select all categories and its subcategories, we could use recursive CTA (MySQL 8.0, MariaDB 10.2 and UP). Like in the example below:
SET @parentCategoryId := 17;
WITH recursive cteCategory(term_id) AS
(
SELECT t.term_id FROM wp_terms t
LEFT JOIN wp_term_taxonomy tt
ON tt.term_taxonomy_id = t.term_id
WHERE tt.term_taxonomy_id = @parentCategoryId
AND tt.taxonomy = 'product_cat'
UNION ALL
SELECT t2.term_id FROM wp_terms t2
LEFT JOIN wp_term_taxonomy tt2
ON tt2.term_taxonomy_id = t2.term_id
INNER JOIN cteCategory
ON tt2.parent = cteCategory.term_id
)
SELECT * FROM cteCategory;
The above query will recursively push child categories IDs to SELECT clause;
+---------+
| term_id |
+---------+
| 17 |
| 56 |
| 57 |
+---------+
3 rows in set (0.001 sec)
You can reed more about recurion here:
https://mariadb.com/kb/en/recursive-common-table-expressions-overview/ and here
How to create a MySQL hierarchical recursive query?
Building a full query
Tags and Categories are not related to each other. They both tied to posts, so that we need to query for all the posts/products assigned to the above categories.
Finally, we need to query all the tags that are related to the above posts. We do that with use of IN
directive.
Now, we need to combine all of the above in to a single query. The solution looks like this:
-- define (parent/starting) category
SET @parentCategoryId := 17;
-- define recursive function here
WITH recursive cteCategory(term_id) AS
(
SELECT t.term_id FROM wp_terms t
LEFT JOIN wp_term_taxonomy tt
ON tt.term_taxonomy_id = t.term_id
WHERE tt.term_taxonomy_id = @parentCategoryId
AND tt.taxonomy = 'product_cat' -- <<< category
UNION ALL
SELECT t2.term_id FROM wp_terms t2
LEFT JOIN wp_term_taxonomy tt2
ON tt2.term_taxonomy_id = t2.term_id
INNER JOIN cteCategory
ON tt2.parent = cteCategory.term_id
)
-- start query for tags used in collection of post/products
SELECT DISTINCT t.* FROM wp_posts AS p
LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id
LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
LEFT JOIN wp_terms t ON t.term_id = tt.term_id
WHERE p.post_type="product" -- <<< post
AND p.post_status = 'publish'
AND tt.taxonomy = "product_tag" -- <<< post_tag
AND p.ID IN
-- build list of posts/products listed under initlial category and their childrens
(SELECT p.ID FROM wp_posts AS p
LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id
LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
LEFT JOIN wp_terms t ON t.term_id = tt.term_id
WHERE p.post_type="product" -- <<< post
AND p.post_status = 'publish'
AND tt.taxonomy = "product_cat" -- <<< category
AND tt.term_taxonomy_id IN (SELECT * FROM cteCategory) -- call recursive function cteCategory
-- AND FIND_IN_SET(t.name, @CategoryByName) -- <<< search category by name
ORDER BY p.ID)
ORDER BY p.ID;
Output a list of tags for given category and its childrens:
+---------+-------------------------+---------------------+------------+
| term_id | name | slug | term_group |
+---------+-------------------------+---------------------+------------+
| 40 | tag ziemniaki | ziemniaki | 0 |
| 46 | tag pomidory koktajlowe | pomidory-koktajlowe | 0 |
| 42 | tag pomidory | pomidory | 0 |
| 44 | tag ogórki | ogorki | 0 |
| 39 | tag cebula | cebula | 0 |
| 41 | tag kapusta | kapusta | 0 |
+---------+-------------------------+---------------------+------------+
6 rows in set (0.002 sec)
The above query will always return a list of non-empty tags sice we build that list, looping on the list of products in a given category group.
Even that this might look like a very complex query (which it is), it was able to complete the task in only 0.002 sec. It was blazing fast, but we still should to cache its result and run it only if we add, modifie or remove any tags on our site. Then we can provide result instantly without any delay.