Select maximum/minimum with another column - sql

Is there is a way to select the maximum of value + another column without the use of TOP and order by?
Assuming that we have a list of people and their ages, we want take the oldest/youngest. I want to select the name + the age. Even If it happens that we want to group them by name, that won't work.
SELECT nom,
max(age)
from Agents
group by nom
╔════════╦═════╗
║ Name ║ Age ║
╠════════╬═════╣
║ John ║ 200 ║
║ Bob ║ 150 ║
║ GSkill ║ 300 ║
║ Smith ║ 250 ║
║ John ║ 400 ║
║ Zid ║ 300 ║
║ Wick ║ 250 ║
║ Smith ║ 140 ║
╚════════╩═════╝

You could use ROW_NUMBER or DENSE_RANK. For example, if you have to show those employees having the MIN and MAX salary then you could use following SQL statement:
SELECT x.Name, x.Salary,
IIF(x.RowNumMIN = 1, 1, 0) AS IsMin,
IIF(x.RowNumMAX = 1, 1, 0) AS IsMax
FROM (
SELECT x.Name, x.Salary,
ROW_NUMBER() OVER(ORDER BY x.Salary ASC) AS RowNumMIN,
ROW_NUMBER() OVER(ORDER BY x.Salary DESC) AS RowNumMAX
FROM dbo.SourceTable AS x
) AS x
WHERE x.RowNumMIN = 1 OR x.RowNumMAX = 1
If there are two or more people having the same min or max salary and you have to show all of then you could use DENSE_RANK function instead of ROW_NUMBER.

Try this query --
;WITH CTE
AS (
SELECT [NAME]
,AGE
,DENSE_RANK() OVER (
ORDER BY AGE DESC
) AS Older
,DENSE_RANK() OVER (
ORDER BY AGE ASC
) AS Younger
FROM tblSample
)
SELECT [NAME] + ': ' + CAST(AGE AS VARCHAR(50))
FROM CTE
WHERE Older = 1 OR Younger = 1

Related

How to remove duplicate values from datatable SQL

Getting values duplicate:
╔══════╦══════╦═══════╦════════════╦═════════╦═════════╦══════╦═══════╗
║ ID ║ Name ║ Class ║ Date ║ Intime ║ Outtime ║ INAM ║ OUTPM ║
╠══════╬══════╬═══════╬════════════╬═════════╬═════════╬══════╬═══════╣
║ 1001 ║ Paul ║ 1st ║ 29-11-2022 ║ Holiday ║ Holiday ║ H ║ H ║
╠══════╬══════╬═══════╬════════════╬═════════╬═════════╬══════╬═══════╣
║ 1001 ║ Paul ║ 1st ║ 29-11-2022 ║ Holiday ║ Holiday ║ H ║ H ║
╠══════╬══════╬═══════╬════════════╬═════════╬═════════╬══════╬═══════╣
║ 1001 ║ Paul ║ 1st ║ 29-11-2022 ║ Holiday ║ Holiday ║ H ║ H ║
╚══════╩══════╩═══════╩════════════╩═════════╩═════════╩══════╩═══════╝
Code:
SELECT DISTINCT COALESCE(tt.ID,t1.ID) AS ID,
COALESCE(tt.Name,t1.Name) AS Name,
COALESCE(tt.Class,t1.Class) AS Class,tt.Date,
COALESCE(tt.Intime,t1.Intime) AS Intime,
COALESCE(tt.Outtime,t1.Outtime) AS Outtime,
COALESCE(tt.INAM,t1.INAM) AS INAM,
COALESCE(tt.OUTPM,t1.OUTPM) AS OUTPM
FROM stuattrecordAMPM AS t1
CROSS JOIN (SELECT * FROM stuattrecordAMPM UNION ALL
SELECT null,null,null,Date,Holiday_Name,Holiday_Name,Status,Status FROM HolidayList) AS tt
order by [ID]
DELETE FROM stuattrecordAMPM
WHERE Date IS NULL
In this code I'm getting duplicate values. How to avoid duplicates from datatable?
You can give a row number to each row grouped by all the columns, then delete the rows having row number greater than 1.
Query
with cte as(
select *, row_number() over(
partition by [id], [name], [class], [date], [intime], [outtime], [inam], [outpm]
order by [id]
) as [rn]
from [your_table_name]
)
delete * from cte
where [rn] > 1;

