Get MAX and MIN in a row SQL - sql

;WITH CTE AS
(
SELECT * FROM
(
SELECT CandidateID, t_Candidate.Name, ISNULL(CAST(AVG(Rate) AS DECIMAL(12,2)),0) AS Rate, t_Ambassadors.Name AS CN
FROM t_Vote INNER JOIN t_Candidate
ON t_Vote.CandidateID = t_Candidate.ID
INNER JOIN t_Ambassadors
ON t_Vote.AmbassadorID = t_Ambassadors.ID
GROUP BY Rate, CandidateID, t_Candidate.Name, t_Ambassadors.Name
)MySrc
PIVOT
(
AVG(Rate)
FOR CN IN ([Jean],[Anna],[Felicia])
)AS nSrc
)SELECT CandidateID, Name, CAST([Jean] AS DECIMAL(12,2)) AS AHH ,CAST([Anna] AS DECIMAL(12,2)) AS MK,CAST([Felicia] AS DECIMAL(12,2)) AS DIL, CAST(([Jean] + [Anna] + [Felicia])/3 AS DECIMAL(12,2)) AS Total
FROM CTE
GROUP BY Cte.CandidateID, cte.Name, cte.[Jean], cte.[Anna], cte.[Felicia]
I have solved my previous problem with the above query. I created a new question because I have new problem. How do I get the MAX and MIN rate in a row?
The following is the result I get from the above query:
| CandidateID | Name | AHH | MK | DIL | Total |
|-------------|------|-------|------|------|-------|
| CID1 | Jay | 7.00 | 3.00 | 3.00 | 4.33 |
| CID2 | Mia | 2.00 | 9.00 | 7.00 | 6.00 |
What I want to achieve is this:
| CandidateID | Name | AHH | MK | DIL | Total |
|-------------|------|-------|------|------|-------|
| CID1 | Jay | 7.00 | 3.00 | 3.00 | 3.00 |
| CID2 | Mia | 2.00 | 9.00 | 7.00 | 7.00 |
So what happened on the 2nd result is that, it removed the Highest and Lowest score/rate from the row and Get the average of remaining rate/score. AHH, MK and DIL are not the only Voters, there are 14 of them, I just took the 3 first to make it short and clearer.

I believe you're looking by something like the following (though I'm using case aggregation rather than a pivot).
Essentially, it does the same thing your query does except that it uses a row number to figure out the highest and lowest and exclude them from the final "total" (in the case of a tie, it'll just select one of them, but you can use RANK() instead of row_number() if you don't want to include tied highest/lowest in the average):
WITH CTE AS
(
SELECT CandidateID,
Name,
CN,
Rate,
Lowest = ROW_NUMBER() OVER (PARTITION BY CandidateID, Name ORDER BY Rate),
Highest = ROW_NUMBER() OVER (PARTITION BY CandidateID, Name ORDER BY Rate DESC)
FROM
(
SELECT CandidateID,
t_Candidate.Name,
CN = t_Ambassadors.Name,
Rate = ISNULL(CAST(AVG(Rate) AS DECIMAL(12,2)),0)
FROM t_Vote
JOIN t_Candidate
ON t_Vote.CandidateID = t_Candidate.ID
JOIN t_Ambassadors
ON t_Vote.AmbassadorID = t_Ambassadors.ID
GROUP BY CandidateID, t_Candidate.Name, t_Ambassadors.Name
) AS T
)
SELECT CandidateID,
Name,
AHH = MAX(CASE WHEN CN = 'Jean' THEN Rate END),
MK = MAX(CASE WHEN CN = 'Anna' THEN Rate END),
DIL = MAX(CASE WHEN CN = 'Felicia' THEN Rate END), -- and so on and so forth for each CN
Total = AVG(CASE WHEN Lowest != 1 AND Highest != 1 THEN Rate END)
FROM CTE
GROUP BY CandidateID, Name;
EDIT: It is possible to do this using PIVOT, but unless I'm mistaken, it becomes a matter of working out the average of the ones that aren't highest and lowest before pivoting, which becomes a bit more convoluted. It's all around easier to use case aggregation, IMO.

Related

MAX Grouping by 2 columns

I'm stumped as in how to do this.
I have 3 columns the first is a parent company, second is the child and the third is it's revenue. I want to find out which child per parent has the most revenue and what that revenue is.
So like the below
Vodafone. Argentina. 5b
Vodafone. Spain. 4b
Vodafone. England. 10b
So the answer would be
Vodafone. England 10b
Apologies for the formatting, on my phone.
You can use row_number(). Here is the demo.
select
company,
child,
revenue
from
(
select
*,
row_number() over (partition by company order by cast(revenue as int) desc) as rn
from yourTable
) subq
where rn = 1
output:
| company | child | revenue |
| -------- | ------- | ------- |
| Vodafone | England | 10 |
You can use dense_rank() if more than one company have same revenue.
You can try the below one -
select * from tablename
where cast(revenue as int) = (select max(cast(revenue as int)) from tablename)

