Rank() function with WHERE clause display nothing MYSQL - mysql

So I have to find student max scores from each of their organization, I comes up with the solution by applying RANK() function:
SET SQL_SAFE_UPDATES = 0;
INSERT INTO interviewqs.details(scores,name,organization)
VALUES
(30,"Daniel","OWARD UNI"),
(40,"Kayla","OWARD UNI"),
(12,"Hope","ZELENSKY UNI"),
(50,"Osman","ZELENSKY UNI"),
(4,"Daniel","REWARD UNI"),
(77,"Joe","REWARD UNI");
DESCRIBE interviewqs.details;
# Find the student with highest scores from each organization
SELECT DISTINCT organization,name,scores,
RANK() OVER (PARTITION BY organization ORDER BY scores DESC)
AS "rank"
FROM details
WHERE "rank" = 1;
The problem is when I executed the code the output display empty table,
Without 'WHERE' function applied
organization name scores rank
OWARD UNI Kayla 40 1
OWARD UNI Daniel 30 3
REWARD UNI Daniel 77 1
REWARD UNI Daniel 30 2
REWARD UNI Daniel 4 4
ZELENSKY UNI Osman 50 1
ZELENSKY UNI Hope 12 3
With 'WHERE' function applied
organization name scores rank
What mistake did I do here?

SELECT DISTINCT
organization,
FIRST_VALUE(name) OVER (PARTITION BY organization ORDER BY scores DESC) name,
MAX(scores) OVER (PARTITION BY organization) scores
FROM details

You cannot use window functions in WHERE clause. The reason is that WHERE clause are processed first before window functions. I would highly suggest you to read up an article, Why Can't I use RANK() in Where Clause
To solve this, change your query to using CTE or subquery as shown below:
Subquery:
SELECT organization, name, scores
FROM (
SELECT
organization, name, scores,
RANK() OVER(PARTITION BY organization ORDER BY scores DESC) AS rnk
FROM details
) tmp
WHERE rnk = 1

This is another way using CTE.
WITH tmp AS
(
SELECT DISTINCT organization, `name`, scores,
RANK() OVER (PARTITION BY organization ORDER BY scores DESC) `rank`
FROM details
)
SELECT organization, `name`, scores FROM tmp WHERE `rank` = 1;
DB Fiddle
Note : rank is a reserve word so you must be careful in using it, always embed it in backticks.
Check MySQL Common Table Expression for more details about CTE.
In MySQL, every statement or query produces a temporary result or relation. A common table expression or CTE is used to name those temporary results set that exist within the execution scope of that particular statement

Related

Operating on a table received from query within the same query SQL

So I have this table resulting from a query. Is there a way to combine all of the purchases for the same username and order them in desc order to find the most loyal customers within the same query? maybe saving it to a variable and then doing something?
username
number_of_purchase
Bob
1
Marry
3
Mike
2
Bob
2
Marry
3
Mike
4
Ariana
3
Sally
1
This should do the work!
You can read about CTE here - https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15.
with users as (
**YOUR QUERY HERE **
)
Select username, sum(number_of_purchase) from users
group by username
Example from Microsoft site:
-- Define the CTE expression name and column list.
WITH Sales_CTE (SalesPersonID, SalesOrderID, SalesYear)
AS
-- Define the CTE query.
(
SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
)
-- Define the outer query referencing the CTE name.
SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear
FROM Sales_CTE
GROUP BY SalesYear, SalesPersonID
ORDER BY SalesPersonID, SalesYear;
Depending on your MySql version. If prior to v8 then you can use a derived table, basically, wrapping your existing query as follows
Select username, sum(number_of_purchase) purchases
from (
> Existing query <
)t
group by username
order by purchases desc
If you are using MySql 8 you could use window functions in your existing query to materialize the sum of purchases per user which you can then order by.
Without your existing query all I can do us suggest you incorporate the following, using the applicable column names
sum(purchase count column) over(partition by username) TotalPurchases

Query the second highest value of a name, if there's only one value return null

