SQL query verification - simple - mysql

I am trying to write a query to find the 2nd oldest girl student in a class
DOB is date of birth as time stamp
name and gender are varchar
SELECT min(DOB)
FROM Student
WHERE DOB > (SELECT min(DOB) FROM Student where gender='girl')
Is this correct?

Or simpler:
SELECT DOB
FROM Student
WHERE gender='girl'
GROUP BY DOB
ORDER BY DOB ASC
LIMIT 1,1
Grouping by DOB means that two identical DOBs (twins?) that the next lowest is select. It can be omitted in a second identical date counts as the second oldest.

The below query will provide you the desired output
WITH T AS
(
SELECT *
DENSE_RANK() OVER (ORDER BY DOB ) AS Rnk
FROM Student
WHERE gender='girl'
)
SELECT min(DOB)
FROM T
WHERE Rnk = 2;
You can change the RNK value to get the next oldest value, say for third oldest you can use WHERE Rnk = 3

WITH myTableWithRows AS (
SELECT (ROW_NUMBER() OVER (ORDER BY Student.DOB)) as row,*
FROM Student )
SELECT * FROM myTableWithRows WHERE row = 2
I created this fake example and the query result is the following:
Or alternatively you can use this query:
Select top 1 * from (select top 2 from Student order by dob desc) order by dob desc

Related

Not getting this SQL query

Print all details of the 16th order placed by each customer if any.
How to print exact 16th Order?
SELECT COUNT(orderId)
FROM orders
GROUP BY CustomerID
ORDER BY CustomerID;
We can use a CTE and RANK to create a list of all orderId's, customerID's and their "order" as you named it.
Then we fetch those entries from the entire result whose order is 16.
WITH result AS
(
SELECT orderId, customerID,
RANK() OVER
(PARTITION BY customerID
ORDER BY orderId) AS rnk
FROM orders
)
SELECT orderId, customerID
FROM result
WHERE rnk=16
GROUP BY orderId, customerID
ORDER BY customerID;
For customerID's having less than 16 orders, nothing will be selected.
We can also use ROW_NUMBER instead of RANK in the above query, this makes no difference in your use case.
Select * from
(
SELECT *,
DENSE_RANK()
OVER(
PARTITION BY customerID
ORDER BY orderID
) my_rank
FROM orders
) as myTable
where my_rank = 16
order by CustomerID;
You can just use offset like:
SELECT *
FROM orders
GROUP BY CustomerID
ORDER BY CustomerID
LIMIT 1 OFFSET 15;
and set the OFFSET value to 15 so it skips the first 15 values and prints from the 16th value and limit it to only one row by setting the LIMIT value to 1

How do I group rows in mysql table till the value changes? (without using variables)

I have a table with name 'employees', I want to group the rows by their designation only till it changes.
please look at the sample table.
Id Name Designation Employee Id Last Modified
1 John General Manager 1 15-10-2001
2 John General Manager 1 05-11-2001
3 John Product Manager 1 06-11-2001
4 John Product Manager 1 15-11-2001
5 John General Manager 1 30-12-2001
Expected Output
Employee Id Name Designation Valid From Valid to
1 John General Manager 15-10-2001 05-11-2001
1 John Product Manager 06-11-2001 15-12-2001
1 John General Manger 16-12-2001 30-12-2001
I tried using group by clause, but it is giving me only 2 rows (General Manager, Product Manager).
But I want an output as shown in the table from which I can solve a bigger problem that involves starting date and ending date for each role.
Thanks in advance!.
Here you go:
WITH cte1 AS(
SELECT EmployeeId,
LastModified,
Designation,
LEAD(Designation) OVER (ORDER BY EmployeeId, LastModified) AS NextDesg,
LAG(LastModified) OVER (ORDER BY EmployeeId, LastModified) AS PrevLm
FROM mytable),
cte2 AS (
SELECT *,
ROW_NUMBER()
OVER (PARTITION BY EmployeeId ORDER BY LastModified) AS NewGroup
FROM cte1
WHERE (Designation != NextDesg OR NextDesg IS NULL)
ORDER BY LastModified)
SELECT t.EmployeeId, t.Name, t.Designation,
MIN(t.LastModified) AS "Valid From",
MAX(t.LastModified) AS "Valid To",
COALESCE(c1.NewGroup, c2.NewGroup) AS NwGr
FROM mytable t
LEFT JOIN cte2 c1
ON t.EmployeeId=c1.EmployeeId
AND t.LastModified=c1.LastModified
LEFT JOIN cte2 c2
ON t.EmployeeId=c2.EmployeeId
AND t.LastModified=c2.PrevLm
GROUP BY t.EmployeeId, t.Name, t.Designation, NwGr;
The main idea here is to assign each set as a new group, then use that to group by the results.
We start by doing common table expression twice. The first one is:
WITH cte1 AS(
SELECT EmployeeId,
LastModified,
Designation,
LEAD(Designation) OVER (ORDER BY EmployeeId, LastModified) AS NextDesg,
LAG(LastModified) OVER (ORDER BY EmployeeId, LastModified) AS PrevLm
FROM mytable)
I'm using LEAD() to get the next Designation value to be used as a delimiter and LAG() to get the previous LastModified value to be used in ON for the second LEFT JOIN in the final query.
The second cte:
cte2 AS (
SELECT *,
ROW_NUMBER()
OVER (PARTITION BY EmployeeId ORDER BY LastModified) AS NewGroup
FROM cte1
WHERE (Designation != NextDesg OR NextDesg IS NULL)
ORDER BY LastModified)
Here is where I used the value returned using LEAD() the first cte to identify the change in Designation then I add ROW_NUMBER() function to generate as NewGroup.
Then in the final query:
...
FROM mytable t
LEFT JOIN cte2 c1
ON t.EmployeeId=c1.EmployeeId
AND t.LastModified=c1.LastModified
LEFT JOIN cte2 c2
ON t.EmployeeId=c2.EmployeeId
AND t.LastModified=c2.PrevLm
...
I did LEFT JOIN from the main table with cte2 twice, the first one is matching by EmployeeId and LastModified of both and the second one is matching by EmployeeID and the previous LastModified value obtained from the LAG() function.
Demo fiddle

