find maximum with specific data in sql server - sql

I still confused about sql server, for example i have student table and i try to find maximum mark for java student
STUDENT
| id | name | mark | subject |
| 1 | jenny | 67 | db |
| 2 | mark | 74 | java |
| 3 | nala | 90 | java |
i try to get output like this
| 3 | nala | 90 |
i write this in sql, but the output is empty.
SELECT id,name,mark
FROM student
WHERE subject='Java'
AND mark=
(SELECT max(mark) FROM student);
how i'm supposed to correct it?

There are many ways to get what you want in SQL. However, you should understand the problem with your approach:
SELECT id, name, mark
FROM student
WHERE subject = 'Java' AND
mark = (SELECT max(mark) FROM student);
The problem is that the maximum value of mark may not be for 'Java'. Hence, no rows can pass both where conditions.
You need to repeat the filter in the subquery, either explicitly:
SELECT id, name, mark
FROM student
WHERE subject = 'Java' AND
mark = (SELECT max(mark) FROM student WHERE subject = 'Java');
Or using a correlated subquery:
SELECT s.id, s.name, s.mark
FROM student s
WHERE s.subject = 'Java' AND
s.mark = (SELECT max(mark) FROM student s2 WHERE s2.subject = s.subject);
Notice that the last query uses table aliases. You should learn to use these in your queries; sometimes they are necessary and they generally make queries easier to write, read, and understand.

You don't need to use sub query. Use Top 1 with ties to get the student with max marks and also if the max mark is shared by more than one student
Where condition will filter the result to have only subject = 'Java' after that Top 1 with order by will fetch you the max mark in java
SELECT TOP 1 with ties id, name, mark
FROM student
WHERE subject = 'Java'
ORDER BY mark DESC

Use TOP 1 with ORDER BY clause to fetch highest data
Try this:
SELECT TOP 1 id, name, mark
FROM student
WHERE subject = 'Java'
ORDER BY mark DESC;
OR
SELECT id, name, mark
FROM (SELECT id, name, mark, ROW_NUMBER() OVER (ORDER BY mark DESC) AS RowNum
FROM student
WHERE subject = 'Java'
) AS A
WHERE RowNum = 1;

With what you are trying (with subquery), you could do:
SELECT id,name,mark
FROM student
WHERE subject='Java'
AND mark = (SELECT MAX(mark)
FROM student
WHERE subject='Java');
You are trying to fetch max of all the records irrespective of subject name.

TRY THIS SIMPLE QUERY TO GET RESULT
SELECT TOP 1 ID,NAME,mark FROM STUDENT
WHERE SUBJECT ='JAVA'
ORDER BY MARK DESC

Related

Sql Query: How to Base on the row name to display

I have the table data as listed on below:
name | score
andy | 1
leon | 2
aaron | 3
I want to list out as below, even no jacky's data, but list his name and score set to 0
aaron 3
andy 2
jacky 0
leon 2
You didn't specify your DBMS, but the following is 100% standard ANSI SQL:
select v.name, coalesce(t.score, 0) as score
from (
values ('andy'),('leon'),('aaron'),('jacky')
) as v(name)
left join your_table t on t.name = v.name;
The values clause builds up a "virtual table" that contains the names you are interested in. Then this is used in a left join so that all names from the virtual table are returned plus the existing scores from your (unnamed table). For non-existing scores, NULL is returned which is turned to 0 using coalesce()
If you only want to specify the missing names, you can use a UNION in the virtual table:
select v.name, coalesce(t.score, 0) as score
from (
select t1.name
from your_table t1
union
select *
from ( values ('jacky')) as x
) as v(name)
left join your_table t on t.name = v.name;
fixed the query, could list out the data, but still missing jacky, only could list out as shown on below, the DBMS. In SQL is SQL2008.
data
name score scoredate
andy 1 2021-08-10 01:23:16
leon 2 2021-08-10 03:25:16
aaron 3 2021-08-10 06:25:16
andy 4 2021-08-10 11:25:16
leon 5 2021-08-10 13:25:16
result set
name | score
aaron | 1
andy | 5
leon | 7
select v.name as Name,
coalesce(sum(t.score),0) as Score
from (
values ('aaron'), ('andy'), ('jacky'), ('leon')
) as v(name)
left join Score t on t.name=v.name
where scoredate>='2021-08-10 00:00:00'
and scoredate<='2021-08-10 23:59:59'
group by v.name
order by v.name asc
Your question lacks a bunch of information, such as where "Jacky"s name comes from. If you have a list of names that you know are not in the table, just use union all:
select name, score
from t
union all
select 'Jacky', 0;