I have a table which has 2 columns, named "Name" and "Score", demonstrates the academic result of a class.
"Name": Andy, Amy, Chloe, John, etc.
"Score": From 0 to 100
Some of the students had submitted all the tests but some have not done yet due to pandemic, in other words, there's some student only got only 1 result.
Now I need to figure out what is the second-highest score that each of them has made.
If the students only submitted once, means that they don't have the second-highest score so we will return null for the score.
I tried
SELECT
name,
CASE
WHEN COUNT(score) IS NULL THEN NULL
ELSE MAX(score)
END AS second_highest
FROM
result
WHERE
score NOT IN (SELECT
MAX(score)
FROM
result
GROUP BY name)
GROUP BY name
ORDER BY name;
But seems like the subquery excluded the only score of the one-submission students, so they don't show up on the query result.
Is there anything I need to change in this method or is there any better approach?
Input and output can be found here sorry I am not familiar with demonstrating sample on Stackoverflow
I use MySQL version 8.0.22
Use ROW_NUMBER() window function to rank the scores for each student and then get the 2nd highest score with conditional aggregation:
SELECT name,
MAX(CASE WHEN rn = 2 THEN score END) second_highest
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY score DESC) rn
FROM result
) t
GROUP BY name
See the demo.

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.

Calculating Running totals across rows and grouping by ID

I want to compute running row totals across a table, however the totals must start over for new IDs
https://imgur.com/a/YgQmYQA
My code:
set #csum := 0;
select ID, name, marks, (#rt := #rt + marks) as Running_total from students order by ID;
The output returns the totals however doesn't break or start over for new IDs
Bro try this... It is tested on MSSQL..
select ID, name, marks,
marks + isnull(SUM(marks) OVER ( PARTITION BY ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) ,0) as Running_total
from students
You need to partition your running total by ID. A running total always needs an order of some column, by ordering on which you want to calculate the running total. Assuming running total under each ID is based on ORDER of marks,
Approach 1: It can be written in a simple query if your DBMS supports Analytical Functions
SELECT ID
,name
,marks
,Running_total = SUM(marks) OVER (PARTITION BY ID ORDER BY marks ASC)
FROM students
Approach 2: You can make use of OUTER APPLY if your database version / DBMS itself does not support Analytical Functions
SELECT S.ID
,S.name
,S.marks
,Running_total = OA.runningtotalmarks
FROM students S
OUTER APPLY (
SELECT runningtotalmarks = SUM(SI.marks)
FROM students SI
WHERE SI.ID = S.ID
AND SI.marks <= S.marks
) OA;
Note:- The above queries have been tested MS SQL Server.

group_concat in SQL Server 2008 [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Combine multiple results in a subquery into a single comma-separated value
Concat groups in SQL Server
I want to be able to get the duplication's removed
SELECT Count(Data) as Cnt, Id
FROM [db].[dbo].[View_myView]
Group By Data
HAVING Count(Data) > 1
In MySQL it was as simple as this:
SELECT Count(Data), group_concat(Id)
FROM View_myView
Group By Data
Having Cnt > 1
Does anyone know of a solution? Examples are a plus!
In SQL Server as of version 2005 and newer, you can use a CTE (Common Table Expression) with the ROW_NUMBER function to eliminate duplicates:
;WITH LastPerUser AS
(
SELECT
ID, UserID, ClassID, SchoolID, Created,
ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY Created DESC) AS 'RowNum'
FROM dbo.YourTable
)
SELECT
ID, UserID, ClassID, SchoolID, Created,
FROM LastPerUser
WHERE RowNum = 1
This CTE "partitions" your data by UserID, and for each partition, the ROW_NUMBER function hands out sequential numbers, starting at 1 and ordered by Created DESC - so the latest row gets RowNum = 1 (for each UserID) which is what I select from the CTE in the SELECT statement after it.
Using the same CTE, you can also easily delete duplicates:
;WITH LastPerUser AS
(
SELECT
ID, UserID, ClassID, SchoolID, Created,
ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY Created DESC) AS 'RowNum'
FROM dbo.YourTable
)
DELETE FROM dbo.YourTable t
FROM LastPerUser cte
WHERE t.ID = cte.ID AND cte.RowNum > 1
Same principle applies: you "group" (or partition) your data by some criteria, you consecutively number all the rows for each data partition, and those with values larger than 1 for the "partitioned row number" are weeded out by the DELETE.
Just use distinct to remove duplicates. It sounds like you were using group_concat to join duplicates without actually wanting to use its value. In that case, MySQL also has a distinct you could have been using:
SELECT DISTINCT Count(Data) as Cnt, Id
FROM [db].[dbo].[View_myView]
GROUP BY Id
HAVING Count(Data) > 1
Also, you can't group by something you use in an aggregate function; I think you mean to group by id. I corrected it in the example above.