尝试仅 return 与 table 不同的 ID 条目,其中必须匹配特定列的所有值

Trying to only return distinct ID entries from a table where all values of a certain column must be matched

所以情况是我有 3 个 Table,其中 2 个是“助手”table,而另一个是主要 table 我正在尝试从中获取不同的 ID:

Main Table dbo.recipes 包含 ID、Name 和其他一些列,例如:

ID  NAME
5   Veggie Cassola
6   Mozzarella Penne
7   Wiener Schnitzel with Fries
8   Grilled Salmon with Rice
9   Greek style Salad

帮手是dbo.stock:

ID_USER     ID_INGREDIENT
1           225
1           585
1           607
1           643
1           763
1           874
1           937
1           959
1           960
2           225
2           246
2           331
2           363
2           511
2           585

和dbo.content:

ID_INGREDIENT   ID_RECIPE
98              5
196             5
333             5
607             5
608             5
613             5
627             5
643             5
763             5
874             5
951             5
956             5
225             6
585             6
607             6

基本上 dbo.stock 是用户拥有的成分清单,因此用户 ID 和成分 ID。 dbo.content是做某道菜所需要的原料。

我想查询的只是用户实际拥有其配料的食谱,因此这意味着应返回所有配料与(特定用户)匹配的所有食谱。我目前的程序代码如下:

SELECT * FROM [dbo].[recipe]
    WHERE [recipe].[id] NOT IN
    (SELECT DISTINCT [content].[id_recipe] FROM [dbo].[content]
        WHERE [content].[id_ingredient] NOT IN
            (SELECT [stock].[id_ingredient] FROM [dbo].[stock]
                WHERE [stock].[id_user] = @userID))

这可行,但我怀疑这是实现此目标的最佳方法。有没有更好的方法达到同样的效果?

MS SQL 服务器 Express 2019

基本上,您想要查找内容中没有库存成分的所有食谱。这不是你用英语思考它的方式,但如果你在 SQL:

中那样思考它就会导致这个
DECLARE @userID int = 1;

SELECT ID, NAME
FROM dbo.recipe AS r
WHERE NOT EXISTS
( 
  SELECT id_ingredient FROM dbo.content WHERE id_recipe = r.ID
  EXCEPT
  SELECT id_ingredient FROM dbo.stock WHERE id_user = @userID
);

但是,此查询更符合您的要求,只是没有上述两个计划 (EXCEPT is sneaky like that) 中的昂贵 DISTINCTs,因此可能是最佳选择:

DECLARE @userID int = 1;

SELECT ID, NAME
FROM dbo.recipe AS r
WHERE NOT EXISTS
( 
  SELECT 1 FROM dbo.content AS c
  WHERE id_recipe = r.ID AND NOT EXISTS 
  (
    SELECT 1 FROM dbo.stock 
    WHERE id_ingredient = c.id_ingredient
      AND id_user = @userID
  )
);

这是一道经典的Relational Division With Remainder题。

@AaronBertrand 为您提供了几个很好的解决方案。这是另一个经常使用的。

DECLARE @userID int = 1;

SELECT
  r.Id,
  r.Name
FROM dbo.recipe AS r
JOIN dbo.content AS c ON c.id_recipe = r.ID
LEFT JOIN dbo.stock AS s ON s.id_ingredient = c.id_ingredient
    AND s.id_user = @userID
GROUP BY
  r.Id,
  r.Name
HAVING COUNT(*) = COUNT(s.id_ingredient);

这会将所有内容连接在一起(左连接 stock),仅按 recipe 和 return 分组-null stock 行。也就是说,每一个content都必须匹配,并且必须至少有一个content.

有一个语义上的差异:如果你还想要所有 recipesno `content,你可以稍微改变它。

DECLARE @userID int = 1;

SELECT
  r.Id,
  r.Name
FROM dbo.recipe AS r
LEFT JOIN dbo.content AS c ON c.id_recipe = r.ID
LEFT JOIN dbo.stock AS s ON s.id_ingredient = c.id_ingredient
    AND s.id_user = @userID
GROUP BY
  r.Id,
  r.Name
HAVING COUNT(c.id_recipe) = COUNT(s.id_ingredient);

db<>fiddle