如何获取标记为 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
,其中包含所有标签A
、B
和C
:
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 表标记解决方案的主要内容,如果未能解决你的问题,请参考以下文章