Question

I would like to create faceted search. I have some MySQL tables:

**products_table**
id   name          category_set
9    Female.Jeans  157
10   Male.Jeans    157

**fields_table**
id   name
1    Color
2    Gender

**fields_values_table**
id   fieldsid   value
1    1          White
2    1          Black
3    1          Orange
4    1          Green
5    1          Blue
6    2          Male
7    2          Female

**products_to_fields_values**
productid   fields_values_id
9           5
9           7
10          5
10          6

I have php function to search my products:

private function get_products ($filter) {

        $per_page   = (isset($filter["show_by"]) && $filter["show_by"] >= 25 && $filter["show_by"] <= 100) ? intval($filter["show_by"]) : 25; 
        $start  = (isset($filter["page_id"]) && intval($filter["page_id"])) ?  ($filter["page_id"] -1)*$per_page : 0;

        $data           = [];

        $search_vids    = false;
        if (isset($filter['vid']) && is_array($filter['vid'])) {

            $in_set = implode(',', array_filter($filter['vid']));

            if (strlen($in_set)) {
                $search_vids = true;
            }
        }

        if ($search_vids) {

            $sql_counter    = 'SELECT COUNT(*) count FROM `products` WHERE 1=1';
            $sql_result = 'SELECT 
                                        P.id,
                                        P.name, 
                                        P.status                                        
                                    FROM `products` P, `products_to_fields_values` V WHERE 1=1';
        } else {

            $sql_counter    = 'SELECT COUNT(*) count FROM `products` WHERE 1=1';
            $sql_result = 'SELECT 
                                        `id`, 
                                        `name`, 
                                        `status`
                                    FROM `products` WHERE 1=1';
        }

        if ($search_vids) {

            $sql_counter    .= ' AND P.id = V.productid AND V.fields_values_id IN ('.$in_set.')';
            $sql_result     .= ' AND P.id = V.productid AND V.fields_values_id IN ('.$in_set.')';
        }

        if (isset($filter['cid']) && intval($filter['cid'])) {

            $sql_counter    .= ' AND FIND_IN_SET(:cid, `category_set`)';
            $sql_result     .= ' AND FIND_IN_SET(:cid, `category_set`)';
            $data[':cid'] = $filter['cid'];
        }

        if (isset($filter['price']['from']) && floatval($filter['price']['from'])) {

            $sql_counter    .= ' AND `price` >= :price_from END ';
            $sql_result     .= ' AND `price` >= :price_from END ';

            $data[':price_from'] = $filter['price']['from'];
        }

        if (isset($filter['price']['to']) && floatval($filter['price']['to'])) {

            $sql_counter    .= ' AND `price` <= :price_to ';
            $sql_result     .= ' AND `price` <= :price_to ';

            $data[':price_to'] = $filter['price']['to'];
        }

        if (isset($filter['q']) && strlen($filter['q']) > 0) {

            $search          = filter_var($filter['q'], FILTER_SANITIZE_STRING);

            $sql_counter    .= ' AND (`name` LIKE concat("%", :search_name, "%")';
            $sql_result     .= ' AND (`name` LIKE concat("%", :search_name, "%")';

            $sql_counter    .= ' OR `product_tag` LIKE concat("%", :search_tag, "%"))';
            $sql_result     .= ' OR `product_tag` LIKE concat("%", :search_tag, "%"))';

            $data[':search_name']  = $search;
            $data[':search_tag']      = $search;
        }   

        $count  = $this->db1->query($sql_counter)->single($data)['count'];

        if ($search_vids) {
            $sql_result             .= ' GROUP BY P.id LIMIT :start, :perpage';
        } else {
            $sql_result             .= ' LIMIT :start, :perpage';
        }

        $data[':start']     = $start;
        $data[':perpage']   = $per_page;

        $rows   = $this->db1->query($sql_result)->resultset($data);
        $pages = ($count >0) ? ceil($count/$per_page) : 0;

        $product_list       = array();
        $fields                 = array();
        $breadcrumbs    = array();

        $cid                    = 0;

        if(count($rows)>0) {
            foreach ($rows as $row) {
                $product_list[]             = $row;
            } 
        }

        $categories                 =   new categories($this->app, $this->user);
        $categories_arr             =   $categories->load();


        if (isset($filter['cid']) && intval($filter['cid'])) { // Categories page

            $sql                = 'SELECT GROUP_CONCAT(fieldid) AS fields_set FROM `fields_to_categories` WHERE `category_set`= :cid';
            $fields_ids     = $this->db1->query($sql)->bind(':cid', $filter['cid'])->single();

            if (isset($fields_ids['fields_set']) && !empty($fields_ids['fields_set'])) {

                $fields         =   new fields($this->app, $this->user);
                $fields         =   $fields->load_controller($fields_ids['fields_set'], false); // False = fields with status = 1
            }

            $breadcrumbs= $categories->getBreadcrumbs($filter['cid']); 

        } else {    

            $breadcrumbs= $categories->getBreadcrumbs($cid); 
        }


        return array(
            'products'          => $product_list,
            'categories'        => $categories_arr["categories"],           
            'filter'                => isset($filter['cid']) && intval($filter['cid']) && isset($fields['fields']) ? $fields['fields'] : '',
            'breadcrumbs'   => isset($breadcrumbs['path']) ? $breadcrumbs['path'] :'',
            'total'             => $count,
            'pages'             => intval($pages),
            'status'                => 1
        );
    }

This will give me the picture: enter image description here But the problem is here:

When I checked Male (1 product) blue color should be 1. Please check the image below. How can I fix my search function? enter image description here

Was it helpful?

Solution

Problem with your query is that it will return the product if it matches even a single filter.

As you need all the filter to match you need some changes in your query

This you can achieve by 2 ways

  1. Create derived table for each filter and then match those derived table
  2. Have a count on each product how many filter it matches and only return if it matches specific number of filters. Example query with fiddle

    select 
        pfv.productid, pt.name
    from 
        fields_table ft 
    inner join 
        fields_values_table fvt 
    on 
        (ft.id=fvt.fieldsid) 
    inner join 
        products_to_fields_values pfv 
    on  
        (pfv.fields_values_id=fvt.id) 
    inner join 
        products_table pt 
    on 
        (pt.id=pfv.productid) 
    inner join
        product p
    on
        (p.productid=pt.id) <-- Assuming product table having price and 1-1 mapping
    where 
        p.price > 500 and p.categoryid=102 and
        (ft.name='color' and fvt.value='Blue')or 
        (ft.name='gender' and fvt.value='female')
    group by 
        pfv.productid, pt.name
    having count(*)>=2  <--Maintain the count of how many field value filters it should match. In this example we have only 2 filters
    

Fiddle (Updated Fiddle)

| PRODUCTID |         NAME |
|-----------|--------------|
|         9 | Female.Jeans |
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top