The query below seems to work despite being wrong.
The Color column doesn't belong in an aggregate function but I don't want to group by Color. I want to return the color for the minimum priority grouped by Vehicle.
I hope the below is enough to work with - I was hoping for a quick answer but will go into more detail if necessary.
SELECT alert.VehicleID,
min(hotcol.[Priority]) as [Priority]
min(hotcol.Color) as Color,
FROM [ALERTS] alert
INNER JOIN [HOTLISTS] hotlist ON alert.[HotlistID] = hotlist.[HotlistID]
INNER JOIN [HOTLIST_COLORS] hotcol ON hotlist.ColorID = hotcol.ColorID
WHERE VehicleID = 17513851
GROUP BY alert.VehicleID
You can use the ranking function ROW_NUMBER to do this. Something like this:
WITH CTE
AS
(
SELECT
alert.VehicleID,
hotcol.Color,
hotcol.[Priority],
ROW_NUMBER() OVER(PARTITION BY alert.VehicleID
ORDER BY hotcol.[Priority]) AS RN
FROM [ALERTS] alert
INNER JOIN [HOTLISTS] hotlist ON alert.[HotlistID] = hotlist.[HotlistID]
INNER JOIN [HOTLIST_COLORS] hotcol ON hotlist.ColorID = hotcol.ColorID
WHERE VehicleID = 17513851
)
SELECT
VehicleID,
Color,
[Priority]
FROM CTE
WHERE rn = 1;
The ROW_NUMBER function will give a ranking number for each alert.VehicleID and each group will be ordered by priority. Then WHERE rn = 1 will filter out all the rows except the minimum one which has rn = 1.
Related
What would be the most efficient way to eliminate records in WHERE clause using TOP 1 logic?
Table tblQuoteStatusChangeLog is not in a JOIN.
But based on value in this table I need to eliminate records that have NewQuoteStatusID = 12
It works the way it is, but I am looking for more efficient way, since I have Sort (Top N Sort) operator that is too expansive.
SELECT
Q.ControlNo
,sum(fid.amtbilled) as Premium
FROM
[dbo].tblQuotes Q
inner join [dbo].[tblFin_Invoices] FI on Q.QuoteID = FI.QuoteID and FI.failed = 0
inner join [dbo].[tblFin_InvoiceDetails] FID on FI.[InvoiceNum] = FID.InvoiceNum
WHERE (
SELECT TOP 1 NewQuoteStatusID
FROM tblQuoteStatusChangeLog
WHERE (ControlNo = Q.ControlNo)
ORDER BY Timestamp DESC
) <> 12
Group by
Q.ControlNo
Your code is RBAR; performing the same subquery 1 at a time, which is very inefficient.
You worry about "sort", but that by itself would not be a problem. Look further up and left of the plan; to the nested loop. See the fat input line at the top and thin just below. Basically you're hitting your sort very many times.
Suggestion: try to use a set-based solution. "Prepare" the data you require for the WHERE clause "in advance", so you can eliminate the RBAR. Imagine you had LatestStatus as a table with ControlNo and StatusID columns. It would be much simpler to apply your filter; and the Query Optimiser should be able to find a more efficient overall plan.
You can set this up using a CTE.
;with StatusByControlNo as (
SELECT ROW_NUMBER() OVER(PARTITION BY ControlNo ORDER BY Timestamp DESC) AS RowNo,
ControlNo, Timestamp, NewQuoteStatusID
FROM tblQuoteStatusChangeLog
) ...
/*Easy to get Latest status per ControlNo from here*/
SELECT ControlNo, NewQuoteStatusID
FROM StatusByControlNo
WHERE RowNo = 1
Now with a few tweaks your query becomes:
;with StatusByControlNo as (
SELECT ROW_NUMBER() OVER(PARTITION BY ControlNo ORDER BY Timestamp DESC) AS RowNo,
ControlNo, Timestamp, NewQuoteStatusID
FROM tblQuoteStatusChangeLog
)
SELECT
Q.ControlNo,
sum(fid.amtbilled) as Premium
FROM
tblQuotes Q
inner join tblFin_Invoices FI
on Q.QuoteID = FI.QuoteID and FI.failed = 0
inner join tblFin_InvoiceDetails FID
on FI.InvoiceNum = FID.InvoiceNum
inner join StatusByControlNo S
on S.ControlNo = Q.ControlNo and S.RowNo = 1
WHERE
S.ControlNo <> 12
Group by Q.ControlNo
It should go without saying you could try a number of variations on this. But the core principle is to reduce RBAR and look for solutions that are more 'set-based'.
I would like to executing this query in a table below, but I can't, it is saying that needs to use having and group by, someone can show me how to it, I am using SQL Server.
SELECT
products.name AS item,
users.name AS buyer,
auctionValues.value AS value
FROM
auctionValues
JOIN
products ON (auctionValues.productId = products.id)
JOIN
auctionOrders ON (auctionsValues.auctionOrderId = auctionOrders.id)
AND (auctionOrders.id = '987')
JOIN
users ON auctionValues.userId = users.id
WHERE
auctionValues.value >= MIN(auctionValues.value) * 1.05
ORDER BY
products.id ASC,
auctionValues.value DESC
This may works :
;with minValues as (
select productId, MIN(auctionValues.value) * 1.05 as minValue
)
SELECT
products.name AS item,
users.name AS buyer,
auctionValues.value AS value
FROM
auctionValues
JOIN
products ON (auctionValues.productId = products.id)
JOIN
auctionOrders ON (auctionsValues.auctionOrderId = auctionOrders.id)
AND (auctionOrders.id = '987')
JOIN
users ON auctionValues.userId = users.id
WHERE
auctionValues.value >= (SELECT minValue from minValues WHERE products.Id = minValues.productId)
ORDER BY
products.id ASC,
auctionValues.value DESC
Typically you group by the columns which are not in the min function.
Seen the following links.
https://learn.microsoft.com/en-us/sql/t-sql/queries/select-group-by-transact-sql?view=sql-server-2017
https://learn.microsoft.com/en-us/sql/t-sql/functions/aggregate-functions-transact-sql?view=sql-server-2017
You can't mix aggregate functions like MIN to non-aggregated columns without including them in GROUP BY. However in your case you just need to use a SELECT subquery to get the MIN value you are using in the condition.
Change this part:
WHERE auctionValues.value >= (SELECT MIN(auctionValues.value) FROM auctionValues) * 1.05
I have a table which get result as left one of the photo.However, I would like to get the result which order by:
1. first, compare the number in val field which group field's data must be 'total'.
2. then, all data with same value at item field will get an order based on the order at condition 1
The right part of the photo is what I want the result is. How should I set the logic with ORDER BY? Thank you.
One approach is to join to a subquery which finds the totals for each item, and then to sort using that.
SELECT
t1.item,
t1.[group],
t1.val
FROM yourTable t1
INNER JOIN
(
SELECT item, val AS total
FROM yourTable
WHERE [group] = 'total'
) t2
ON t1.item = t2.item
ORDER BY
t2.total DESC,
t1.item,
CASE WHEN t1.[group] = 'total' THEN 1 ELSE 0 END,
t1.[group];
I need to display only top 10 Class based on Total (SUM(Premium)) column.
I go to group ClassCode properties --> Filters and setting top 10 by SUM(NetWrittenPremium) but it doesnt work.
I need to display only top 10 and also the total amount should be for only those 10.
Cant understand how to achieve it.
here is my query:
;WITH cte_TopClasses
AS (
SELECT
c.YearNum,
c.MonthNum,
DD.ClassCode,
ISNULL(SUM(prm.Premium),0) as NetWrittenPremium
FROM tblCalendar c
LEFT JOIN ProductionReportMetrics prm ON c.YearNum = YEAR(prm.EffectiveDate) and c.MonthNum = MONTH(prm.EffectiveDate)
AND CompanyGUID = '18E04C99-D796-4CFA-B1E7-28328321C8AD'
LEFT JOIN [dbo].[Dynamic_Data_GLUnitedSpecialty] DD on prm.QuoteGUID = DD.QuoteGuid
WHERE ( c.YearNum = YEAR(GETDATE())-1 and c.MonthNum >= MONTH(GETDATE())+1 ) OR
( c.YearNum = YEAR(GETDATE()) and c.MonthNum <= MONTH(GETDATE()) )
GROUP BY c.YearNum,
c.MonthNum,
DD.ClassCode--,prm.Premium
)
SELECT ROW_NUMBER() OVER (PARTITION BY ClassCode ORDER BY NetWrittenPremium DESC),*
FROM cte_TopClasses
and my outcome from the query:
#Alan
Thanks.
Query output look like that:
If I add ClassCode in order by in dense_rank then $142,000 is not gonna be in a query. Which is not good.
Any other ideas? Maybe I can use partition function?
In Group Properties add a sort and set
Sort by expression "=Sum(NetWrittenPremium)"
Order = "Z to A"
Them in the filter as follows:
Expression = "=Sum(NetWrittenPremium)"
Operator = Top N
Value = 10
Actually I've just noticed the Total row.... This will not calculate the total correctly and you cannot use aggregations in filters on the tablix (which would have worked otherwise).
You best bet would be to push this back to the server and do it there.
I can't test this on your data directly but it should work, I tested on someting similar...
SELECT r.* FROM
(SELECT d.*
, dense_rank() OVER(ORDER BY TotalNWP Desc) AS rnk
FROM
(SELECT DISTINCT
ClassCode, YearNum, MonthNum
, SUM(t.NetWrittenPremium) OVER (PARTITION BY ClassCode, YearNum, MonthNum) AS NetWrittenPremium
, SUM(t.NetWrittenPremium) OVER (PARTITION BY ClassCode) AS TotalNWP
FROM cte_TopClasses t
) d
) r
WHERE rnk <=10
Now there should be no need to do any filtering in SSRS, just sorting by the rnk column.
You only remaining problem is how to determine which of the last results (which all have the same total) take precedent over the others. You could do something like adding ClassCode to the dense_rank function to the are chosen alphabetically but that;s for yo to decide I guess.
I think this will be easier to show an example first and then explain:
SELECT P.ID,
(CASE WHEN PC.NewCostPrice IS NULL
THEN P.Cost ELSE MAX(PC.Date) PC.NewCostPrice
END)
FROM price AS P
LEFT OUTER JOIN priceChange as PC
ON P.ID = PC.ID
So in the example, if the NewCostPrice IS NULL, meaning there wasn't a price change, then I want the normal cost (P.Cost). However, if there was a price change, I want the most recent (MAX(Date)) price change. I am not sure how to incorporate that into the CASE statement.
I feel like it can be done with a subquery and having clause but that didn't really work out when I tried. Any suggestions?
Thanks!
There are 2 approaches you might consider - I would test both to see which performs better for your situation.
Use ROW_NUMBER() in subquery to find most recent price change of all price changes, then join that to prices to get correct price.
Use correlated subquery (many ways of this, either in SELECT as in other answer or with OUTER APPLY) to get only most recent price change for each row of prices
If your price table is very large and you are getting a large number of prices at once, method #1 will likely be better so the correlated subquery doesn't run for every single row of the result set.
If your final query pulls back a relatively small number of records instead of huge result sets for your server, then the correlated subquery could be better for you.
1. The ROW_NUMBER() approach
SELECT
P.ID,
COALESCE(PC.NewCostPrice, P.Cost) AS LatestPrice
FROM Price AS P
LEFT OUTER JOIN (
SELECT
ID,
ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY [Date] DESC) AS RowId,
NewCostPrice
FROM PriceChange
) PC
ON P.ID = PC.ID
AND PC.RowId = 1 -- Only most recent
2a. Correlated subquery (SELECT)
SELECT
P.ID,
COALESCE((
SELECT TOP 1
NewCostPrice
FROM PriceChange PC
WHERE PC.ID = P.ID
ORDER BY PC.[Date] DESC
), P.Cost) AS LatestPrice
FROM Price AS P
2b. Correlated subquery with OUTER APPLY
SELECT
P.ID,
COALESCE(PC.NewCostPrice, P.Cost) AS LatestPrice
FROM Price AS P
OUTER APPLY (
SELECT TOP 1
NewCostPrice
FROM PriceChange PC
WHERE PC.ID = P.ID
ORDER BY PC.[Date] DESC
) PC
Whether you use 2a or 2b is more likely a preference in how you want to maintain the query going forward.
Easy way
SELECT distinct P.ID,
ISNULL((SELECT TOP 1 PC1.NewCostPrice FROM priceChange as PC1 WHERE PC1.ID = p.id ORDER BY PC1.Date DESC), p.cost)
FROM price AS P
Here I assume PC.ID is not a primary key, or it makes no sense to join with ID while there could be different price on the same item.
From your query I assume you just want to fetch the latest NewCostPrice sorted by Date, by joining priceChange
SELECT
P.ID,
CASE
WHEN PC.NewCostPrice IS NULL THEN P.Cost
ELSE PC.NewCostPrice
END AS NewPrice
FROM
price AS P
LEFT JOIN
(SELECT *, RANK() OVER (PARTITION BY ID ORDER BY [Date] DESC) as rk FROM priceChange) PC ON P.ID = PC.ID AND PC.rk = 1
SELECT P.ID
,(CASE
WHEN PC.NewCostPrice IS NULL
THEN P.Cost
ELSE (SELECT TOP 1 PC1.NewCostPrice
FROM priceChange PC1
WHERE PC1.ID = PC.ID
GROUP BY PC1.NewCostPrice, PC1.Date
ORDER BY PC1.Date DESC
)
END
)
FROM price AS P
LEFT OUTER JOIN priceChange as PC
ON P.ID = PC.ID