Search code examples
sqlwordpresstaxonomy

Wordpress SQL query with multiple taxonomies


i am working on a project that displays posts type within a radius entered ordered by the distance from the target address. right now i am using this query which works fine:

sql = " SELECT SQL_CALC_FOUND_ROWS DISTINCT wposts.post_id, ( " . $_GET['mikm'] . " *           acos( cos( radians( $lat ) ) * cos( radians( wposts.lat ) ) * cos( radians( wposts.long ) - radians( $long ) ) + sin( radians( $lat ) ) * sin( radians( wposts.lat) ) ) )  AS distance  
    FROM post_address  wposts 
    WHERE wposts.post_type ='" . $post_type . "'
    HAVING distance <= $radius OR distance IS NULL ORDER BY distance LIMIT " . $from_page . "," . $per_page;


post_address is a table that hold posts id, post type, and addresses. when a post being saved or updated with an address this table is being update with the information. the form i use has fields for address, checkbox to choose mile or kilometers, and distance in a dropdown menu. i use the method get with the form for pagination.

while the query above works fine i am trying to add taxonomies support. i managed to make it work with one taxonomy using

wp_dropdown_categories($tax_name)


and the SQL:

SELECT SQL_CALC_FOUND_ROWS DISTINCT wposts.post_id, ( " . $_GET['mikm'] . " * acos( cos( radians( $lat ) ) * cos( radians( wposts.lat ) ) * cos( radians( wposts.long ) - radians( $long ) ) + sin( radians( $lat ) ) * sin( radians( wposts.lat) ) ) )  AS distance  
FROM post_address  wposts
LEFT JOIN $wpdb->term_relationships ON (wposts.post_id = $wpdb->term_relationships.object_id)
LEFT JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)
WHERE wposts.post_type ='" . $post_type . "'
AND $wpdb->term_taxonomy.taxonomy IN ('" $tax_name "')
AND ($wpdb->term_taxonomy.parent IN (" . $_GET[$tax_name] . ") OR $wpdb->term_taxonomy.term_id IN (" . $_GET[$tax_name] . "))
HAVING distance <= $radius OR distance IS NULL ORDER BY distance LIMIT " . $from_page . "," . $per_page;


now, since one taxonomy work fine i want to make it work with multiple taxonomies and that is where i get stuck. if for example post type "cars" has 2 taxonomies "model" and "color" i want to be able to filted posts that has "this" model and "this" color. i do not know how many taxonomies exist for the post type(who ever going to use this plugin will enter it manually in admin settings) and they right now being saved in array. for example $taxonomies = array('car_model','car_color') and i can populate the categories dropdown using:

`
foreach ($taxonomies as $tax) {
echo     '<div id="' . $tax . '_cat">';
echo       '<label for="category-id">Choose category: </label>';
        custom_taxonomy_dropdown($tax); 
echo     '</div>';    
`


and

function custom_taxonomy_dropdown($tax_name) {
$args = array(
        'taxonomy'          => $tax_name,
        'hide_empty'        => 0,
        'depth'             => 10,
        'hierarchical'      =>  1,
        'id'                =>  $tax_name . '_id',
        'name'              => $tax_name,
        'selected'          => $_GET[$tax_name],
        'show_option_all'   => 'All categories',
        );      
wp_dropdown_categories($args); 
} 


the dropdowns works fine and the output being sent to url as well but i cant find out the SQL query that will make it work.

this is my first project. excuse me if this question is way to long but i was trying to as clear as i can. i also not sure if any of the code above are the best way to go but any help from anyone would be much appreciated.

UPDATE:

below is the code i am using now and it works with multiple taxonomies. however, i still get into some issues. when i am choosing "all categories" in all of the taxonomies the sql will look like:

AND $wpdb->term_taxonomy.term_id IN () 

and the count will show count(*) = 0 which bring no results instead of all results.

other issue is with child and grandchild categories. when i choose the parent category i expect to get results of all its child and grandchild categories. but since no post is attached to the parent but to its child categories i get no results. this is my code now:

    "SELECT SQL_CALC_FOUND_ROWS DISTINCT wposts.post_id, (" . $_GET['mikm'] . "* acos( cos( radians( $lat ) ) * cos( radians( wposts.lat ) ) * cos( radians( wposts.long ) - radians( $long ) ) + sin( radians( $lat ) ) * sin( radians( wposts.lat) ) ) )  AS distance 
        FROM posts_address  wposts
        LEFT JOIN $wpdb->term_relationships ON (wposts.post_id = $wpdb->term_relationships.object_id)
        LEFT JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
        LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)
    WHERE 
    wposts.post_type IN ('" . $post_type . "')

    AND $wpdb->term_taxonomy.term_id IN (" . $total_terms .") 
    GROUP BY wposts.post_id, wposts.lat, wposts.long        
        HAVING count(*) = " . $bc . " AND distance <= $radius OR distance IS NULL ORDER BY distance LIMIT " . $from_page . "," . $per_page  ;

any help is appriciated.


Solution

  • If I read your requirements correctly, you don't need to extract information on the taxonomy from the database, only verify that a post "joins" all required taxonomies. Join posts with all required taxonomies, then filter based on the line multiplication result.

    First step, where you have

    $wpdb->term_taxonomy.taxonomy IN ('" $tax_name "')
    

    and

    $wpdb->term_taxonomy.parent IN (" . $_GET[$tax_name] . ") OR $wpdb->term_taxonomy.term_id IN (" . $_GET[$tax_name] . "))
    

    have it so that the arguments that go into the IN operator produce a list of all the taxonomy names, comma separated, surrounded by quotes. e.g:

    $wpdb->term_taxonomy.taxonomy IN ('model_a', 'color_blue')
    

    Now, the query will multiply posts with all of the taxonomies (in your filter) that the post matches. From here on, it's simple: group by the post fields (you need only group by the ones you output in the query), and filter using HAVING so that you return only posts multiplied by all categories. In this example, the post should have matched two categories, so:

    (...)
    GROUP BY wposts.post_id, wposts.lat, wposts.long
    HAVING count(*) = 2 AND (distance <= $radius OR distance IS NULL ORDER BY distance LIMIT " . $from_page . "," . $per_page);
    

    Note that the count(*) filter should match the number of taxonomy "axes" matched. Also note that I'm assuming there are no collisions in taxonomies (i.e. There is no "blue" model and "blue" color).

    You should probably remove the DISTINCT operator in the query (grouping already produces distinct posts, and I'm not sure when will mysql's optimizer calculate the distinct operator).