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.Category
和 co.Colour = l.Colour
放在 WHERE
中,而不是在 JOIN
s 中放置适当的 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 返回多行 - 左侧需要一行的主要内容,如果未能解决你的问题,请参考以下文章
SQL SERVER LEFT JOIN, INNER JOIN, RIGHT JOIN
如何在从多个表中获取多行的同时删除 sql JOIN 中的重复项