select query to get result by merging two columns

I have a table like below :
Id Price1 Price2
3 30 20
3 40 20
3 50 20
I want to write a query to get a below result :
Desired Ouput :
RowNo Id Price
1 3 20
2 3 30
3 3 40
4 3 50
Please help!!
Use Cross apply to unpivot the data and generate row number
SELECT Row_number()OVER(ORDER BY price) AS Rowno,*
FROM (SELECT DISTINCT Id,
Price
FROM (VALUES (3,30,20),
(3,40,20),
(3,50,20) ) tc ( Id, Price1, Price2)
CROSS apply (VALUES (Price1),
(Price2)) Cs (Price)) A
Result :
╔═══════╦════╦═══════╗
║ Rowno ║ Id ║ Price ║
╠═══════╬════╬═══════╣
║ 1 ║ 3 ║ 20 ║
║ 2 ║ 3 ║ 30 ║
║ 3 ║ 3 ║ 40 ║
║ 4 ║ 3 ║ 50 ║
╚═══════╩════╩═══════╝
You can use union to combine the rows (so duplicates are removed). And then row_number() to calculate rownum:
select row_number() over (order by price) as rownum, id, price
from ((select id, price1 as price from t) union
(select id, price2 from t
) t
order by price;

Pivot Column of varchar

I´m trying to PIVOT some data in a table, but I cannot do it because I do not find the way to do it using varchar columns. I have this table:
declare #table table(name VARCHAR(50) not null, occupation VARCHAR(MAX))
insert into #table values ('A','Doctor')
insert into #table values ('B','Doctor')
insert into #table values ('A','Professor')
insert into #table values ('A','Singer')
insert into #table values ('A','Actor')
SELECT
CASE WHEN occupation = 'Doctor' THEN NAME END AS Doctor,
CASE WHEN occupation = 'Professor' THEN NAME END AS Professor,
CASE WHEN occupation = 'Singer' THEN NAME END AS Singer,
CASE WHEN occupation = 'Actor' THEN NAME END AS Actor
FROM #table
Output:
Doctor Professor Singer Actor
A NULL NULL NULL
B NULL NULL NULL
NULL A NULL NULL
NULL NULL A NULL
NULL NULL NULL A
And for Pivot i get this output:
select * from
(
select name, occupation from #table ) src
pivot (
min(name)
for occupation in ([Doctor],[Professor],[Singer],[Actor])) as pvt
Doctor Professor Singer Actor
A A A A
And for min / max / function the pivot function gives me only partial output, for the count function I get number of records for doctor, singer etc.. But I need actual rows, not the row count.
What I need is this:
Doctor Professor Singer Actor
A A A A
B NULL NULL NULL
i.e suppose if we have 5 name for doctors we need to show 5 entries for doctor column.
I find this easier to express as conditional aggregation using a sequential number generated using `row_number():
select max(case when occupation = 'Doctor' then name end) as Doctor,
max(case when occupation = 'Professor' then name end) as Professor,
max(case when occupation = 'Singer' then name end) as Singer,
max(case when occupation = 'Actor' then name end) as Actor
from (select t.*,
row_number() over (partition by occupation order by name) as seqnum
from #table t
) t
group by seqnum
order by seqnum;
You can use PIVOT as you proposed, just add column with ROW_NUMBER:
SELECT [Doctor],[Professor],[Singer],[Actor]
FROM (SELECT name, occupation,
rn = ROW_NUMBER() OVER (PARTITION BY occupation ORDER BY occupation)
FROM #table ) AS src
PIVOT (
MIN(name)
FOR occupation IN ([Doctor],[Professor],[Singer],[Actor])
) AS pvt
LiveDemo
Output:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ ║ ║
╚════════╩═══════════╩════════╩═══════╝
EDIT:
You did not write how to handle more rows so consider this case.
Above solution will return:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ C ║ ║
╚════════╩═══════════╩════════╩═══════╝
vs:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ ║ ║
║ ║ ║ C ║ ║
╚════════╩═══════════╩════════╩═══════╝
If you want second case use:
SELECT [Doctor],[Professor],[Singer],[Actor]
FROM (SELECT name, occupation,
rn = DENSE_RANK() OVER (ORDER BY Name)
FROM #table ) AS src
PIVOT (
MIN(name)
FOR occupation IN ([Doctor],[Professor],[Singer],[Actor])
) AS pvt
LiveDemo2

How to get New, Existing and Inactive users from table

For example, Below is input table which has Month & User
Output Required:
NewUsers are new in that month. ExistingUsers are users in that month which have some data in previous month as well. Inactive users are users active in previous month but not in current month
Is it possible?
You can use windowed function to achieve that:
New User is very easy COUNT rows that have rn = 1
Existing Users: easy too, COUNT rows that have rn > 1
Inactive Users: bit complicated (get sum of new + existing and substract (new + existing) from row before.
Code:
WITH cte AS
(
SELECT *
,rn = ROW_NUMBER() OVER (PARTITION BY UserKey ORDER BY MonthId)
FROM #tab t1
), cte2 AS(
SELECT
MonthId,
[New_User] = COUNT(CASE WHEN rn = 1 THEN 1 END),
[Existing_User] = COUNT(CASE WHEN rn > 1 THEN 1 END),
[s] = COUNT(rn)
FROM cte
GROUP BY MonthId
)
SELECT
MonthId,
[New_User],
[Existing_User],
[Inactive_User] = CASE WHEN [s] - LAG(s, 1) OVER(ORDER BY MonthId) < 0
THEN ABS([s] - LAG(s, 1) OVER(ORDER BY MonthId))
ELSE 0
END
FROM cte2
ORDER BY MonthId;
LiveDemo
Output:
╔═════════╦═══════════╦════════════════╦════════════════╗
║ MonthID ║ New_Users ║ Existing_Users ║ Inactive_Users ║
╠═════════╬═══════════╬════════════════╬════════════════╣
║ 201411 ║ 1 ║ 0 ║ 0 ║
║ 201412 ║ 1 ║ 1 ║ 0 ║
║ 201501 ║ 1 ║ 2 ║ 0 ║
║ 201502 ║ 0 ║ 2 ║ 1 ║
╚═════════╩═══════════╩════════════════╩════════════════╝
Warning:
I've assumed that data per each MonthId is UNIQUE if not add one more CTE step to remove duplicates first.

How to sort a column based on length of data in it in SQL server

As we all know general sorting is using order by. The sort I want to perform is different. I want the smallest length value in middle of table n the largest ones in top and bottom of it. One half should be descending and another half should be ascending. Can you guys help. It was an interview question.
This is one way:
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY LEN(YourColumn))
FROM dbo.YourTable
)
SELECT *
FROM CTE
ORDER BY RN%2, (CASE WHEN RN%2 = 0 THEN 1 ELSE -1 END)*RN DESC
Test Data
DECLARE #Table TABLE
(ID INT, Value VARCHAR(10))
INSERT INTO #Table VALUES
(1 , 'A'),
(2 , 'AB'),
(3 , 'ABC'),
(4 , 'ABCD'),
(5 , 'ABCDE'),
(6 , 'ABCDEF'),
(7 , 'ABCDEFG'),
(8 , 'ABCDEFGI'),
(9 , 'ABCDEFGIJ'),
(10 ,'ABCDEFGIJK')
Query
;WITH CTE AS (
SELECT *
,NTILE(2) OVER (ORDER BY LEN(Value) DESC) rn
FROM #Table )
SELECT *
FROM CTE
ORDER BY CASE WHEN rn = 1 THEN LEN(Value) END DESC
,CASE WHEN rn = 2 THEN LEN(Value) END ASC
Result
╔════╦════════════╦════╗
║ ID ║ Value ║ rn ║
╠════╬════════════╬════╣
║ 10 ║ ABCDEFGIJK ║ 1 ║
║ 9 ║ ABCDEFGIJ ║ 1 ║
║ 8 ║ ABCDEFGI ║ 1 ║
║ 7 ║ ABCDEFG ║ 1 ║
║ 6 ║ ABCDEF ║ 1 ║
║ 1 ║ A ║ 2 ║
║ 2 ║ AB ║ 2 ║
║ 3 ║ ABC ║ 2 ║
║ 4 ║ ABCD ║ 2 ║
║ 5 ║ ABCDE ║ 2 ║
╚════╩════════════╩════╝
Here's a short approach that would ge t you started:
WITH cte AS
(
SELECT TOP 1000 number
FROM master..spt_values
WHERE type = 'P' and number >0
)
SELECT number, row_number() OVER(ORDER BY CASE WHEN number %2 = 1 THEN number ELSE -(number) END) pos
FROM cte