Finding the most recent date in SQL for a range of rows

I have a table of course work marks, with the table headings:
Module code, coursework numbers, student, date submitted, mark
Sample data in order of table headings:
Maths, 1, Parry, 12-JUN-92, 20
Maths, 2, Parry, 13-JUN-92, 20
Maths, 2, Parry, 15-JUN-92, 25
Expected data after query
Maths, 1, Parry, 12-JUN-92, 20
Maths, 2, Parry, 15-JUN-92, 25
Sometimes a student retakes an exam and they have an additional row for a piece of coursework.
I need to try get only the latest coursework’s in a table. The following works when I isolate a particular student:
SELECT *
FROM TABLE
WHERE NAME = ‘NAME’
AND DATE IN (SELECT MAX(DATE)
FROM TABLE
WHERE NAME = ‘NAME’
GROUP BY MODULE_CODE, COURSEWORK_NUMBER, STUDENT)
This provides the correct solution for that person, giving me the most recent dates for each row (each coursework) in the table. However, this:
SELECT *
FROM TABLE
AND DATE IN (SELECT MAX(DATE)
FROM TABLE
GROUP BY MODULE_CODE, COURSEWORK_NUMBER, STUDENT)
Does not provide me with the same table but for every person who has attempted the coursework. Where am I going wrong? Sorry if the details are a bit sparse, but I’m worried about plagiarism.
Working with SQL plus
This is a good spot to use Oracle keep syntax:
select
module_code,
course_work_number,
student,
max(date_submitted) date_submitted,
max(mark) keep(dense_rank first order by date_submitted desc) mark
from mytable
group by module_code, course_work_number, student
Demo on DB Fiddle:
MODULE_CODE | COURSE_WORK_NUMBER | STUDENT | DATE_SUBMITTED | MARK
:---------- | -----------------: | :------ | :------------- | ---:
Maths | 1 | Parry | 12-JUN-92 | 20
Maths | 2 | Parry | 15-JUN-92 | 25
You are looking for a groupwise maximum. See this article from MySQL:
https://dev.mysql.com/doc/refman/8.0/en/example-maximum-column-group-row.html
I'm not sure about the correct syntax for Oracle, but it should be similar. At least the query structure should put you on the right path.
You could use the row_number function to solve this:
select x.*
(SELECT a.*,row_number() over(partition by name order by date desc) as row1
FROM TABLE a)x
where x.row1=1
The idea is to assign a row number based on the date and then select the cases where row number is 1. Hope this helps.

How to concatenate rows delimited with comma using standard SQL?

