如何修改内部联接,使其每个项目仅返回 1 行?

Sky*_*ell -1 t-sql sql-server join

我有一个表,hs_book其中有一列名为GENRE_ID

我从一个名为的表中获取该类型的描述GENRE

我的列子查询Genre

SELECT *,
    Genre = (
        SELECT DESCRIPTION
        FROM GENRE
        WHERE GENRE_ID = (
            SELECT TOP 1 GENRE_ID 
            FROM BOOK_GENRES
            WHERE BOOK_ID = hs_book.BOOK_ID
            )
    )
FROM hs_book
Run Code Online (Sandbox Code Playgroud)

当我执行此查询时,我得到 525 本书(这是正确的)。

应高级 DBA 的要求,我尝试将其转换为 JOIN,这样我就不需要子查询,所以我这样做了:

SELECT * FROM hs_book hsb
INNER JOIN BOOK_GENRES bg ON hsb.BOOK_ID = bg.BOOK_ID
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我得到了 541 个结果,比子查询多了 16 个结果。

经过查资料,我发现有些书有多个GENRE_IDs.

有没有办法修改我的加入,以便我只为每本书返回一种类型?

Cha*_*ace 5

您需要确保BOOK_GENRES每个返回一行BOOK_ID

  • 要么聚合它
SELECT
  hsb.ID,
  hsb.Name,
  STRING_AGG(bg.Genre, ', ') Genre
FROM hs_book hsb
INNER JOIN BOOK_GENRES bg ON hsb.BOOK_ID = bg.BOOK_ID
GROUP BY
  hsb.ID,
  hsb.Name;
Run Code Online (Sandbox Code Playgroud)
  • 或者将其预先聚合在派生表中
SELECT
  hsb.ID,
  hsb.Name,
  bg.Genre
FROM hs_book hsb
INNER JOIN (
    SELECT
      bg.BOOK_ID,
      STRING_AGG(bg.Genre, ', ') Genre
    FROM BOOK_GENRES bg
    GROUP BY
      bg.BOOK_ID
) bg ON hsb.BOOK_ID = bg.BOOK_ID;
Run Code Online (Sandbox Code Playgroud)

您也可以使用APPLY.

SELECT
  hsb.ID,
  hsb.Name,
  bg.Genre
FROM hs_book hsb
CROSS APPLY (
    SELECT
      STRING_AGG(bg.Genre, ', ') Genre
    FROM BOOK_GENRES bg
    WHERE hsb.BOOK_ID = bg.BOOK_ID
    GROUP BY
      ()
) bg;
Run Code Online (Sandbox Code Playgroud)
  • 或者用ROW_NUMBER它来过滤。如果您不关心哪个顺序,则使用ORDER BY (SELECT NULL).
SELECT
  hsb.ID,
  hsb.Name,
  bg.Genre
FROM hs_book hsb
INNER JOIN (
    SELECT
      BG.BOOK_ID,
      bg.Genre,
      ROW_NUMBER() OVER (PARTITION BY bg.BOOK_ID ORDER BY bg.Genre) rn
    FROM BOOK_GENRES bg
) bg ON hsb.BOOK_ID = bg.BOOK_ID AND bg.rn = 1;
Run Code Online (Sandbox Code Playgroud)

前三个选项可能具有相同的性能,因为编译器通常可以在它们之间进行转换。

最后一个可能与您最初的查询类似。

请注意,它们的语义与原始语义略有不同,因为它们使用INNER JOINCROSS APPLY。您可能想切换到LEFT JOINOUTER APPLY