I have not been able to figure out scope of reference when there are nested queries. For example I can't seem to reference NoCarsFound.CU in my inner join. I don't understand why in my join I can't reference previous result sets...I think I just don't undrestand the scope of referencing previous select results or referencing aliases in my inner join's ON comparisons.
I also get Ambiguous column name 'CU'
So I keep getting errors saying it doesn't know NoCarsFound.CU. I even tried to reference a straight view such as vwInvalidCars.CU, and it doesn't understand vwInvalidCars either.
create procedure [Rac].[GetCarStatDetails_sp]
#Year int,
#CarTitle varchar(30),
#Company varchar(31),
#CU varchar(12),
#UserName varchar(50)
as
BEGIN
DECLARE #CarMatch table
(
FaceValueTol varchar(100),
FaceValueDesc varchar(100),
Year int,
CU varchar(16),
PaintTypeDesc varchar(50)
)
insert into #CarMatch
Select temp1.FaceValueTol,
temp1.FaceValueDesc,
temp1.Year,
temp1.CU,
temp1.PaintTypeDesc
from Rac.viewCarBase as temp1
join (select Username, userCars.CU from Nep.viewUserCars userCars where UserName = #UserName) as userCars on userCars.CU = temp1.CU
INNER JOIN
(
Select CU,
from Rac.vwCarFactor carfactors
where RiskFactorTypeID not in (334,553,34334,534,7756)
Group by CU
) as temp
on
and temp1.CU = temp.CU
and temp1.PaintTypeDesc = temp.CalcPaintTypeDesc
Where
temp1.RiskFactorTypeID=4
and temp1.[Year]=#Year
and (temp1.CarTitle=#CarTitle or #CarTitle='<All>')
and (temp1.CU=#CU or #CU='<All>')
SELECT ProductID_bo,
Coalesce(CarTitle_bo,LTRIM(RTRIM(CarTitle))) as CarTitle,
Coalesce(Company_bo,LTRIM(RTRIM(Company))) as Company,
Coalesce(CU_bo,LTRIM(RTRIM(CU))) as CU
FROM
Rac.viewCarBase as NoCarsFound
join (select Username, userCars.CU from Nep.viewUserCars userCars where UserName = #UserName) as userCars on userCars.CU = NoCarsFound.CU
LEFT OUTER JOIN
(
Select ProductID_bo,
CarTitle_bo,
Company_bo,
CU_bo,
from (
SELECT ProductID as ProductID_bo,
LTRIM(RTRIM(CarTitle)) as CarTitle_bo,
LTRIM(RTRIM(Company)) as Company_bo,
FROM Rac.viewCarBase
join (select Username, userCars.CU from Nep.viewUserCars userCars where UserName = #UserName) as userCars on userCars.CU = Rac.viewCarBase.CU
where ProductID in (Select ProductID from #CarMatch) and
and (CarTitle=#CarTitle or #CarTitle='<All>')
and (Company=#Company or #Company='<All>')
and (CU=#CU or #CU='<All>')
) AS SUB1
Group By
CarTitle_bo,
Company_bo,
CU_bo,
ON
NoCarsFound.CU = CarsFoundDeals.CU_bo
where
and (CarTitle=#CarTitle or #CarTitle='<All>')
and (Company=#Company or #Company='<All>')
and (CU=#CU or #CU='<All>')
end
I would look at the two following pieces of SQL from what you have above:
LEFT OUTER JOIN
(
Select ProductID_bo,
CarTitle_bo,
Company_bo,
CU_bo,
from (
SELECT ProductID as ProductID_bo,
LTRIM(RTRIM(CarTitle)) as CarTitle_bo,
LTRIM(RTRIM(Company)) as Company_bo,
FROM Rac.viewCarBase
join (select Username, userCars.CU from Nep.viewUserCars userCars where UserName = #UserName) as userCars on userCars.CU = Rac.viewCarBase.CU
where ProductID in (Select ProductID from #CarMatch) and
and (CarTitle=#CarTitle or #CarTitle='<All>')
and (Company=#Company or #Company='<All>')
and (CU=#CU or #CU='<All>') --<--HERE!!!!!!!!!!!!!!!!!!!!!!!!
) AS SUB1
and
where
and (CarTitle=#CarTitle or #CarTitle='<All>')
and (Company=#Company or #Company='<All>')
and (CU=#CU or #CU='<All>') -- <--HERE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Both of these appear to be referencing CU in a way that could potentially cause the error you are reporting.
Related
I have address table which can have multiple addresses for a customer based on address_id. I have written CTE to get address_id having more count which is as below
SELECT * FROM (
SELECT address_id
, customer_key
, attention
, ROW_NUMBER() OVER (PARTITION BY customer_key ORDER BY total_addresses DESC, address_id DESC ) AS row_num
FROM (
SELECT address_id
, customer_key
, attention
, count(*) AS total_addresses
FROM (
SELECT address_id
, i.customer_key
, amcp.attention
FROM address_mailing_current_pre amcp
JOIN v_list_of_bus_pre vlobp ON vlobp.busi_role_rel_id = amcp.busi_role_rel_id
JOIN party_current_pre pcp ON pcp.party_link_id = vlobp.party_link_id
JOIN vw_crods_inscope_customer i on trim(vlobp.bus_src_sys_name_cde) || '-' || trim(vlobp.src_sys_party_id) = i.customer_key
AND vlobp.lobstatus_cde LIKE 'RENEWAL%'
AND vlobp.party_bus_role_cde LIKE 'PH%'
JOIN party_grp_role_current_pre pgrc ON pgrc.PARTY_LINK_ID = vlobp.PARTY_LINK_ID AND pgrc.PARTY_GRP_ID = vlobp.PARTY_GRP_ID
JOIN bus_role_rel_pre brrc ON brrc.BUSI_ROLE_REL_ID = vlobp.BUSI_ROLE_REL_ID
UNION ALL
SELECT address_id
, i.customer_key
, arcp.attention
FROM address_risk_current_pre arcp
JOIN v_list_of_bus_pre vlobp ON vlobp.src_sys_bus_id= arcp.src_sys_bus_id
JOIN party_current_pre pcp ON pcp.party_link_id = vlobp.party_link_id
JOIN vw_crods_inscope_customer i on trim(vlobp.bus_src_sys_name_cde) || '-' || trim(vlobp.src_sys_party_id) = i.customer_key
AND vlobp.lobstatus_cde LIKE 'RENEWAL%'
AND vlobp.party_bus_role_cde LIKE 'PH%'
JOIN party_grp_role_current_pre pgrc ON pgrc.PARTY_LINK_ID = vlobp.PARTY_LINK_ID AND pgrc.PARTY_GRP_ID = vlobp.PARTY_GRP_ID
JOIN bus_role_rel_pre brrc ON brrc.BUSI_ROLE_REL_ID = vlobp.BUSI_ROLE_REL_ID
)
GROUP BY address_id, customer_key, attention
ORDER BY total_addresses DESC, address_id DESC
)
)
WHERE row_num = 1
This gives me latest address_id. Sometimes it happens that latest address_id doesn't have address in main address_current table but the next address_id can have address. so I want to use that address_id.
for e.g. If I run below query
SELECT address_id
, customer_key
, attention
, count(*) AS total_addresses
FROM (
SELECT address_id
, i.customer_key
, amcp.attention
FROM address_mailing_current_pre amcp
JOIN v_list_of_bus_pre vlobp ON vlobp.busi_role_rel_id = amcp.busi_role_rel_id
JOIN party_current_pre pcp ON pcp.party_link_id = vlobp.party_link_id
JOIN vw_crods_inscope_customer i on trim(vlobp.bus_src_sys_name_cde) || '-' || trim(vlobp.src_sys_party_id) = i.customer_key
AND vlobp.lobstatus_cde LIKE 'RENEWAL%'
-- AND vlobp.party_bus_role_cde LIKE 'PH%'
WHERE vlobp.src_sys_party_id LIKE '151167409%'
UNION ALL
SELECT address_id
, i.customer_key
, arcp.attention
FROM address_risk_current_pre arcp
JOIN v_list_of_bus_pre vlobp ON vlobp.src_sys_bus_id= arcp.src_sys_bus_id
JOIN party_current_pre pcp ON pcp.party_link_id = vlobp.party_link_id
JOIN vw_crods_inscope_customer i on trim(vlobp.bus_src_sys_name_cde) || '-' || trim(vlobp.src_sys_party_id) = i.customer_key
AND vlobp.lobstatus_cde LIKE 'RENEWAL%'
-- AND vlobp.party_bus_role_cde LIKE 'PH%'
WHERE vlobp.src_sys_party_id LIKE '151167409%'
I get below result
Here 1st address_id doesn't give any address but the next one does. How can I iterate over each address_id in a VIEW and get the address_id which has the address?
From here, and here I have figured out that if I want to aggregate a set of related rows into an array of objects I have to use this syntax:
(select to_json(C) from ( /* subquery */ ) C)
So, if I have three tables: user, creature and their junction table user_creature:
And I want to retrieve each user, and each creature that belongs to this user, I would have to do something like this:
select to_json(T)
from (
select "user".id as user_id,
(select to_json(C) -- !!! There it is
from (
select name, height
from creature
inner join "user_creature" uc on creature.id = "uc".creature_id
inner join "user" u on "uc".user_id = u.id
where u.id = user_id
) C) as "creatures" -- !!! There it is
from "user"
) T;
This query successfully retrieves a list of users and their related creatures:
Is there a way to drop select and from keywords from the query, so that I can write my query like this:
select to_json(T)
from (
select "user".id as user_id,
to_json( -- !!! Calling to_json directly on select statement
select name, height
from creature
inner join "user_creature" uc on creature.id = "uc".creature_id
inner join "user" u on "uc".user_id = u.id
where u.id = user_id
) as "creatures"
from "user"
) T;
It is possible to use a subquery as the argument to to_json, but not practical:
You need to wrap the subquery in a grouping parenthesis: to_json( (SELECT … FROM …) )
The subquery must return exactly one row (but that's normal)
The subquery must return exactly one column. This is a bit harder - you can return a record, but if you build it dynamically (e.g. from a selection of columns, you can hardly control the field names)
(See a demo here).
Instead, use json_build_object if you want to write a single SELECT query only:
SELECT json_build_object(
'user_id', u.id,
'creatures', (
SELECT json_build_object(
'name', c.name,
'height', c.height
)
FROM creature c
INNER JOIN "user_creature" uc ON c.id = uc.creature_id
WHERE uc.user_id = u.id
)
)
FROM "user" u;
And, if you want to be able to retrieve multiple rows use SELECT json_agg(json_build_object(…)) FROM … or ARRAY(SELECT json_build_object(…) FROM …):
SELECT json_build_object(
'user_id', u.id,
'creatures', (
SELECT json_agg(json_build_object(
'name', c.name,
'height', c.height
))
FROM creature c
INNER JOIN "user_creature" uc ON c.id = uc.creature_id
WHERE uc.user_id = u.id
)
)
FROM "user" u;
I'm trying to pull a dataset that returns records ONLY when there are two QUALIFERs present. I've tried left joins, populating data in temp tables then manipulating something, then numerous having clauses (resulting in subquery selects, and additional groups). I would appreciate any assistance on what I can do further.
Query:
Select E, CASE WHEN QUALIFER = '1' THEN 'NAME1' WHEN QUALIFER = '2' then 'NAME2' ELSE 'FINALNAME' END AS TYPE, count(rt.ID) 'Number '
from TABLE_ONE co (nolock)
join TABLE_TWO rt (nolock)
on co.ID = rt.ID
where co.E in (select * from #tempEmail)
AND convert(date,co.INSERTED_TIMESTAMP)between '1/1/2020' and '8/15/2020'
AND TRANS_STATUS = 'APPROVED'
group by E, QUALIFER
order by E, QUALIFER
Current resultset:
E TYPE Number
FAKEEMAIL1#Gmail NAME1 1
FAKEEMAIL1#Gmail NAME2 1
otheremailj#gmail.com Name1 21
Desired resultset:
E TYPE Number
FAKEEMAIL1#Gmail NAME1 1
FAKEEMAIL1#Gmail NAME2 1
Thank you.
Let's try the below query. I used a temp table to make things more simple for my mind.
if object_id('tempdb.dbo.#email') is not null drop table #email
create table #email
(
email varchar(50),
typeValue varchar(15),
Number int
)
insert into #email(email, typeValue, Number)
Select
E,
CASE WHEN QUALIFER = '1' THEN 'NAME1' WHEN QUALIFER = '2' then 'NAME2' ELSE 'FINALNAME' END AS TYPE,
count(rt.ID) 'Number '
from TABLE_ONE co (nolock)
join TABLE_TWO rt (nolock)
on co.ID = rt.ID
where co.E in (select * from #tempEmail)
AND convert(date,co.INSERTED_TIMESTAMP)between '1/1/2020' and '8/15/2020'
AND TRANS_STATUS = 'APPROVED'
group by E, QUALIFER
select a.email, a.typeValue
from #email a
inner join
(
select email, typeValue, rank() over (partition by email order by typeValue) as typeCount
from #email t
) as b
on b.email = a.email
and b.typeCount > 1
I'm trying to convert the following Oracle query to PostgreSQL:
MERGE into feepay.TRPT_W2_REPORTS TRPT1
USING(
WITH RWS AS
(SELECT PROG.BINCLIENT, TRPT.PUT_DIRECTORY
FROM feepay.program2 PROG
INNER JOIN feepay.TRPT_W2_PROGRAMS TRPT
ON (PROG.BINCLIENT = TRPT.BINCLIENT OR PROG.ISSUER_ID = TRPT.ISSUER_ID))
SELECT TCI.CUSTOMERNAME AS ACCOUNT,
TC.CUSTOMER_ID AS urn,
TC.LAST_NAME,
TC.FIRST_NAME,
TC.DOB,
TCA.ADDRESS
FROM feepay.TAU_CARDNUMBERS TCN
INNER JOIN feepay.TAU_CUSTOMER_CARDNUMBER TCCN ON (TCN.CARDNUMBER_ID = TCCN.CARDNUMBER_ID)
INNER JOIN feepay.TBLCUSTOMERS TC ON (TCCN.CUSTOMER_ID = TC.CUSTOMER_ID)
LEFT JOIN feepay.tau_customeraddress TCA ON (TC.CUSTOMER_ID = TCA.CUSTOMER_ID)
INNER JOIN feepay.TAU_ISSUER TI ON (TI.ISSUER_ID = TCN.ISSUER_ID)
INNER JOIN feepay.TBLCUSTOMERS TCI ON (TCI.CUSTOMER_ID = TI.CUSTOMER_ID)
LEFT JOIN feepay.TRPT_W2_REPORTS TRPT ON (TRPT.URN = TC.CUSTOMER_ID)
WHERE BINCLIENT IN (SELECT BINCLIENT FROM RWS)
AND TC.CUSTOMERNAME NOT IN ('freepay card','svds card')) TRPT2
ON (TRPT1.URN = TRPT2.URN)
WHEN MATCHED THEN
UPDATE SET
TRPT1.ACCOUNT = TRPT2.ACCOUNT,
TRPT1.LAST_NAME = TRPT2.LAST_NAME,
TRPT1.FIRST_NAME = TRPT2.FIRST_NAME,
TRPT1.DOB = TRPT2.DOB,
TRPT1.ADDRESS = TRPT2.ADDRESS,
TRPT1.LAST_UPDATE = now(),
TRPT1.STATUS = 'u' /* uPDATED */
WHEN NOT MATCHED THEN
INSERT (ACCOUNT, URN, LAST_NAME, FIRST_NAME, ISENTITY, DOB, ADDRESS, LAST_UPDATE, STATUS)
VALUES (TRPT2.ACCOUNT, TRPT2.URN, TRPT2.LAST_NAME, TRPT2.FIRST_NAME, 'y', TRPT2.DOB, TRPT2.MIDDLE_NAME,
TRPT2.ADDRESS, now(), 'i');
Unfortunately PostgreSQL does not support MERGE, so I'm really stuck. I hope some database pro out of here can help me with this.
You can use INSERT ON CONFLICT () instead:
insert into feepay.TRPT_W2_REPORTS (ACCOUNT, URN, LAST_NAME, FIRST_NAME, ISENTITY, DOB, ADDRESS, LAST_UPDATE, STATUS)
WITH RWS AS (
SELECT PROG.BINCLIENT, TRPT.PUT_DIRECTORY
FROM feepay.program2 PROG
INNER JOIN feepay.TRPT_W2_PROGRAMS TRPT
ON (PROG.BINCLIENT = TRPT.BINCLIENT OR PROG.ISSUER_ID = TRPT.ISSUER_ID)
)
SELECT TCI.CUSTOMERNAME,
TC.CUSTOMER_ID,
TC.LAST_NAME,
TC.FIRST_NAME,
'Y'
TC.DOB,
TCA.ADDRESS,
now(),
'i'
FROM feepay.TAU_CARDNUMBERS TCN
INNER JOIN feepay.TAU_CUSTOMER_CARDNUMBER TCCN ON (TCN.CARDNUMBER_ID = TCCN.CARDNUMBER_ID)
INNER JOIN feepay.TBLCUSTOMERS TC ON (TCCN.CUSTOMER_ID = TC.CUSTOMER_ID)
LEFT JOIN feepay.tau_customeraddress TCA ON (TC.CUSTOMER_ID = TCA.CUSTOMER_ID)
INNER JOIN feepay.TAU_ISSUER TI ON (TI.ISSUER_ID = TCN.ISSUER_ID)
INNER JOIN feepay.TBLCUSTOMERS TCI ON (TCI.CUSTOMER_ID = TI.CUSTOMER_ID)
LEFT JOIN feepay.TRPT_W2_REPORTS TRPT ON (TRPT.URN = TC.CUSTOMER_ID)
WHERE BINCLIENT IN (SELECT BINCLIENT FROM RWS)
AND TC.CUSTOMERNAME NOT IN ('freepay card','svds card')) TRPT2
ON CONFLICT (URN)
DO UPDATE SET
ACCOUNT = excluded.ACCOUNT,
LAST_NAME = excluded.LAST_NAME,
FIRST_NAME = excluded.FIRST_NAME,
DOB = excluded.DOB,
ADDRESS = excluded.ADDRESS,
LAST_UPDATE = now(),
STATUS = 'u' /* uPDATED */
You need to verify if the columns in the SELECT list match the columns as listed in the INSERT column list.
I have two tables, plus a matching table. For argument's sake, let's call them Recipes and Ingredients. Each Recipe should have at least one Ingredient, but may have many. Each Ingredient can be used in many Recipes.
Recipes Ingredients Match
=============== =============== ===============
ID int ID int RecipeID int
Name varchar Name varchar IngredientID int
Sample data:
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
Here's the problem: I need to sort the Recipes based on the name(s) of their Ingredients. Given the above sample data, I would need this sort order for recipes:
Turkey Sandwich (First ingredient bread, then cheese)
Chicken Sandwich (First ingredient bread, then chicken)
Pizza (First ingredient cheese)
Soup (First ingredient chicken)
Ranking the recipes by the first ingredient is straightforward:
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;
Beyond that, I'm stuck. In my example above, this almost works, but leaves the two sandwiches in an ambiguous order.
I have a feeling that the MIN(SortOrder) should be replaced by something else, maybe a correlated subquery looking for the non-existence of another record in the same CTE, but haven't figured out the details.
Any ideas?
(It is possible for a Recipe to have no ingredients. I don't care what order they come out in, but the end would be ideal. Not my main concern at this point.)
I'm using SQL Server 2008 R2.
Update: I added an SQL Fiddle for this and updated the example here to match:
http://sqlfiddle.com/#!3/38258/2
Update: I have a sneaking suspicion that if there is a solution, it involves a cross-join to compare every combination of Recipe/Ingredient against every other, then filtering that somehow.
I think this will give you what you want (based on your supplied Fiddle)
-- 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;
It just uses POWER to ensure that the most significant ingredients get weighted first.
This will work for any number of recipes and up to 120 ingredients (in total)
Will not work if recipes contain duplicate ingredients, though you could filter those out if they could occur
Binary Flag version:
;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 version:
-- 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
This should get you what you need:
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;
The only alternative way I can think of to do it, is to use dynamic sql to generate a pivot
This doesn't have the limitation on the number of ingredients that my alternative has, but doesn't exactly feel elegant!
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
Create a function and use that.
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]