Let's suppose we have a table T1 and a table T2. There is a relation of 1:n between T1 and T2. I would like to select all T1 along with all their T2, every row corresponding to T1 records with T2 values concatenated, using only SQL-standard operations.
Example:
T1 = Person
T2 = Popularity (by year)
for each year a person has a certain popularity
I would like to write a selection using SQL-standard operations, resulting something like this:
Person.Name Popularity.Value
John Smith 1.2,5,4.2
John Doe NULL
Jane Smith 8
where there are 3 records in the popularity table for John Smith, none for John Doe and one for Jane Smith, their values being the values represented above. Is this possible? How?
I'm using Oracle but would like to do this using only standard SQL.
Here's one technique, using recursive Common Table Expressions. Unfortunately, I'm not confident on its performance.
I'm sure that there are ways to improve this code, but it shows that there doesn't seem to be an easy way to do something like this using just the SQL standard.
As far as I can see, there really should be some kind of STRINGJOIN aggregate function that would be used with GROUP BY. That would make things like this much easier...
This query assumes that there is some kind of PersonID that joins the two relations, but the Name would work too.
WITH cte (id, Name, Value, ValueCount) AS (
SELECT id,
Name,
CAST(Value AS VARCHAR(MAX)) AS Value,
1 AS ValueCount
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Name) AS id,
Name,
Value
FROM Person AS per
INNER JOIN Popularity AS pop
ON per.PersonID = pop.PersonID
) AS e
WHERE id = 1
UNION ALL
SELECT e.id,
e.Name,
cte.Value + ',' + CAST(e.Value AS VARCHAR(MAX)) AS Value,
cte.ValueCount + 1 AS ValueCount
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Name) AS id,
Name,
Value
FROM Person AS per
INNER JOIN Popularity AS pop
ON per.PersonID = pop.PersonID
) AS e
INNER JOIN cte
ON e.id = cte.id + 1
AND e.Name = cte.Name
)
SELECT p.Name, agg.Value
FROM Person p
LEFT JOIN (
SELECT Name, Value
FROM (
SELECT Name,
Value,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY ValueCount DESC)AS id
FROM cte
) AS p
WHERE id = 1
) AS agg
ON p.Name = agg.Name
This is an example result:
--------------------------------
| Name | Value |
--------------------------------
| John Smith | 1.2,5,4.2 |
--------------------------------
| John Doe | NULL |
--------------------------------
| Jane Smith | 8 |
--------------------------------
As per in Oracle you can use listagg to achive this -
select t1.Person_Name, listagg(t2.Popularity_Value)
within group(order by t2.Popularity_Value)
from t1, t2
where t1.Person_Name = t2.Person_Name (+)
group by t1.Person_Name
I hope this will solve your problem.
But the comment you have given after #DavidJashi question .. well this is not sql standard and I think he is correct. I am also with David that you can not achieve this in pure sql statement.
I know that I'm SUPER late to the party, but for anyone else that might find this, I don't believe that this is possible using pure SQL92. As I discovered in the last few months fighting with NetSuite to try to figure out what Oracle methods I can and cannot use with their ODBC driver, I discovered that they only "support and guarantee" SQL92 standard.
I discovered this, because I had a need to perform a LISTAGG(). Once I found out I was restricted to SQL92, I did some digging through the historical records, and LISTAGG() and recursive queries (common table expressions) are NOT supported in SQL92, at all.
LISTAGG() was added in Oracle SQL version 11g Release 2 (2009 – 11 years ago: reference https://oracle-base.com/articles/misc/string-aggregation-techniques#listagg) , CTEs were added to Oracle SQL in version 9.2 (2007 – 13 years ago: reference https://www.databasestar.com/sql-cte-with/).
VERY frustrating that it's completely impossible to accomplish this kind of effect in pure SQL92, so I had to solve the problem in my C# code after I pulled a ton of extra unnecessary data. Very frustrating.

How to perform "Select Count" with complicated "Where" statement to compute co-occurrences?

Let's have an example to declare my concern:
Suppose we have a Table (Tags) which has two columns like this
UserID -------------------------------- Tag
1 -------------------------------------- SQL
1 -------------------------------------- Select
1 -------------------------------------- DB
2 -------------------------------------- SQL
2 -------------------------------------- Programming
2 -------------------------------------- Code
2 -------------------------------------- Software
3 -------------------------------------- Code
4 -------------------------------------- SQL
4 -------------------------------------- Code
I need to count DISTINCT co-occurrences for each tag based on UserID
So, the output should be like this (with Order by Co-occurrences desc):
Tag -------------------------------- Co-occurrences
---------------------------------------------
SQL --------------------------------------- 5
Programming ------------------------------- 3
Code -------------------------------------- 3
Software ---------------------------------- 3
Select ------------------------------------ 2
DB ---------------------------------------- 2
This is just an example..
How can I make a Select statement that can do this?
I came up with one way but for only ONE specific tag:
SELECT count (distinct (Tag)) - 1 as Co_occurrences
FROM Tags
WHERE Tag is NOT NULL and UserID in
( SELECT UserID
FROM Tags
where tag = 'SQL')
Is it possible to change the above statement to make it general for all tags in the table?
SELECT t2.tag, count (distinct (t1.Tag)) - 1 as Co_occurrences
FROM Tags t1 inner join
Tags t2 on t1.UserId = t2.UserId
GROUP BY t2.tag
ORDER BY count (distinct (t1.Tag)) desc
A GROUP BY is what you are looking for:
SELECT
UserID,
Tag,
COUNT(DISTINCT Tag) - 1 AS Co_occurrences
FROM Tags
GROUP BY UserID, Tag
ORDER BY UserID, Tag
Edit: As mentioned in the comments, the above does not answer the question. I improved the answer of #OSA-E a bit, to explain what the -1 is doing after the count.
SELECT
[t1].[Tag],
COUNT(DISTINCT [t2].[Tag]) AS [Co_occurrences]
FROM [Tags] [t1]
INNER JOIN [Tags] [t2] ON [t1].[UserID] = [t2].[UserID]
WHERE [t1].[Tag] <> [t2].[Tag]
GROUP BY [t1].[Tag]
ORDER BY [Co_occurrences] DESC
Here is the Fiddle.

SQL select value if no corresponding value exists in another table

I have a database which tries to acheive point-in-time information by having a master table and a history table which records when fields in the other table will/did change. e.g.
Table: Employee
Id | Name | Department
-----------------------------
0 | Alice | 1
1 | Bob | 1
Table: History
ChangeDate | Field | RowId | NewValue
---------------------------------------------
05/05/2009 | Department | 0 | 2
That records that employee 0 (Alice) will move to department 2 on 05/05/2009.
I want to write a query to determine the employee's department on a particular date. So it needs to:
Find the first history record for that field and employee before given date
If none exists then default to the value currently in the master employee table.
How can I do this? My intuition is to select the first row of a result set which has all suitable history records reverse ordered by date and with the value in the master table last (so it's only the first result if there are no suitable history records), but I don't have the required SQL-fu to achieve this.
Note: I am conscious that this may not be the best way to implement this system - I am not able to change this in the short term - though if you can suggest a better way to implement this I'd be glad to hear it.
SELECT COALESCE (
(
SELECT newValue
FROM history
WHERE field = 'Department'
AND rowID = ID
AND changeDate =
(
SELECT MAX(changedate)
FROM history
WHERE field = 'Department'
AND rowID = ID
AND changeDate <= '01/01/2009'
)
), department)
FROM employee
WHERE id = #id
In both Oracle and MS SQL, you can also use this:
SELECT COALESCE(newValue, department)
FROM (
SELECT e.*, h.*,
ROW_NUMBER() OVER (PARTITION BY e.id ORDER BY changeDate) AS rn
FROM employee e
LEFT OUTER JOIN
history h
ON field = 'Department'
AND rowID = ID
AND changeDate <= '01/01/2009'
WHERE e.id = #id
)
WHERE rn = 1
Note, though, that ROWID is reserved word in Oracle, so you'll need to rename this column when porting.
This should work:
select iif(history.newvalue is null, employee.department, history.newvalue)
as Department
from employee left outer join history on history.RowId = employee.Id
and history.changedate < '2008-05-20' // (i.e. given date)
and history.changedate = (select max(changedate) from history h1
where h1.RowId = history.RowId and h1.changedate <= history.changedate)