【问题标题】:How to join two tables using a comma-separated-list in the join field如何在连接字段中使用逗号分隔列表连接两个表
【发布时间】:2011-10-15 05:33:44
【问题描述】:

我有两张桌子,categoriesmovies

movies 表中,我有一列categories。该列包含电影适合的类别。类别是用逗号分隔的 ID。

这是一个例子:

Table categories {
  -id-       -name-
  1          Action
  2          Comedy
  4          Drama
  5          Dance
}

Table movies {
  -id-       -categories-  (and some more columns ofc)
  1          2,4
  2          1,4
  4          3,5
}

现在回到实际问题:是否可以执行从电影表中排除类别列的查询,而是从类别表中选择匹配的类别并将它们返回到数组中?就像一个连接,但问题是有多个用逗号分隔的类别,是否可以做某种正则表达式?

【问题讨论】:

  • 你为什么不规范化到三个表;电影、类别、MoviesCategories?
  • 以数组形式返回它们是什么意思?
  • 你能改进题名吗?技术列表没有描述问题。这就是标签。
  • @Brad,这真是个好主意。创建数据库时我没有想到这一点。这对我来说会容易得多。速度怎么样?如果我将它们分成第三张表,处理器会不会更重?
  • @Katie:这将大大高效(一般而言),因为这就是关系数据库的设计工作方式

标签: mysql join csv


【解决方案1】:
select
    m.id,
    group_concat(c.name)
from
    movies m
    join categories c on find_in_set(c.id, m.categories)
group by
    m.id

输出应该是这样的:

Table movies {
  -id-       -categories-
  1          Comedy,Drama
  2          Action,Drama
  4          Other,Dance
}

【讨论】:

  • 这里至少有一个语法错误。我认为如果您在movies m 之后添加逗号并将ON 更改为WHERE(您没有使用写出的JOIN 语法),那么您将获得该输出。但是我看不到这个"Other" 是从哪里来的?
  • @Tomalak 这是一个错字。我忘了加join。我不喜欢在 WHERE 子句中写连接条件。 其他只是一个例子,它取决于categories table。
【解决方案2】:

在数据库字段中使用逗号分隔的列表是一种反模式,应不惜一切代价避免。
因为在 SQL 中将这些逗号分隔值提取出来是一个 PITA。

您应该添加一个单独的链接表来表示类别和电影之间的关系,如下所示:

Table categories
  id integer auto_increment primary key
  name varchar(255)

Table movies
  id integer auto_increment primary key
  name varchar(255)

Table movie_cat
  movie_id integer foreign key references movies.id
  cat_id integer foreign key references categories.id
  primary key (movie_id, cat_id)

现在你可以做

SELECT m.name as movie_title, GROUP_CONCAT(c.name) AS categories FROM movies m
INNER JOIN movie_cat mc ON (mc.movie_id = m.id)
INNER JOIN categories c ON (c.id = mc.cat_id)
GROUP BY m.id

回到您的问题
或者使用您的数据,您可以这样做

SELECT m.name as movie_title
  , CONCAT(c1.name, if(c2.name IS NULL,'',', '), ifnull(c2.name,'')) as categories 
FROM movies m
LEFT JOIN categories c2 ON 
 (replace(substring(substring_index(m.categories, ',', 2),
  length(substring_index(m.categories, ',', 2 - 1)) + 1), ',', '') = c2.id)
INNER JOIN categories c1 ON 
 (replace(substring(substring_index(m.categories, ',', 1), 
  length(substring_index(m.categories, ',', 1 - 1)) + 1), ',', '') = c1.id)

请注意,仅当每部电影的类别不超过 2 个时,最后一个查询才有效。

【讨论】:

  • @Johan 我的代码运行良好,但我想做的不止 2 个。我需要改变什么?
  • movie_cat.id 是干什么用的?
  • @LightnessRacesinOrbit,每个表必须有一个主键。 (或遭受各种障碍)。
  • @Johan:这并不意味着您必须为其创建一个无意义且任意的自增整数字段。把主键放在(movie_id,cat_id) 大声哭!这个movie_cat.id 100% 毫无意义。
【解决方案3】:

布拉德是对的;标准化是解决方案。 存在规范化来解决这个问题。如果它物有所值的话,它应该在你的 MySQL 书中得到很好的介绍。


但是,如果您真的坚持,您可以通过与 FIND_IN_SET 交叉匹配来伪造直接连接(这很方便地期望以逗号分隔的项目字符串)。

现在,MySQL 不能返回“一个数组”——这就是结果集的用途——但它可以为您提供由管道分隔的类别名称 (|):

SELECT
       `m`.`id`,
       `m`.`name`,
       GROUP_CONCAT(`c`.`name` SEPARATOR "|") AS `cats`
  FROM
       `movies`     AS `m`,
       `categories` AS `c`
 WHERE
       FIND_IN_SET(`c`.`id`, `m`.`categories`) != 0
 GROUP BY
       `m`.`id`;

结果:

id  "name"     "cats"
---------------------------------------------------
1   "Movie 1"  "Comedy|Drama"
2   "Movie 2"  "Action|Drama"
4   "Movie 4"  "Dance"

【讨论】:

  • 在这种情况下我们如何连接多个表?
  • @GaneshAher:这会很困难,这也是这个工具不适合这项工作的另一个原因!使用联接。
【解决方案4】:

试试这个

SELECT m.*, c.* FROM movies m 
RIGHT JOIN categories c on find_in_set(c.id, m.categories) 
GROUP BY m.id

【讨论】:

    【解决方案5】:

    这不是直接回答您的问题,但您在 movies 表中的内容非常糟糕。

    不要使用逗号组合categories,您应该做的是将每个类别放在单独的行中,例如:

    Table movies {
      -id-       -categories-
      1          2
      1          4
      2          1
      2          4
      4          3
      4          5
    }
    

    【讨论】:

    • 不,这也很糟糕。电影类别关系应该在一个单独的表中;您刚刚复制了 movies 中的所有“其他列”。
    猜你喜欢
    • 1970-01-01
    • 2013-10-06
    • 1970-01-01
    • 2019-05-04
    • 2020-10-02
    • 2020-03-05
    • 2021-09-05
    • 1970-01-01
    相关资源
    最近更新 更多