如何获取标记为 Tag1 AND Tag2 AND ... 的所有项目,用于 3-table 标签解决方案

How to get all items that are tagged Tag1 AND Tag2 AND ... for a 3-table tag solution

所以我实施了 3 table 标签解决方案,如 this classic post 中所示(所谓的“Toxi”解决方案)。一切都很好,我唯一遇到的困难是获取所有标记有多个标签的项目。

我的第一个解决方案使用 IN 并且工作正常,但当然给了我所有具有任何提供的标签的项目。但我真正想要的是所有具有每个提供的标签的项目。

所以我尝试:

    /**
     * GALLERY::filter_by_tags()
     * @param array $tags - an array of strings
     */
    public function filter_by_tags($tags){
        if (count($tags) > 1){
        //do the string building of the query 
        $query = "SELECT DISTINCT m.* FROM fm_maps m
                                INNER JOIN fm_map_tags mt ON mt.map_id = m.id
                            INNER JOIN fm_tags t
                            ON t.id = mt.tag_id WHERE t.tag = ";
        $current_tag_string = "";
        $sql_values = [];                                                                               
        $i = 1;                                                                                                                                         
        foreach ($tags as $tag){
            $current_tag_string = 'tag' . $i;                                                                                   
            $sql_values[$current_tag_string] = $tag;                                                        
            if ($i == 1) {
                $query .= ':' . $current_tag_string;                                                            
            }   else {
                $query .= ' INNER JOIN fm_map_tags mt ON mt.map_id = m.id INNER JOIN fm_tags t
                            ON t.id = mt.tag_id WHERE t.tag = :' . $current_tag_string;                         
            }
            $i++;                                                                                               
        }
        $stmt = $this->map_db_connector->conn->prepare($query);
        $stmt->execute($sql_values);
        $maps = $stmt->fetchAll(PDO::FETCH_CLASS, 'MAP');                                                       
        foreach ($maps as $current_map){
            $this->create_gallery_entry($current_map);
        }
    } else {
        //just use tags[0]
        $stmt = $this->map_db_connector->conn->prepare("SELECT DISTINCT m.* FROM fm_maps m
                                                         INNER JOIN fm_map_tags mt ON mt.map_id = m.id
                                                         INNER JOIN fm_tags t ON t.id = mt.tag_id WHERE t.tag IN (:tag)");
        $stmt->bindValue(':tag', $tags[0]);
        $stmt->execute();
        $maps = $stmt->fetchAll(PDO::FETCH_CLASS, 'MAP');                                                               
        foreach ($maps as $current_map){
            $this->create_gallery_entry($current_map);
        }
    }

这可能不必要地复杂,但原则上整个功能都有效。只是第一个 if 子句中的 SQl 语句本身被破坏了。 Returns Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INNER JOIN fm_map_tags mt ON mt.map_id = m.id INNER JOIN fm_tags t ON' else 部分工作正常。我也试过了

foreach ($tags as $tag){
            $current_tag_string = 'tag' . $i;                                                                                   
            $sql_values[$current_tag_string] = $tag;                                                        
            if ($i == 1) {
                $query .= ':' . $current_tag_string;                                                            
            }   else {
                $query .= ' AND t.tag = :' . $current_tag_string;                         
            }

我认为这更简单,但这也不起作用。我没有收到任何错误,但也没有结果。

作为 SQL 和 PHP 初学者,我被困住了。谁能指出我正确的方向?

此查询将为您提供 fm_maps,其中包含所有标签 ABC:

SELECT * FROM fm_maps
WHERE EXISTS (
    SELECT 1 FROM fm_map_tags mt
    JOIN fm_tags t ON mt.tag_id = t.id AND t.tag IN ('A', 'B', 'C') 
    WHERE mt.map_id = fm_maps.id
    GROUP BY mt.map_id
    HAVING COUNT(*) = 3
)

使用此查询实现 filter_by_tags 方法可能是这样的:

/**
* GALLERY::filter_by_tags()
* @param array $tags - an array of strings
*/
public function filter_by_tags($tags)
{
    if (empty($tags)) {
        // Do something if there are no tags

    } else {
        $placeholders = '?' . str_repeat(', ?', count($tags) - 1);
        $query = 
            "SELECT * FROM fm_maps WHERE EXISTS ("
            . "SELECT 1 FROM fm_map_tags mt "
            . "JOIN fm_tags t ON mt.tag_id = t.id AND t.tag IN ($placeholders) "
            . "WHERE mt.map_id = fm_maps.id GROUP BY mt.map_id HAVING COUNT(*) = ?)";

        $params = $tags;
        $params[] = count($tags);

        $stmt = $this->map_db_connector->conn->prepare($query);
        $stmt->execute($params);
        $maps = $stmt->fetchAll(PDO::FETCH_CLASS, 'MAP');
        foreach ($maps as $current_map){
            $this->create_gallery_entry($current_map);
        }
    }
}