MySQL 在同一张表上有 2 个 LEFT JOIN

Posted

技术标签:

【中文标题】MySQL 在同一张表上有 2 个 LEFT JOIN【英文标题】:MySQL with 2 LEFT JOINs on the same table 【发布时间】:2009-05-23 16:28:38 【问题描述】:

我正在尝试运行此查询:

SELECT 
  Destaque.destaque, Noticia.id, Noticia.antetitulo, 
  Noticia.titulo, Noticia.lead, Noticia.legenda, 
  Noticia.publicacao, Seccao.descricao, Album.pasta,
  Foto.ficheiro, Foto.descricao, Cronista.nome, 
  Cronista.profissao, Cronista.ficheiro,
  AudioFile.*, AudioCollection.*, VideoFile.*, VideoCollection.*
FROM 
  nt_highlights AS Destaque
  LEFT JOIN nt_noticias  AS Noticia         ON Destaque.noticia_id = Noticia.id
  LEFT JOIN mm_fotos     AS Foto            ON Noticia.foto_id = Foto.id
  LEFT JOIN nt_temas     AS Seccao          ON Noticia.tema_id = Seccao.id
  LEFT JOIN mm_albuns    AS Album           ON Foto.album_id = Album.id
  LEFT JOIN nt_cronistas AS Cronista        ON Cronista.id = Noticia.cronista_id  
  LEFT JOIN ntNoticias_mmFiles AS Rel       ON Rel.noticia_id = Noticia.id
  LEFT JOIN mm_files     AS AudioFile       ON AudioFile.id = Rel.file_id
  LEFT JOIN mm_coleccoes AS AudioCollection ON AudioFile.coleccao_id = AudioCollection.id        
  LEFT JOIN mm_files     AS VideoFile       ON VideoFile.id = Rel.file_id
  LEFT JOIN mm_coleccoes AS VideoCollection ON VideoFile.coleccao_id = VideoCollection.id
WHERE 
  Destaque.area_id = 1
  AND Noticia.paraPublicacao = 1 
  AND Noticia.publicacao <= NOW()   
  AND (AudioFile.mimeType != '' OR AudioFile.id IS NULL)
  AND (VideoFile.mimeType = '' OR VideoFile.id IS NULL)
ORDER BY 
  Destaque.destaque

这会给我一些文章(来自nt_noticias),我的想法是同时从mm_files 表中获取VideoAudio 文件。

发生的情况是,当我有一篇有声音和视频的文章时,mysql 会返回 4 行:

有声音(视频为空) 有视频(声音为空) 全部为空 有声音和视频

如何“强制”它在每篇文章中只返回一行并关联任何现有的视频和音频?我在这里做错了什么?

【问题讨论】:

【参考方案1】:

认为你想要这样的东西:

SELECT 
  Destaque.destaque, Noticia.id, Noticia.antetitulo, 
  Noticia.titulo, Noticia.lead, Noticia.legenda, 
  Noticia.publicacao, Seccao.descricao, Album.pasta,
  Foto.ficheiro, Foto.descricao, Cronista.nome, 
  Cronista.profissao, Cronista.ficheiro,
  AudioFile.*, AudioCollection.*, VideoFile.*, VideoCollection.*
FROM 
  nt_highlights AS Destaque
  LEFT JOIN nt_noticias  AS Noticia         ON Destaque.noticia_id = Noticia.id
  LEFT JOIN mm_fotos     AS Foto            ON Noticia.foto_id = Foto.id
  LEFT JOIN nt_temas     AS Seccao          ON Noticia.tema_id = Seccao.id
  LEFT JOIN mm_albuns    AS Album           ON Foto.album_id = Album.id
  LEFT JOIN nt_cronistas AS Cronista        ON Cronista.id = Noticia.cronista_id  
  LEFT JOIN ntNoticias_mmFiles AS AudioRel  ON Rel.noticia_id = Noticia.id
                                               AND AudioRel.file_id IN (
    SELECT file_id 
    FROM   ntNoticias_mmFiles 
    WHERE  noticia_id = Noticia.id AND IsAudioFile = 1 /* whatever the check is */
    LIMIT  1
  )
  LEFT JOIN mm_files     AS AudioFile       ON AudioFile.id = Rel.file_id
  LEFT JOIN mm_coleccoes AS AudioCollection ON AudioFile.coleccao_id = AudioCollection.id        
  LEFT JOIN ntNoticias_mmFiles AS VideoRel  ON VideoRel.noticia_id = Noticia.id
                                               AND VideoRel.file_id IN (
    SELECT file_id 
    FROM   ntNoticias_mmFiles 
    WHERE  noticia_id = Noticia.id AND IsVideoFile = 1  /* whatever the check is */
    LIMIT  1
  )
  LEFT JOIN mm_files     AS VideoFile       ON VideoFile.id = Rel.file_id
                                               AND VideoFile.IsVideoFile = 1
  LEFT JOIN mm_coleccoes AS VideoCollection ON VideoFile.coleccao_id = VideoCollection.id
WHERE 
  Destaque.area_id = 1
  AND Noticia.paraPublicacao = 1 
  AND Noticia.publicacao <= NOW()   
ORDER BY 
  Destaque.destaque

我的想法是这样的:

您最多需要一个音频文件和一个视频文件。每个Noticia 有多个文件可用,因此您需要确保每种类型最多有一个文件进入连接。这也意味着您必须加入ntNoticias_mmFiles 表两次——每种类型一次。

这就是连接条件中的子查询应该做的事情:每种文件类型选择一行。从那里继续,您可以将其余数据加入其中,就像您已经做的那样。

【讨论】:

LIMIT 子句在查询中不起作用,因为我在 MySQL 5.0 上,但我用 GROUP BY noticia_id 替换它并且工作得很好。谢谢一堆! 我总是忘记 MySQL 不处理子查询中的 LIMIT。 :-) 但我很高兴能帮上忙。【参考方案2】:

JOIN 将返回所有组合,这就是问题所在。 如果每篇文章只有一个音频和/或视频文件,那么您可能需要查看子选择。 在 SQL Server 中,这看起来像(未经测试的代码):

SELECT title, 
       (select TOP 1 audio from audio where audio.aid = articles.id) as Audio, 
       (select TOP 1 video from video where video.aid = articles.id) as Video
FROM articles

请注意,在大型数据集上,这可能会表现不佳,因为此示例中的子选择是针对返回到外部查询的每一行单独执行的。例如,如果您返回 10,000 篇文章,那么实际上将在服务器上执行总共 20,001 个查询。 还有其他可能的答案来克服这个问题,但他们会更多地参与(我怀疑你可以用派生表做一些事情,但目前我还没有想到)。

【讨论】:

【参考方案3】:

您可能希望将该连接查询优化为视图。这是一个很大的查询,而且有这么多的连接,效率会很低。此外,视图可以帮助您调试连接,并且通过允许您分别编写连接(在视图中)和 WHERE 子句(在视图中的选择中)来帮助调试查询,从而基本上简化了操作。

【讨论】:

以上是关于MySQL 在同一张表上有 2 个 LEFT JOIN的主要内容,如果未能解决你的问题,请参考以下文章

mysql通过在同一张表上查询的自定义顺序

MySQL INSERT 在同一张表上使用带有 COUNT() 的子查询

如何在 Oracle SQL 中查找和过滤同一张表上的数据

更新时在同一张表上执行触发器

TYPO3 版本中同一张表上的双向 n:m 关系。 4.7.2

使用多线程代码在一张表上发生 MySQL 死锁