in SQL, how to remove distinct column values (not rows, as usually done)

I have a production case, for a supply chain. We have devices that are moved around in warehouses, and I need to find the previous warehouse locations.
I have a table like this:
+--------+------------+--------+--------+--------+
| device | current_WH | prev_1 | prev_2 | prev_3 |
+--------+------------+--------+--------+--------+
| 1 | AB | KK | KK | KK |
| 2 | DE | DE | DE | NQ |
| 3 | FF | MM | ST | ST |
+--------+------------+--------+--------+--------+
I need to find the distinct values of current_WH and the "prev" columns. So I'm not flattening rows, but narrowing columns. I need to get this:
+--------+------------+--------+--------+--------+
| device | current_WH | prev_1 | prev_2 | prev_3 |
+--------+------------+--------+--------+--------+
| 1 | AB | KK | blank | blank |
| 2 | DE | NQ | blank | blank |
| 3 | FF | MM | ST | blank |
+--------+------------+--------+--------+--------+
I'll figure out nulls or blanks later. But for now I need one row for each device that shows the current WH and previous locations. There could be any number - not always the same.
If I do "distinct" that flattens rows. Doing a distinct and group by doesn't achieve the requirement.
Any help is appreciated. Thanks!
You need to do unpivot to let your column value rows, because that will easier to compare before current_WH value data, then do a pivot to recover the data schema.
Do unpivot to let your column value rows, because that will easier to compare before current_WH value data, and add a new grp column it can help to recover your expected result.
use LAG function to get the previous value it will be compared with current_WH value.
use SUM with CASE WHEN and window function to cumulative number if the previous equal to current_WH value.
if the SUM cumulative number greater than 0 means the name was repeated.
look like this.
with cteUnion as(
SELECT device,current_WH,0 grp
FROM T
UNION ALL
SELECT device,prev_1,1 grp
FROM T
UNION ALL
SELECT device,prev_2,2 grp
FROM T
UNION ALL
SELECT device,prev_3,3 grp
FROM T
),cte1 as(
SELECT *,
LAG(current_WH) over(partition by current_WH order by grp) perviosVal
from cteUnion
),cteResult as (
SELECT *,
(CASE WHEN sum(CASE WHEN perviosVal = current_WH then 1 else 0 end) over(partition by device order by grp) > 0 THEN 'Block' else current_WH end) val
FROM cte1
)
select device,
MAX(CASE WHEN grp = 0 then val end) current_WH ,
MAX(CASE WHEN grp = 1 then val end) prev_1,
MAX(CASE WHEN grp = 2 then val end) prev_2,
MAX(CASE WHEN grp = 3 then val end) prev_3
from cteResult
GROUP BY device
sqlfiddle
NOTE
grp column number value depends on your order.

Select only 1 payment from a table with customers with multiple payments

I have a table called "payments" where I store all the payments of my costumers and I need to do a select to calculate the non-payment rate in a given month.
The costumers can have multiples payments in that month, but I should count him only once: 1 if any of the payments is done and 0 if any of the payment was made.
Example:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-01 | 0 |
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
The result I expect is from the rate of november:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
So the rate will be 50%.
But if the select is:
SELECT * FROM payment WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
It will return me 3 rows and the rate will be 66%, witch is wrong. Ideas?
PS: This is a simpler example of the real table. The real query have a lot of columns, subselects, etc.
It sounds like you need to partition your results per customer.
SELECT TOP 1 WITH TIES
ID,
DATEDUE,
AMOUNT
ORDER BY ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC)
WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
PS: The BETWEEN operator is frowned upon by some people. For clarity it might be better to avoid it:
What do BETWEEN and the devil have in common?
Try this
SELECT
id
, SUM(AMOUNT) AS AMOUNT
FROM
Payment
GROUP BY
id;
This might help if you want other columns.
WITH cte (
SELECT
id
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC ) AS RowNum
-- other row
)
SELECT *
FROM
cte
WHERE
RowNum = 1;
To calculate the rate, you can use explicit division:
select 1 - count(distinct case when amount > 0 then id end) / count(*)
from payment
where . . .;
Or, in a way that is perhaps easier to follow:
select avg(flag * 1.0)
from (select id, (case when max(amount) > 0 then 0 else 1 end) as flag
from payment
where . . .
group by id
) i

How to return all rows with MAX value meeting a condition of another field in SQL?