SQL query to get count of records on previous updated date (not latest update) and group by certain column

I have table T
SELECT country, count(*) ,max(upated_date) from T
GROUP BY country
This will give me count of records and latest update date by country.
How to get count of records on previous latest updated date by country?
note: updated date is different for each country
basically I want like this
If you want the second last update date, then use window functions:
SELECT country, count(*),
max(updated_date),
max(case when seqnum = 2 then updated_date end) as penultimate_updated_date
FROM (SELECT t.*,
RANK() OVER (PARTITION BY country ORDER BY updated_date DESC) as seqnum
FROM T
) t
GROUP BY country

MySQL - Group and return single row for each group based on most recent

So I'm having an issue with what I expect is a very simple problem, but for the life of me I can't figure it out!
I have a table like this:
id name status date
1 bob good 01/01/2020
2 john good 01/01/2020
3 bob bad 02/01/2020
4 john good 02/01/2020
5 ben good 02/01/2020
I want to retrieve the latest record for each name.
I have tried the following:
SELECT name
,STATUS
,MAX(DATE)
FROM TABLE
GROUP BY name
ORDER BY MAX(DATE)
I thought this worked, however it is returning a record for bob, john and ben, but it is showing bobs date as 02/01/2020 but his status as "good" from the other record!
At a loss as to how to do this in the simplest way possible, all help is much appreciated!
Don't think of this as aggregation. Think of this as filtering!
Select t.name, t.status, t.date
from table t
where t.date = (select max(t2.date)
from table t2
where t2.name = t.name
);
You are not aggregating anything. Your result set just wants columns from one row, the row with the maximum date for each name. That is more like filtering than grouping.
With not exists:
select t.* from tablename t
where not exists (
select 1 from tablename
where name = t.name and date > t.date
)
The result is:
every row of the table for which there is not another row with the same name and later date.
For MySql 8.0+ you can use ROW_NUMBER() window function:
select t.id, t.name, t.status, t.date
from (
select *, row_number() over (partition by name order by date desc) rn
from tablename
) t
where t.rn = 1
Maria DB 10.2 apparently. – Ed Jones
SELECT DISTINCT name,
FIRST_VALUE(status) OVER (PARTITION BY name
ORDER BY date DESC) status,
MAX(date) OVER (PARTITION BY name) date
FROM table;
The index by (name, data) will increase the performance.

How to display only value that occurs second highest number of times?

Write a query to display the customer name who visited the second highest number of times
select customer_id,count(*) from booking group by customer_id ;
using this query i got the count of number of visits for each customer as shown below
CUSTOMER_ID,COUNT(*)
C001,6
C002,1
C003,1
C004,1
C005,4
but i want to display only c005 since he has visited the second maximum time
SELECT customer_id, COUNT(*)
FROM booking
GROUP BY customer_id
HAVING COUNT(*) <> (SELECT MAX(t.custCount)
FROM (SELECT COUNT(*) AS custCount
FROM booking
GROUP BY customer_id) t )
ORDER BY COUNT(*) DESC
LIMIT 1
As a side note, this won't work if there are ties for second place. In this case, you use the above query as a condition in the WHERE clause, e.g.
SELECT customer_id
FROM booking
GROUP BY customer_id
HAVING COUNT(*) = (query given above)
You can use a outer query and filter the same like
select customer_id from (
select customer_id,
count(*) as datacount
from booking
group by customer_id ) xxx
order by datacount desc
limit 1;