【问题标题】:One-to-many relationships: how to return several columns of a single row of the child table?一对多关系:如何返回子表单行的几列?
【发布时间】:2014-10-05 10:27:36
【问题描述】:

我有两个具有一对多关系的表。表 A 有一个 ID 列,表 B 有一个 A_ID 列。

我希望我的输出包含表 A 中的每条记录一行。该行应该有一些来自 A 的值,但也有一些来自 B 中的行之一。对于表 A 中的每条记录,有 0、1 或B 中的更多记录 - 我希望能够对这些进行排序并只返回一个(例如,Z 列最大的记录)。

Table A - Artists
╔═════╦════════════════╦════════════════════════╗
║ ID  ║     Artist     ║       Real name        ║
╠═════╬════════════════╬════════════════════════╣
║ 383 ║ Bob Dylan      ║ Robert Allen Zimmerman ║
║ 395 ║ Marilyn Manson ║ Brian Hugh Warner      ║
║ 402 ║ David Bowie    ║ David Robert Jones     ║
╚═════╩════════════════╩════════════════════════╝

Table B - Tracks
╔══════╦═══════════╦══════════════════════╦════════════════╗
║  ID  ║ Artist_ID ║      Track Name      ║ Chart position ║
╠══════╬═══════════╬══════════════════════╬════════════════╣
║ 1458 ║       383 ║ Maggie's Farm        ║             22 ║
║ 1598 ║       383 ║ Like a Rolling Stone ║              4 ║
║ 1674 ║       395 ║ Personal Jesus       ║             13 ║
║ 1782 ║       383 ║ Lay Lady Lay         ║              5 ║
╚══════╩═══════════╩══════════════════════╩════════════════╝

以上面的表格为例,我们可能希望返回以下内容:

╔════════════════╦══════════════════════╦══════════╗
║     Artist     ║     Top charting     ║ Position ║
╠════════════════╬══════════════════════╬══════════╣
║ Bob Dylan      ║ Like a Rolling Stone ║ 4        ║
║ Marilyn Manson ║ Personal Jesus       ║ 13       ║
║ David Bowie    ║ null                 ║ null     ║
╚════════════════╩══════════════════════╩══════════╝

请注意,结果集包含来自子表的两列,但仅针对每个 Artist_ID 具有最小图表位置的行。 (而且由于可怜的老鲍伊在表 B 中没有任何轨道,因此他在子表的列中获取了空值。)

如果我想从 B 返回单个列,我会在 select 语句中使用子查询。 (我想我可以在 select 语句中包含多个子查询,但这可能会导致严重的性能损失并且看起来不太优雅。)

因为我需要从子查询中返回多个列,所以我可以用JOIN 来代替它,并将其视为一个表。不幸的是,这似乎导致我的输出中出现重复行 - 即当有多个子记录时,每个子记录在输出中都有一行。

解决这个问题的最佳方法是什么?

【问题讨论】:

    标签: sql sql-server database join subquery


    【解决方案1】:

    由于您想为每个 artist 返回 TOP chart_position,我建议您使用类似于 row_number()windowing function

    此函数将为每个artist. This sequence can be generated in a specific order, in your case you will order by thechart_position` 创建一个唯一的序列。然后,您将返回具有“TOP”值或序列等于 1 的行。

    代码类似于:

    select artist,
      Top_Chart = track_name,
      Position = chart_position
    from
    (
      select a.artist,
        t.track_name,
        t.chart_position,
        seq = row_number() over(partition by a.id order by t.chart_position)
      from artists a
      left join tracks t
        on a.id = t.artist_id
    ) d
    where seq = 1;
    

    SQL Fiddle with Demo

    如果您使用的不是支持窗口函数的 SQL Server 版本(如 SQL Server 2000),那么您可以使用自联接到 tracks 表和聚合函数来编写此查询以获得最高绘制曲目,然后加入艺术家。

    select a.artist,
      Top_Chart = t.track_name,
      Position = t.chart_position
    from artists a
    left join
    (
      select t.artist_id,
        t.track_name,
        t.chart_position
      from tracks t
      inner join
      (
        select chart_position = min(chart_position),
          artist_id
        from tracks
        group by artist_id
      ) m
        on t.artist_id = m.artist_id
        and t.chart_position = m.chart_position
    ) t
      on a.id = t.artist_id;
    

    SQL Fiddle with Demo。它们都给出相同的结果。

    【讨论】:

    • 接受解释和 SQL Fiddle(我以前没见过 - 谢谢!)
    【解决方案2】:

    这将起作用,您只需要创建一个子查询来获取给定艺术家的每个曲目的行号并选择第一个(即行号为 1 的位置)。复制并粘贴以下内容,它会起作用,然后您可以根据自己的目的换掉/编辑:

    DECLARE @TableA TABLE (ID INT, Artist VARCHAR(100), RealName VARCHAR(100))
    DECLARE @TableB TABLE (ID INT, Artist_ID INT, TrackName VARCHAR(100), ChartPosition INT)
    
    INSERT INTO @TableA (ID, Artist, RealName)
    VALUES (383, 'Bob Dylan', 'Robert Allen Zimmerman'),
           (395, 'Marilyn Manson', 'Brian Hugh Warner'),
           (402, 'David Bowie','David Robert Jones')
    
    INSERT INTO @TableB (ID, Artist_ID, TrackName, ChartPosition)
    VALUES
        (1458, 383, 'Maggie''s Farm', 22),
        (1598, 383, 'Like a Rolling Stone', 4),
        (1674, 395, 'Personal Jesus', 13),
        (1782, 383, 'Lay Lady Lay', 5)
    
    
    SELECT 
        [Artist],
        [Top Charting],
        [Position]
    FROM (  SELECT 
                ta.Artist AS [Artist],
                tb.TrackName AS [Top Charting],
                tb.ChartPosition AS [Position],
                ROW_NUMBER() OVER (PARTITION BY ta.Artist ORDER BY tb.ChartPosition) [RowNum]
            FROM @TableA ta
                LEFT JOIN @TableB tb ON ta.ID = tb.Artist_ID
            ) Result
    WHERE Result.RowNum = 1
    

    【讨论】:

      【解决方案3】:

      一些不那么繁琐和更优雅的东西:

      select top (1) with ties *
      from artists a
          left join tracks t on a.Id = t.ArtistId
      order by row_number() over(partition by a.Id order by t.ChartPos);
      

      【讨论】:

      • 我不认为这能达到我想要的效果。也许你误解了这个问题?
      • 在此处发布之前,我尝试了对您的数据的查询,它返回了您想要的内容。实际上,我总是这样做。与此同时,您的回复表明您甚至没有尝试过。
      • 我很抱歉,你是对的。我不明白为什么它会起作用。我只是把它放在一个小提琴(sqlfiddle.com/#!3/d5aba/1/0)中,现在看到魔术正在使用with tiesover。好东西。
      • 是的,with ties 是一个纯粹的魔法 :) 更是如此,因为没有多少人知道它的能力。
      猜你喜欢
      • 2021-01-16
      • 1970-01-01
      • 2011-03-09
      • 2018-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      • 1970-01-01
      相关资源
      最近更新 更多