如何获取标记为 Tag1 AND Tag2 AND ... 的所有项目以获取 3 表标记解决方案

Posted

技术标签:

【中文标题】如何获取标记为 Tag1 AND Tag2 AND ... 的所有项目以获取 3 表标记解决方案【英文标题】:How to get all items that are tagged Tag1 AND Tag2 AND ... for a 3-table tag solution 【发布时间】:2020-12-11 22:11:40 【问题描述】:

所以我实现了一个 3 表标签的解决方案,在 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 语句本身被破坏了。返回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 初学者,我被困住了。谁能指出我正确的方向?

【问题讨论】:

检查echo $query,你会发现一个错误。 WHERE 之后不能有INNER JOIN。它必须是SELECT columns FROM table1 INNER JOIN table2 ON condition INNER JOIN table3 ON condition ... WHERE ... 如果你多次加入fm_map_tags,你需要每次给他们不同的别名。 但您不需要多次加入。在 Toxi 解决方案中使用“Intersection (AND)”查询。 啊,关于在 WHERE 之后非法使用 JOIN 的细节对我来说是新的,不同的别名也是一个很好的捕捉。谢谢你!此外,我尝试使用 AND,因为它看起来更简单,但没有返回任何地图。可能应该回显或记录查询,因为@pavel 建议进一步调试该方法。 【参考方案1】:

此查询将为您提供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);
        
    

【讨论】:

非常感谢!像魅力一样工作,比我的方法简单得多。我已经习惯了使用命名参数,我什至没有考虑位置参数,但是在这种情况下它们实际上很有意义!

以上是关于如何获取标记为 Tag1 AND Tag2 AND ... 的所有项目以获取 3 表标记解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别