CASE Statement with a JOIN and GROUP BY - sql-server

I'm trying to select the winners of a race from an event by the specific competition they entered, for example.
The competition table
competition_ID, eventss_ID, competitor_ID, stageName, roundNo, startTime, finisheTime, judges_ID
The eventss table
eventss_ID, eventsName, noOfStages, eventsDate, entryFee, venue_ID, judges_ID
The results I want are;
Event RoundNo competitior_ID Competiton Winner
swimming 1 COM101 1st Place
swimming 1 COM213 2nd Place
swimming 2 COM101 1st Place
swimming 2 COM234 2nd Place
golf 1 COM654 1st Place
golf 1 COM874 2nd Place
Query I tried:
SELECT *
,CASE
WHEN finshTime = (SELECT MIN(finshTime) FROM competition) THEN '1st Place'
WHEN finshTime = (SELECT MAX(finshTime) FROM competition) THEN '2nd Place'
ELSE 'Draw'
END [Competition Winner]
FROM competition
JOIN eventss on eventss.eventss_ID = competition.eventss_ID
GROUP BY competition.roundNo

This would probably work to produce the desired output but it's hard to tell without proper sample data.
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, CTE2 AS (
SELECT *
, CASE (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO) - (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO)
WHEN 0 THEN 'Draw'
ELSE CASE RN
WHEN 1 THEN '1st Place'
ELSE '2nd Place' END END Drawn
FROM CTE c
WHERE RN IN (1, 2))
SELECT e.eventsName [Event], CTE2.RoundNo, CTE2.competitor_ID, CTE2.Drawn [Competition Winner]
FROM eventss e
JOIN CTE2 ON CTE2.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, CTE2.roundNo, CTE2.Drawn
Note: I'm making the assumption that "finishtime" is stored as TIME or DATETIME. If it's stored as something else, this won't work.
EDIT: In the case of more than two people tied for first place, or in the case of a tie for second place, this query should work...
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, tiesforfirst AS (
SELECT *
, '1st Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo))
, tiesforsecond AS (
SELECT *
, '2nd Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo)
AND (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = T.eventss_ID AND roundNo = T.roundNo) = 1)
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 1st' END [Competition Winner]
FROM eventss e
JOIN tiesforfirst tf ON tf.eventss_ID = e.eventss_ID
UNION
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforsecond WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 2nd' END [Competition Winner]
FROM eventss e
JOIN tiesforsecond tf ON tf.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, roundNo

Related

ROW_NUMBER returning the same value for all rows (+ query performance)

I have the following query (built to showcase the problem)
WITH
CategoryPromotions
AS
(
SELECT CategoryId = 7, Price = 10
UNION ALL
SELECT CategoryId = 3, Price = 15
UNION ALL
SELECT CategoryId = 1, Price = 5
)
,
Products
AS
(
SELECT Id = 1, Price = 20
)
,
ProductsCategories
AS
(
SELECT ProductId = 1, CategoryId = 2
UNION ALL
SELECT ProductId = 1, CategoryId = 8
UNION ALL
SELECT ProductId = 1, CategoryId = 6
)
,
Tally
AS
(
SELECT N = 1
UNION ALL
SELECT N = 2
UNION ALL
SELECT N = 3
UNION ALL
SELECT N = 4
UNION ALL
SELECT N = 5
)
,
Hierarchy
AS
(
SELECT Id = 2, SortPath = 0x00000001000000070000000400000002
UNION ALL
SELECT Id = 8, SortPath = 0x00000001000000070000000400000008
UNION ALL
SELECT Id = 6, SortPath = 0x0000000300000006
)
SELECT ProductsCategories.*, xD.*
FROM Products
RIGHT JOIN ProductsCategories
ON Products.Id = ProductsCategories.ProductId
CROSS APPLY
(
SELECT TOP (1) promos.CategoryId
, Products.Price AS BasePrice
, promos.Price
, (
CASE
WHEN promos.Price IS NOT NULL THEN
(Products.Price - promos.Price)
ELSE
Products.Price
END
) AS DiscountedPrice
, ROW_NUMBER() OVER
(
ORDER BY CASE
WHEN promos.Price IS NOT NULL THEN
(Products.Price - promos.Price)
ELSE
Products.Price
END
ASC
) AS PriceRank
FROM (SELECT ProductsCategories.ProductId, ProductsCategories.CategoryId) bpc
CROSS APPLY
(
SELECT TOP (1) categories.CategoryId
, catpromo.Price
FROM
(
SELECT CategoryId = CAST(SUBSTRING(Hierarchy.SortPath,Tally.N,4) AS INT)
, Tally.N
FROM Hierarchy
INNER JOIN Tally
ON Tally.N BETWEEN 1
AND DATALENGTH(Hierarchy.SortPath)
WHERE Hierarchy.Id = bpc.CategoryId
GROUP BY SUBSTRING(Hierarchy.SortPath,tally.N,4)
, tally.n
) AS categories
INNER JOIN CategoryPromotions catpromo
ON categories.CategoryId = catpromo.CategoryId
ORDER BY categories.N DESC
) AS promos
WHERE bpc.ProductId = 1
ORDER BY PriceRank
) AS XD
WHERE products.Id = 1;
This is the query result:
Why the ROW_NUMBER isn't working? And is there anything I can do in order to improve the query performance? This will be applied to a million row result query for each individual product. I tried to fake +/- the structure that it will be used in.
The desired result is the 1 row that has the lowest DiscountedPrice. (Cannot use MIN, since I need all the columns)
EDIT: Without TOP (1)
The ROW_NUMBER is working just fine. The problem is that you're calculating it for each row in your ProductCategories table. Here's a lighter version of your query.
WITH cteProductsCategoriesDiscounts AS(
SELECT
ProductsCategories.ProductId
, ProductsCategories.CategoryId
, promos.CategoryId AS promoCategoryId
, Products.Price AS BasePrice
, promos.Price
, ISNULL(Products.Price - promos.Price, Products.Price) AS DiscountedPrice
, ROW_NUMBER() OVER ( PARTITION BY ProductsCategories.ProductId ORDER BY ISNULL(Products.Price - promos.Price, Products.Price) ) AS PriceRank
FROM Products
RIGHT JOIN ProductsCategories ON Products.Id = ProductsCategories.ProductId
CROSS APPLY (
SELECT TOP (1)
CAST(SUBSTRING(Hierarchy.SortPath,N*4-3,4) AS INT) AS CategoryId
, catpromo.Price
FROM Hierarchy
INNER JOIN Tally t ON t.N BETWEEN t.n AND DATALENGTH(Hierarchy.SortPath)/4
INNER JOIN CategoryPromotions catpromo ON CAST(SUBSTRING(Hierarchy.SortPath,N*4-3,4) AS INT) = catpromo.CategoryId
WHERE Hierarchy.Id = ProductsCategories.CategoryId
ORDER BY t.N
) AS promos
WHERE ProductsCategories.ProductId = 1
)
SELECT *
FROM cteProductsCategoriesDiscounts
WHERE PriceRank = 1;
EDIT: Made an adjustment to allow multiple products.

Top N percent Desc and Top M percent Asc

I am trying to get top 5 customertypes and show data for each 5 customer types, The balance (which can be any amount) I show them as "Other Customer Types". my issue is since the rows can be random and not perfectly divisible by a number then there can be repeated values in the top 5 showing up in the "Other" group which overstates the Total sales.
the Data is also being rendered in SSRS
My code using TOP PERCENT:
select final.[description], sum(final.YTDSales$) as YTDSales$
FROM(
select top 25 percent pytd2.[Description], sum(pytd2.YTDSales$) as YTDSales$
FROM(
-- ytd sales
select re.SIC_Desc as [description], sum((ol.NetAmt - ol.WhlOrdDiscAmt) / #exrt) AS YTDSales$
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN #FStartDate AND #EndDate AND ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.SIC_Desc
)PYTD2
group by pytd2.[description]
order by sum(pytd2.YTDSales$) DESC
UNION ALL
select top 75 percent 'Other' as 'description', sum(pytd.YTDSales$) as YTDSales$
FROM(
-- ytd sales
select re.SIC_Desc as [description], sum((ol.NetAmt - ol.WhlOrdDiscAmt) / #exrt) AS YTDSales$
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN #FStartDate AND #EndDate AND ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.SIC_Desc
)PYTD
group by Ppytd.[description]
order by sum(pytd.YTDSales$)
)final
group by final.[Description]
order by sum(final.YTDSales$) DESC
my results:
As you can see the Large Independent and Other has the same figure of $2280.60 in YTDQty since it is being repeated
I was picturing something like this:
with data as (
-- your base query here grouped and summarized by customer type
), rankedData as (
select *, row_number() over (order by YTDSales$ desc) as CustTypeRank
from data
)
select
case when CustTypeRank <= 5 then min("description") else 'Others' end as "description",
sum(YTDSales$) as YTDSales$
from rankedData
group by case when CustTypeRank <= 5 then CustTypeRank else 999 end
order by case when CustTypeRank <= 5 then CustTypeRank else 999 end
I actually used RANK instead which worked great :-
select 0 as rankytd, RANK() OVER(ORDER BY sum(ol.NetAmt - ol.WhlOrdDiscAmt) DESC) as rankpytd, re.sic, ol.VendId, vd.name, re.SIC_Desc As [description], 0 AS YTDQty, sum(ol.Quantity) AS PYTDQty
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join dbo.Product p on ol.Cono = p.Cono and ol.prodid = p.ProdId and p.ProdCatId in (#pcat)
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN DATEADD(YEAR, -1,#FStartDate) AND DATEADD(YEAR, -1, #EndDate) and ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.sic, ol.VendId, vd.Name, re.SIC_Desc

How to use CROSS APPLY in this scenario

I have a ProductStatus table as listed below. I need to list all products whose latest status is “SU”. Along with that I need to list what was the previous status of this product.
Based on referring various posts, it seems like CROSS APPLY will be suitable for this. I made an attempt as listed below but that didn’t give the expected result.
What is the best way to achieve this in SQL Server 2005 (without using subquery)?
DECLARE #ProductStatus TABLE (ProductStatusID INT, productCode VARCHAR(5), statusCode VARCHAR(2))
INSERT INTO #ProductStatus
SELECT 1,'10011','RE' --Recevied
UNION
SELECT 2,'10011','SU' --Suspended
UNION
SELECT 3,'10012','IT' -- In Transit
UNION
SELECT 4,'10012','RE' -- Received
UNION
SELECT 10,'10012','PR' -- Produced
UNION
SELECT 12,'10012','SU' -- Suspended
UNION
SELECT 14,'10013','RE' -- Recevied
UNION
SELECT 16,'10014','SU' -- Recevied
UNION
SELECT 18,'10014','RE' -- Recevied
CROSS APPLY attempt
SELECT *
FROM #ProductStatus P
CROSS APPLY
(
SELECT MAX(V.ProductStatusID) as maxVal
FROM #ProductStatus V
WHERE V.ProductCode = P.ProductCode
AND V. ProductStatusID < P.ProductStatusID
GROUP BY V.ProductCode
)ML
WHERE P.statusCode = 'SU'
EXPECTED RESULT
You can do this with cross apply but I think row_number() is an easier approach:
select ProductCode,
max(case when seqnum = 1 then statusCode end) as LastStatus,
max(case when seqnum = 2 then statusCode end) as PrevStatus
from (select p.*,
row_number() over (partition by ProductCode order by ProductStatusId desc) as seqnum
from #ProductStatus p
) p
group by ProductCode
having max(case when seqnum = 1 then statusCode end) = 'SU';
Lijo, I've structured it as a CTE so you can see how I've developed my ideas. You can refactor it as sub-queries without affecting the meaning if you are more comfortable with those.
;with MostRecentStatus as (
select
MAX(ProductStatusID) as ProductStatusID,
productCode
from #ProductStatus as p1
group by productCode
)
,MostRecentIsSU as (
select
p2.ProductStatusID,
p2.productCode,
p2.statusCode
from MostRecentStatus as mrs
inner join #ProductStatus as p2
on p2.ProductStatusID = mrs.ProductStatusID
and p2.statusCode = 'SU'
)
select
m.ProductStatusID,
m.productCode,
m.statusCode,
p3.statusCode as PrevStatus,
p3.ProductStatusID as PrevProductStatusID
from MostRecentIsSU as m
left outer join #ProductStatus as p3
on p3.productCode = m.productcode
and p3.ProductStatusID = m.ProductStatusID - 1;
Edit: ..and here's the ROW_NUMBER() version with kudos to #attila.
;with InSquence as
(
select
ProductStatusID,
productCode,
statusCode,
ROW_NUMBER() OVER(PARTITION BY productCode ORDER BY ProductStatusID desc) as Sequence
from #ProductStatus
)
,FirstIsSU as
(
select
ProductStatusID,
productCode
from InSquence
where Sequence = 1
and statusCode = 'SU'
)
,PreviousCode as
(
select
ProductStatusID,
productCode,
statusCode
from InSquence
where Sequence = 2
)
select
f.ProductStatusID,
f.productCode,
'SU' as CurrentStatus,
p.statusCode as PrevStatus,
p.ProductStatusID as PrevProductStatusID
from FirstIsSU as f
left outer join PreviousCode as p
on p.productCode = f.ProductCode;
Here is a convoluted solution which serves mainly to illustrate that this should probably be done using row_number() :)
SELECT
F.productCode, F.statusCode, F.productStatusID, F.PriorProductStatusID, PriorStatus.statusCode
FROM
(
SELECT
PCS.productCode, PCS.statusCode, PCS.productStatusID, MAX(PS.productStatusID) PriorProductStatusID
FROM
(
SELECT productCode, MAX(productStatusID) productStatusID
FROM #ProductStatus
GROUP BY productCode
) LatestStatus
INNER JOIN
#ProductStatus PCS
ON PCS.productCode = LatestStatus.productCode
AND PCS.productStatusID = LatestStatus.productStatusID
AND PCS.statusCode = 'SU'
LEFT OUTER JOIN
#ProductStatus PS
ON PS.productCode = PCS.productCode
AND PS.productStatusID < PCS.productStatusID
GROUP BY PCS.productCode, PCS.statusCode, PCS.productStatusID
) F
LEFT OUTER JOIN
#ProductStatus PriorStatus
ON F.productCode = PriorStatus.productCode
AND F.PriorProductStatusID = PriorStatus.ProductStatusID

How to select entries that are back to back?

How would I select c_user_id who have made back to back entries in a SQL Server 2008 database ?
Preferably people who have made more than 3 back to back entries like pras.chla#gmail.com below (sorting by c_id desc and c_id is an identity column)
c_id c_user_id c_entry
1427 xermadr.asdf#me.com 155575
1426 pras.chla#gmail.com 155829
1425 pras.chla#gmail.com 155826
1424 pras.chla#gmail.com 155828
1423 pras.chla#gmail.com 155830
1422 sdfe.qqol#gmail.com 155559
thanks again ?
One way
SELECT DISTINCT c_user_id
FROM tab t1
CROSS APPLY (SELECT 1 AS C
FROM (SELECT TOP 2 *
FROM tab t2
WHERE t2.c_id < t1.c_id
ORDER BY t2.c_id DESC) T
HAVING COUNT(c_user_id) = 2 AND COUNT(DISTINCT c_user_id) = 1 AND MIN(c_user_id) = t1.c_user_id) CA
Or another
WITH T AS
(
SELECT *,
ROW_NUMBER() OVER (order by c_id) -
ROW_NUMBER() OVER (PARTITION BY c_user_id order by c_id) AS Grp
FROM tab t1
)
SELECT DISTINCT c_user_id
FROM T
GROUP BY c_user_id, Grp
HAVING COUNT(*) >=3
;WITH someUserTableWithOrderNumber as
(
SELECT ROW_NUMBER ( ) OVER (order by c_id) OrderNumber,
c_id,
c_user_id,
c_entry
FROM someUserTable
)
SELECT DISTINCT a.c_user_id
FROM someUserTableWithOrderNumber a
JOIN someUserTableWithOrderNumber b on a.OrderNumber = b.OrderNumber + 1 AND a.c_user_id = b.c_user_id
JOIN someUserTableWithOrderNumber c on b.OrderNumber = c.OrderNumber + 1 AND b.c_user_id = c.c_user_id
JOIN someUserTableWithOrderNumber d on c.OrderNumber = d.OrderNumber + 1 AND c.c_user_id = d.c_user_id

Case statement in sql using other selected columns in the same statement

I would like to know if the following is possible in SQL server 2005. Column A and B are calculated using other case statements in my actual stored proc. I don't want to repeat the same for another field unnecessarily. If this is not syntactically possible, any other ideas?
SELECT A, B, CASE WHEN column1='1' THEN A ELSE B END Col1.
Modified version of actual query provided as requested. CTE kind of seems to be tough in this model. WANNABE is the column I want to accomplish in the sub select statement.
SELECT 1 AS Region, 'Test',
CAST(Work AS NUMERIC(18,2)) Work,
Work + 2 AS Work2,
WANNABE
FROM
(
SELECT
ROW_NUMBER() OVER(PARTITION BY G.Value, C.C, FR.Mod1 ORDER BY FR.Date DESC, FG.Date DESC, FC.Date DESC) ROW,
CASE WHEN COALESCE(FR.Mod1, '') = '' THEN '' ELSE FR.Mod1 END Mod1,
CASE WHEN #var1=1 AND #var2 = 1 THEN FR.Col1 * G.Value
WHEN #var1=1 AND #var2 = 0 THEN FP.Col1 * G.Value END Work,
CASE WHEN 1=1 THEN Work ELSE 1 END WANNABE,
(
SELECT Col3
FROM Table2
WHERE c = FR.Value
) AS Custom
FROM MainTable FR
JOIN #C C ON FR.Col2 = C.Col2
LEFT JOIN Function1(#VersionDate) cv ON cv.Code = C.Code
LEFT JOIN Function2(#VersionDate) hv ON hv.Code = C.Code
LEFT JOIN #G G ON 1 = 1
LEFT JOIN SubTable1 FG ON FG.Number = G.Value, 2 AND FG.Date = #VersionDate
LEFT JOIN SubTable2 FO ON FO.Number = G.Value
AND FO.Date = #VersionDate AND FO.Code = FR.Code AND FR.Mod1 = FO.Mod1
LEFT JOIN SubTable3 FP ON FP.Code = FR.Code AND FP.VersionDate = #Versiondate
AND CASE WHEN DATALENGTH(FR.Mod1) = 0 THEN '00' ELSE FR.Mod1 END = CASE WHEN DATALENGTH(FP.Mod1) = 0 THEN '00' ELSE FP.Mod1 END
LEFT JOIN SubTable4 FC ON FC.Date = #VersionDate
WHERE FR.Date = #VersionDate
) x
WHERE x.Row = 1
AND RTRIM(LTRIM(x.Col1)) IN ('', '2')
You can define the A,B column aliases in a CTE then reference them in an outer select.
;WITH CTE AS
(
SELECT CASE ... END AS A,
CASE ... END AS B,
column1
FROM your_table
)
SELECT A,
B,
CASE WHEN column1='1' THEN A ELSE B END Col1
FROM CTE
Similarly you can also define them in a CROSS APPLY that is sometimes a bit less verbose.
A silly example just to show the syntax is
SELECT A,
B,
CASE WHEN type='P' THEN A ELSE B END Col1
FROM master..spt_values
CROSS APPLY (SELECT CASE WHEN number %2 = 1 THEN 1 END,
CASE WHEN number %2 = 0 THEN 0 END) T(A,B)
Following your update you can replace the derived table with a CTE and nest CTEs as follows
;WITH x as
(
SELECT
ROW_NUMBER() OVER(PARTITION BY G.Value, C.Code, FR.Mod1 ORDER BY FR.Date DESC, FG.Date DESC, FC.Date DESC) ROW,
...<snip>
WHERE FR.Date = #VersionDate
),
x2 As
(
SELECT *,
CASE WHEN 1=1 THEN Work ELSE 1 END WANNABE
FROM x
)
SELECT 1 AS Region, 'Test',
CAST(Work AS NUMERIC(18,2)) Work,
Work + 2 AS Work2,
WANNABE
FROM x2
WHERE x2.Row = 1
AND RTRIM(LTRIM(x2.Col1)) IN ('', '2')
Yeah it is posible, but how is all your sql statement? You can use the case statement in the select statement as you are using it.
Something like this
SELECT SUM((CASE WHEN column1='1' THEN 10 ELSE 0 END)) AS A, SUM((CASE WHEN column1='2' THEN 10 ELSE 0 END)) AS B
FROM YourTable

Resources