SQL Server JOIN 返回多行 - 左侧需要一行

Posted

技术标签:

【中文标题】SQL Server JOIN 返回多行 - 左侧需要一行【英文标题】:SQL Server JOIN returning multiple rows - need one row on left side 【发布时间】:2021-12-22 02:43:20 【问题描述】:

我已经在 Google 上进行了广泛的搜索和测试。我想我可能是大脑衰退了。

我们的网站有各种产品的特定登录页面。

Control 表保存页面标题和产品搜索信息。

Control

ID       Title                Category        Colour
------------------------------------------------------------
1        Blue Postcards       Postcard        Blue
2        Blue Photos          Photo           Blue
3        Yellow Postcards     Postcard        Yellow
4        Postcards            Postcard        null

Products

pk_ProdID       Category         Price
-------------------------------------------
100             Postcard         59
200             Photo            42
300             Postcard         33
400             Photo            97
500             Postcard         78
600             Postcard         48

每个产品都有零个或多个可以出售的颜色的记录。

Products_Colours

fk_ProdID  Colour
-------------------------
100        Blue
100        Yellow
200        Blue
300        Yellow
400        Red
500        Blue
500        Yellow
600        Blue

如果Control 的 ID 为 1,则页面应显示蓝色明信片。

以下是想要的结果:

ProdID         Category         Price
-------------------------------------------
100            Postcard         59
500            Postcard         78
600            Postcard         48

我实际上得到的是:

ProdID         Category         Price
-------------------------------------------
100            Postcard         59
100            Postcard         59
500            Postcard         78
500            Postcard         78
600            Postcard         48

SQL

此存储过程为我的 php 页面返回两个记录集,以减少对数据库的请求数。

DECLARE @OrderBy varchar(1)
SET @OrderBy = 'D'
DECLARE @Row int
SET @Row = 1
DECLARE @ControlID int
SET @Control = 1

/* get control info for web page */
SELECT 
c.ID,
c.Title

FROM dbo.Control c

WHERE c.ID = @ControlID;

/* get search criteria */
WITH ControlSearch AS 
(
SELECT 
ID,
Category,
Colour
FROM Control WHERE ID = @ControlID
),

/* get products that match search criteria */
ProductSearch AS 
(
SELECT 
DISTINCT p.ProdID,       
p.Category
Count(1) OVER() As Total_Records,
   
   ROW_NUMBER() OVER(
   ORDER BY 
    CASE WHEN @OrderBy = 'D' THEN p.Price END DESC,
    CASE WHEN @OrderBy = 'U' THEN p.Price END ASC
    ) As RowNum       
       
   FROM dbo.Products p
   JOIN ControlSearch l ON l.ID = @ControlID
   LEFT JOIN dbo.Products_Colours co ON p.ProdID = co.ProdID
   WHERE
  
   (p.Category = l.Category)
   
   AND       
  
   (co.Colour = l.Colour OR l.Colour is null)
)   

/* return recordset */
SELECT * FROM ProductSearch WHERE RowNum >= @Row AND RowNum < (@Row + 50)

我向用户显示匹配记录的总数,并输出行号,以便我可以按 50 个产品分页。

为什么这会根据它有多少颜色返回多个产品?

【问题讨论】:

“为什么这会根据它有多少颜色返回多个产品?” 因为这就是连接的工作方式。如果您有 1 行与 2 行相关,并且您 JOIN 1 行与 2 行相关,则您将获得 2 行。就像如果你有 2 行与 4 行相关,当你 JOIN 时你会得到 8 行。 为什么将子句 p.Category = l.Categoryco.Colour = l.Colour 放在 WHERE 中,而不是在 JOINs 中放置适当的 ON 子句?例如,拥有JOIN ControlSearch l ON p.Category = l.Category 然后拥有WHERE l.ID = @ControlID 更有意义。 如何将其更改为每个匹配的产品仅返回 1 行? 这取决于您想要哪一个“每个产品 1 行”,为什么?为什么它应该选择蓝色而不是黄色,反之亦然?不要说没关系; SQL Server 没有ARBITRARY 功能。 【参考方案1】:

有点摸不着头脑,但也许你真正想要的是EXISTS

DECLARE @ControlID int
SET @ControlID = 1;

SELECT *
FROM dbo.Products P
WHERE EXISTS (SELECT 1
              FROM dbo.Control C
                   LEFT JOIN dbo.Products_Colours PC ON C.Colour = PC.Colour
              WHERE C.Category = P.Category
                AND PC.fk_ProdID = P.pk_ProdID
                AND (PC.Colour IS NOT NULL OR C.Colour IS NULL)
                AND C.ID = @ControlID);

【讨论】:

【参考方案2】:

看起来你需要这样的东西

SELECT
  p.ProdID,       
  p.Category,
  COUNT(*) OVER () AS Total_Records,
  CASE WHEN @OrderBy = 'U'
    THEN COUNT(*) OVER () - ROW_NUMBER() OVER (ORDER BY p.Price) + 1
    ELSE ROW_NUMBER() OVER (ORDER BY p.Price) END
    AS RowNum   
FROM dbo.Products P
JOIN dbo.Control C ON C.ID = @ControlID  -- single-row cross join
WHERE C.Category = P.Category
  AND (
    C.Colour IS NULL OR EXISTS (SELECT 1
        FROM dbo.Products_Colours PC
        WHERE C.Colour = PC.Colour
          AND PC.fk_ProdID = P.pk_ProdID)
  );

【讨论】:

以上是关于SQL Server JOIN 返回多行 - 左侧需要一行的主要内容,如果未能解决你的问题,请参考以下文章

带有join语句的sql中多行的总和

SQL Server - 连接表,使多行变成一行

SQL SERVER LEFT JOIN, INNER JOIN, RIGHT JOIN

如何在从多个表中获取多行的同时删除 sql JOIN 中的重复项

SQL Server:带有 GROUP BY 子句的 OUTER JOIN 不会返回所有可能的组合?

MS Access / SQL Server JOIN返回左表中的所有值,如果右侧缺少则返回零