I have the following costs table:
+--------+------+-----------+
| Year | ID | Amount |
+--------+------+-----------+
| 1960 | 1 | 100 |
| 1960 | 2 | 200 |
| 1960 | 3 | 200 |
| 1960 | 4 | 150 |
| 1961 | 1 | 300 |
| 1961 | 2 | 200 |
| 1961 | 3 | 100 |
| 1961 | 4 | 300 |
+---------+------+----------+
I want all ID’s having the MAX Amount by Year. For example, for 1960, I want rows with ID's 2 and 3. For 1961, I want rows with ID's 1 and 4.
SELECT Year, ID, Amount FROM costs WHERE Amount = (SELECT MAX(Amount) FROM costs);
The above gets me all MAX values across all Years. But I want a condition that only gets me the max Amount values per year. How do I add an condition to only select records with Year = 1960?
Please try this with below query.This is tested. Its working fine.
By clicking on the below link you can see your expected result in live which you want.
SQL Fiddle Live Demo
SELECT
t1.*
FROM
costs t1
WHERE
t1.amount = (
SELECT
MAX(t2.amount)
FROM
costs t2
WHERE
t2. `year` = t1. `year`
);
Try this....It should work
SELECT
*
FROM
costs
WHERE
(YEAR, amount) IN (
SELECT
YEAR,
max(amount)
FROM
costs
GROUP BY
YEAR
);
One option which should run on all major databases is to use a subquery which finds the max amounts for each year to select the records you want:
SELECT c1.*
FROM costs c1
INNER JOIN
(
SELECT Year, MAX(Amount) AS MaxAmount
FROM costs
GROUP BY Year
) c2
ON c1.Year = c2.Year AND
c1.Amount = c2.MaxAmount
Another way to do this would be to use a correlated subquery:
SELECT c1.*
FROM costs c1
WHERE c1.Amount = (SELECT MAX(c2.Amount) FROM costs c2 WHERE c2.Year = c1.Year)
I expect that joining (the first option) would be the fastest method for larger tables, especially if you have proper indices would could be used.
SELECT Year , ID , Amount
FROM #Table T1
JOIN
(
SELECT MAX(Amount) Amount,Year
FROM #Table
GROUP BY Year
) A ON A.Year = T1.Year AND A.Amount = T1.Amount

Creating Columns for Summed Answers

So I'm trying to move my row info into the column - I haven't used a Pivot before and tried using it now but clearly I'm doing something wrong -.-;
Here is my original Query
(Select
CASE WHEN ee.ExpenseTypeID=1 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=2 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=3 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=4 THEN Sum(Amount) END as Amount,
et.ExpenseDescription,
ee.UserID
From ExpensesEntries ee, ExpenseTypes et
Where ee.ExpenseTypeID=et.ExpenseTypeID
Group By ee.ExpenseTypeID, et.ExpenseDescription, ee.UserID
Order By UserID) b
Which produces something like this
Amount | ExpenseDescription | UserID
----------------------------------------
156.00 | Upload | 123
----------------------------------------
23.00 | Parking | 123
----------------------------------------
15.37 | Other | 123
----------------------------------------
112.00 | Other | 456
----------------------------------------
28.50 | Parking | 456
----------------------------------------
What I would like to do
UserID | Upload | Parking | Other
----------------------------------
123 | 156.00 | 23.00 | 15.37
----------------------------------
456 | NULL | 28.50 | 112.00
I tried doing this - which is the equivalent of slapping on a Pivot but with the Cases in my original select - I'm not sure if I need to get rid of them entirely and add it into Pivot ?
Select
CASE WHEN ee.ExpenseTypeID=1 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=2 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=3 THEN Sum(Amount)
WHEN ee.ExpenseTypeID=4 THEN Sum(Amount) END as Amount,
et.ExpenseDescription,
ee.UserID
From ExpensesEntries ee, ExpenseTypes et
Where ee.ExpenseTypeID=et.ExpenseTypeID
Group By ee.ExpenseTypeID, et.ExpenseDescription, ee.UserID
Order By UserID
PIVOT
(Amount
FOR et.ExpenseDescription IN ('Upload','Other','Parking')
) as pvt
You didn't provide details on what each of the ExpenseTypeId values relate to but you can do this using an aggregate function with a CASE expression. The CASE expression will check the value of the ExpenseTypeId and get a sum of the amount - you will then name the column with the ExpenseDescription:
select ee.UserId,
sum(case when ee.ExpenseTypeID=1 then Amount else 0 end) Upload,
sum(case when ee.ExpenseTypeID=2 then Amount else 0 end) Parking,
sum(case when ee.ExpenseTypeID=3 then Amount else 0 end) Other
from ExpensesEntries ee
inner join ExpenseTypes et
on ee.ExpenseTypeID=et.ExpenseTypeID
group by ee.UserId;
If you want to use PIVOT, then you would alter your query to something similar to the following. This uses a join between your 2 tables in a subquery and you return the UserId, ExpenseDescription and Amount. The PIVOT will sum the amount for each ExpenseDescription - this process converts the descriptions to columns:
select UserId, Upload, Other, Parking
from
(
select ee.UserId,
et.ExpenseDescription
Amount
from ExpensesEntries ee
inner join ExpenseTypes et
on ee.ExpenseTypeID=et.ExpenseTypeID
) d
pivot
(
sum(Amount)
for ExpenseDescription in (Upload, Other, Parking)
) p;