我有两张桌子,还有一张匹配的桌子。为了论证,我们称之为食谱和配料。每个食谱应该至少有一种成分,但可能有很多。每种成分都可以用于许多食谱中。
Recipes Ingredients Match
=============== =============== ===============
ID int ID int RecipeID int
Name varchar Name varchar IngredientID int
示例数据:
Recipes Ingredients Match (shown as CDL but stored as above)
=============== =============== ===============
Soup Chicken Soup: Chicken, Tomatoes
Pizza Tomatoes Pizza: Cheese, Chicken, Tomatoes
Chicken Sandwich Cheese C. Sandwich: Bread, Chicken, Tomatoes
Turkey Sandwich Bread T. Sandwich: Bread, Cheese, Tomatoes, Turkey
Turkey
问题在于:我需要根据成分的名称对食谱进行排序。鉴于上面的示例数据,我需要这个配方的排序顺序:
Turkey Sandwich (First ingredient bread, then cheese)
Chicken Sandwich (First ingredient bread, then chicken)
Pizza (First ingredient cheese)
Soup (First ingredient chicken)
通过第一个成分对食谱进行排名非常简单:
WITH recipesranked AS (
SELECT Recipes.ID, Recipes.Name, Recipes.Description,
ROW_NUMBER() OVER (ORDER BY Ingredients.Name) AS SortOrder
FROM
Recipes
LEFT JOIN Match ON Match.RecipeID = Recipes.ID
LEFT JOIN Ingredients ON Ingredients.ID = Match.IngredientID
)
SELECT ID, Name, Description, MIN(SortOrder)
FROM recipesranked
GROUP BY ID, Name, Description;
除此之外,我被困住了。在我上面的例子中,这几乎可以工作,但是两个三明治的位置不明确。
我感觉MIN(SortOrder)
应该被其他东西取代,也许是一个相关的子查询,寻找同一CTE中不存在的另一条记录,但还没有弄清楚细节。
有什么想法吗?
(食谱可能没有任何成分。我不关心它们出现的顺序,但最终会是理想的。此时不是我的主要关注点。)
我正在使用SQL Server 2008 R2。
更新:我为此添加了一个SQL小提琴,并在此处更新了示例以匹配:
http://sqlfiddle.com/#!3/38258/2
更新:我有一种潜在的怀疑,如果有解决方案,它涉及交叉连接,以比较食谱/成分的每个组合,然后以某种方式过滤。
答案 0 :(得分:2)
我认为这会给你你想要的东西(基于你提供的小提琴)
-- Show recipes ranked by all their ingredients alphabetically
WITH recipesranked AS (
SELECT Recipes.ID, Recipes.Name, SortedIngredients.SortOrder
FROM
Recipes
LEFT JOIN Match ON Match.RecipeID = Recipes.ID
LEFT JOIN
(
SELECT ID, Name, POWER(2.0, ROW_NUMBER() OVER (ORDER BY Name Desc)) As SortOrder
FROM Ingredients) AS SortedIngredients
ON SortedIngredients.ID = Match.IngredientID
)
SELECT ID, Name, SUM(SortOrder)
FROM recipesranked
GROUP BY ID, Name
-- Sort by sum of the ingredients. Since the first ingredient for both kinds
-- of sandwiches is Bread, this gives both of them the same sort order, but
-- we need Turkey Sandwiches to come out first between them because Cheese
-- is it's #2 sorted ingredient, but Chicken is the #2 ingredient for
-- Chicken sandwiches.
ORDER BY SUM(SortOrder) DESC;
它只是使用POWER来确保最重要的成分首先得到加权。
这适用于任意数量的食谱和最多120种成分(总计)
如果配方含有重复成分,则无效,但如果可以发生,可以将其过滤掉
答案 1 :(得分:1)
二进制标志版本:
;with IngredientFlag( IngredientId, Flag )
as
(
select
i.id Ingredient
, POWER( 2, row_number() over ( order by i.Name desc ) - 1 )
from
Ingredients i
)
, RecipeRank( RecipeId, Rank )
as
(
select
m.RecipeID
, row_number() /* or rank() */ over ( order by SUM( flag.Flag ) desc )
from
Match m
inner join IngredientFlag flag
on m.IngredientID = flag.IngredientId
group by
m.RecipeID
)
select
RecipeId
, Name
, Rank
from
RecipeRank rr
inner join Recipes r
on rr.RecipeId = r.id
Str Concat版本:
-- order the ingredients per recipe
;with RecipeIngredientOrdinal( RecipeId, IngredientId, Name, Ordinal )
as
(
select
m.RecipeID
, m.IngredientID
, i.Name
, Row_Number() over ( partition by m.RecipeId order by i.Name ) Ordinal
from
Match m
inner join Ingredients i
on m.IngredientID = i.id
)
-- get ingredient count per recipe
, RecipeIngredientCount( RecipeId, IngredientCount )
as
(
select
m.RecipeID
, count(1)
from
Match m
group by
m.RecipeID
)
-- recursively build concatenated ingredient list per recipe
-- (note this will return incomplete lists which is why I include
-- 'generational' in the name)
, GenerationalConcatenatedIngredientList( RecipeId, Ingredients, IngredientCount )
as
(
select
rio.RecipeID
, cast( rio.Name as varchar(max) )
, rio.Ordinal
from
RecipeIngredientOrdinal rio
where
rio.Ordinal = 1
union all
select
rio.RecipeID
, cil.Ingredients + rio.Name
, rio.Ordinal
from
RecipeIngredientOrdinal rio
inner join GenerationalConcatenatedIngredientList cil
on rio.RecipeID = cil.RecipeId and rio.Ordinal = cil.IngredientCount + 1
)
-- return row_number or rank ordered by the concatenated ingredients list
-- (don't need to return Ingredients but shown for demonstrative purposes)
, RecipeRankByIngredients( RecipeId, Rank, Ingredients )
as
(
select
cil.RecipeId
, row_number() over ( order by cil.Ingredients ) -- or rank()
, cil.Ingredients
from
GenerationalConcatenatedIngredientList cil
inner join RecipeIngredientCount ric
on cil.RecipeId = ric.RecipeId
-- don't forget to filter for only the completed ingredient lists
-- and ignore all intermediate values
and cil.IngredientCount = ric.IngredientCount
)
select * from RecipeRankByIngredients
答案 2 :(得分:0)
这可以满足您的需求:
WITH recipesranked AS (
SELECT Recipes.ID, Recipes.Name, ROW_NUMBER() OVER (ORDER BY Ingredients.Name) AS SortOrder,
Rank () OVER (partition by Recipes.Name ORDER BY Ingredients.Name) as RankOrder
FROM
Recipes
LEFT JOIN Match ON Match.RecipeID = Recipes.ID
LEFT JOIN Ingredients ON Ingredients.ID = Match.IngredientID
)
SELECT ID, Name,SortOrder, RankOrder
FROM recipesranked
Where RankOrder = 1
ORDER BY SortOrder;
答案 3 :(得分:0)
我能想到的唯一替代方法是使用动态sql生成数据透视
这对我的替代品所含成分的数量没有限制,但并不完全是优雅的!
DECLARE @MaxIngredients INT
SELECT @MaxIngredients = MAX(IngredientCount)
FROM
(
SELECT COUNT(*) AS IngredientCount
FROM Match
GROUP BY RecipeID
) A
DECLARE @COLUMNS nvarchar(max)
SELECT @COLUMNS = N'[1]'
DECLARE @COLUMN INT
SELECT @COLUMN = 2
WHILE (@COLUMN <= @MaxIngredients)
BEGIN
SELECT @COLUMNS = @COLUMNS + N',[' + CAST(@COLUMN AS varchar(19)) + N']', @COLUMN = @COLUMN + 1
END
DECLARE @SQL nvarchar(max)
SELECT @SQL =
N'WITH recipesranked as(
SELECT *
FROM
(
SELECT M.RecipeID,
ROW_NUMBER() OVER (PARTITION BY M.RecipeID ORDER BY I.SortOrder) AS IngredientIndex,
I.SortOrder
FROM Match M
LEFT
JOIN
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Name) As SortOrder
FROM Ingredients
) I
ON I.ID = M.IngredientID
) AS SourceTable
PIVOT
(
MIN(SortOrder) --min here is just for the syntax, there will only be one value
FOR IngredientIndex IN (' + @COLUMNS + N')
) AS PivotTable)
SELECT R.Name
FROM RecipesRanked RR
JOIN Recipes R
ON RR.RecipeID = R.ID
ORDER BY ' + @COLUMNS
EXEC SP_EXECUTESQL @SQL
答案 4 :(得分:0)
创建一个函数并使用它。
CREATE FUNCTION GetIngredients(@RecipeName varchar(200))
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE @Ingredients VARCHAR(MAX)
SET @Ingredients=NULL
SELECT TOP 9999999
@Ingredients = COALESCE(@Ingredients + ', ', '') + Ingredients.Name
FROM Recipes
LEFT JOIN Match ON Match.RecipeID = Recipes.ID
LEFT JOIN Ingredients ON Ingredients.ID = Match.IngredientID
WHERE Recipes.Name=@RecipeName
ORDER BY Ingredients.Name ASC
return @Ingredients
END
GO
SELECT
Recipes.Name AS RecipeName, dbo.GetIngredients(Recipes.Name) [Ingredients]
FROM Recipes
ORDER BY [Ingredients]