简体   繁体   中英

In SQL, Select all FKs from one-to-many table where table data for FK Exists in each of several lists/joins

My database has recipes with associated data (within a range of categories, each recipe has [0 - many] options selected for each category). To search the recipes, a user can select [0 - many] options from [0 - many] categories.

I'm trying to construct a stored procedure that returns all RecipeIDs that match at least one OptionID for each Category where the user selected at least one OptionID.

So - if you want to find all main dishes and desserts with fruit, the proc needs to return all RecipeIDs where:

  • in RecipeData, for all entries with the same RecipeID (for all Options for a single Recipe)
    • at least one OptionID is for 'Main Dish' OR at least one OptionID is for 'Dessert'
    • AND at least one OptionID is for 'Fruit'
    • AND ignore 'Ranking' (user didn't select any Options in this Category)

The number of Categories is finite and limited (currently 12). Right now, I have the user's search query supplied as 12 table variables - one for each Category, listing the selected OptionIDs for that Category. I'd prefer to submit the user's search query to the proc as a single table, but I'm not sure if that would be possible. Regardless, that's a lower priority.

It must be possible to construct a query to return what I'm looking for, but I have no idea how to do this. Everything I can think of involves looping through groups (RecipeData for each Recipe, Options for each Category), and from what I know, SQL isn't built to do this.

Can I do this in SQL, or will I have to do this in my C# code? If I can do this in SQL - how?

Parameters:

DECLARE @MealTypeOptionID TABLE ( OptionID INT )
DECLARE @IngredientOptionID TABLE ( OptionID INT )
DECLARE @RankingOptionID TABLE ( OptionID INT )

-- all 'Main Dish' or 'Dessert' recipes that have 'Fruit'
INSERT INTO @MealTypeOptionID (OptionID) VALUES (1), (2)
INSERT INTO @IngredientOptionID (OptionID) VALUES (4)

Tables:

Recipe
---------------------------------------------------------------
RecipeID    RecipeName
---------------------------------------------------------------
1           'Apple Pie'
2           'Blueberry Ice Cream'
3           'Brownies'
4           'Tuna Casserole'
5           'Pork with Apples'
6           'Fruit Salad'

Category
---------------------------------------------------------------
CategoryID    CategoryName
---------------------------------------------------------------
1             'Meal Type'
2             'Ingredients'
3             'Ranking'

Option
---------------------------------------------------------------
OptionID    CategoryID    OptionName
---------------------------------------------------------------
1           1             'Main Dish'
2           1             'Dessert'
3           1             'Side Dish'
4           2             'Fruit'
5           2             'Meat'
6           3             'Meh'
7           3             'Great'

RecipeData
---------------------------------------------------------------
RecipeDataID    RecipeID    OptionID
---------------------------------------------------------------
1               1           2
2               1           4
3               1           7
4               2           2
5               2           4
6               3           2
7               4           1
8               4           5
9               4           6
10              5           1
11              5           4
12              5           5
13              6           3
14              6           4

My solution:

-- @optionsToInclude is a parameter of the proc
DECLARE @optionsToInclude TABLE (CategoryID INT, OptionID INT)

-- result table
DECLARE @recipeIDs TABLE (RecipeID INT)

-- get CategoryID FOR first select
DECLARE @categoryID INT
SELECT TOP 1 @categoryID = CategoryID FROM @optionsToInclude GROUP BY CategoryID

-- insert into result table all RecipeIDs that contain any OptionIDs within CategoryID
INSERT INTO @recipeIDs (RecipeID)
SELECT DISTINCT d.RecipeID
FROM RecipeData d
INNER JOIN @optionsToInclude c
    ON c.CategoryID = @categoryID
    AND c.OptionID = d.OptionID

-- delete from @optionsToInclude all entries where CategoryID = @categoryID
DELETE FROM @optionsToInclude WHERE CategoryID = @categoryID

-- check if any more Categories exist to loop through
DECLARE @exists BIT = 1

IF (NOT EXISTS (SELECT * FROM @optionsToInclude))
    SET @exists = 0

WHILE @exists = 1
BEGIN

    -- get CategoryID for select
    SELECT TOP 1 @categoryID = CategoryID FROM @optionsToInclude GROUP BY CategoryID

    -- delete from result table all RecipeIDs that do not contain any OptionIDs within CategoryID
    DELETE FROM @recipeIDs
    WHERE RecipeID NOT IN
    (
        SELECT DISTINCT d.RecipeID
        FROM dbo.RecipeData d
        INNER JOIN @optionsToInclude i
            ON i.CategoryID = @categoryID
            AND i.OptionID = d.OptionID
    )

    -- delete from @optionsToInclude all entries where CategoryID = @categoryID
    DELETE FROM @optionsToInclude WHERE CategoryID = @categoryID

    -- check if any more Categories exist to loop through
    IF (NOT EXISTS (SELECT * FROM @optionsToInclude))
        SET @exists = 0

END

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM