SQL Server: how to get this result from this table (example inside) - sql

I would like to ask you guys how would you do a query to show the data of this table:
week name total
==== ====== =====
1 jon 15.2
1 jon 10
1 susan 10
1 howard 9
1 ben 10
3 ben 30
3 susan 10
3 mary 10
5 jon 10
6 howard 12
7 tony 25.1
8 tony 7
8 howard 10
9 susan 6.2
9 howard 9
9 ben 10
11 howard 10
11 howard 10
like this:
week name total
==== ====== =====
1 ben 10
1 howard 9
1 jon 25.2
1 mary 0
1 susan 10
1 tony 0
3 ben 30
3 howard 0
3 jon 0
3 mary 10
3 susan 10
3 tony 0
5 ben 0
5 howard 0
5 jon 10
5 mary 0
5 susan 0
5 tony 0
6 ben 0
6 howard 12
6 jon 0
6 mary 0
6 susan 0
6 tony 0
7 ben 0
7 howard 0
7 jon 0
7 mary 0
7 susan 0
7 tony 25.1
8 ben 0
8 howard 10
8 jon 0
8 mary 0
8 susan 0
8 tony 7
9 ben 10
9 howard 9
9 jon 0
9 mary 0
9 susan 6.2
9 tony 0
11 ben 0
11 howard 20
11 jon 0
11 mary 0
11 susan 0
11 tony 0
I tried something like:
select t1.week_id ,
t2.name ,
sum(t1.total)
from xpto as t1 ,
xpto as t2
where t1.week_id = t2.week_id
group by t1.week_id, t2.name
order by t1.week_id, t2.name
But I'm failing to understand the "sum" part and I can't figure out why...
Any help would be very appreciated. Thanks in advance, and sorry for my english.

you might try something like the following:
select week = w.week ,
name = n.name ,
sum_total = coalesce( sum( d.total ) , 0 )
from ( select distinct week from my_table ) w
cross join ( select distinct name from my_table ) n
left join my_table d on d.week = w.week
and d.name = n.name
group by w.week ,
n.name
order by 1,2
The cross join of first two derived tables constructs their cartesian product: all week and all names from the table, regardless of whether or not a particular week/name combination exists.
We then take that, join it against the actual data rows and summarize it, using coalesce() to collapse any null results from the aggregate function sum() to 0.

As I understood you you want to show all weeks and all names across all table regardless whether they were entered for the week or not. To do so you will need to first build a list of all names and week, cross join them and then join them to the list of totals, like this:
;with names as (select distinct name from xpto),
weeks as (select distinct week from xpto),
totals as (select week, name, sum(total) as total
from xpto group by week, name)
select w.week, n.name, coalesce(total, 0) as total
from names n cross join weeks w
left join totals t on t.name=n.name and w.week = t.week
order by 1,2
SQL Fiddle

I've edited my answer because I now understand what you want to do a bit better.
I prefer doing things in several steps rather than trying to do several transformations of data with a single join or subquery. So I would approach this like this:
;
with Weeks as (
select distinct Week_id
from xpto
)
, Names as (
select distinct Name
from xpto
)
, Scores as (
select week_id
, name
, score = sum(t1.score)
from xpto t1
group by
t1.week_id
, t1.name
)
, WeeksAndNames as (
select week_id
, name
from Weeks
cross join Names
)
-- The final query!
select wan.week_id
, wan.name
, total = COALESCE(s.total,0)
from WeeksAndNames wan
left join Scores s
on wan.week_id = s.week_id
and wan.name = s.name
order by
wan.week_id
, wan.name
Lengthy, I'll grant you, and you can probably condense it. But this shows each step you need to go through to transform your data into the list you want.

Related

How to select values from table based on field with the same id?

Let's say I have following sql tables:
Table "student"
id
first_name
last_name
1
Lillian
Nees
2
William
Lorenz
3
Mary
Moore
4
Giselle
Collins
5
James
Moultrie
6
John
Rodriguez
Table "exam_result":
exam_result_id
subject_id
student_id
mark
1
2
1
49
2
2
2
21
3
1
3
81
4
4
1
33
5
4
2
19
6
3
2
46
7
1
5
55
8
3
5
75
9
2
5
60
11
1
6
86
12
2
6
92
13
3
6
48
14
4
6
78
I need to select all students, who have all their exam marks <50 or haven't taken any exam at all.
So, in this case I need students with id 1, 2 and 4.
Closest thing I came up with is the following query, but it gives students with id 1, 2, 4 and 6. I don't need a student with id 6, since he has only 1 failed exam, not all of them.
SELECT DISTINCT s.id, s.first_name, s.last_name
FROM university.student s
LEFT JOIN
university.exam_result er ON
s.id = er.student_id
WHERE er.mark < 50 OR er.mark IS NULL;
I need it to work in both PostgreSQL and MariaDB, so I don't want anything specific to them.
The query that works is:
SELECT DISTINCT s.id, s.first_name, s.last_name
FROM university.student s
WHERE NOT EXISTS(SELECT 1 FROM university.exam_result er WHERE s.id = er.student_id AND er.mark > 49);
Thanks, #jarlh for your comment.
I would use a NOT EXISTS condition combined with a check for the marks
select s.*
from student s
where not exists (select *
from exam_result er
where er.student_id = s.id)
or 50 >= all (select er.mark
from exam_result er
where er.student_id = s.id)
One option is doing this with aggregation, by ensuring the count of marks less than 50 is equal to all the count of their marks. When the student has 0 marks, the condition will still be satisfied.
SELECT s.id, s.first_name, s.last_name
FROM student s
LEFT JOIN exam_result er ON s.id = er.student_id
GROUP BY s.id, s.first_name, s.last_name
HAVING COUNT(CASE WHEN er.mark < 50 THEN 1 END) = COUNT(er.mark)
Check the MariaDB demo and PostgreSQL demo.

create pivot table using sql

I have a database called player.db
These database has two tables.
The tables called person and the other is called match.
person table is
Player_ID
Player
Country
1
Lionel Messi
Argentina
2
Luis Suarez
Uruguay
3
Neymar
Brazil
match table is
Match _ID
Game
Player_ID
Date
Season
1
Uruguay-Paraguay
2
5/3/2019
1
2
Uruguay-Chile
2
19/3/2019
1
3
Argentina-Chile
1
22/3/2019
1
4
Brazil-Guyana
3
3/4/2019
1
5
Brazil-USA
3
1/6/2020
2
6
Brazil-Belize
3
3/7/2020
2
7
Brazil-Suriname
3
5/7/2020
2
8
Argentina-USA
1
8/8/2020
2
9
Argentina-Canada
1
3/3/2021
3
10
Argentina-Grenada
1
8/3/2021
3
11
Uruguay-Suriname
2
7/4/2021
3
12
Uruguay-Mexico
2
2/2/2022
4
13
Uruguay-Jamaica
2
4/2/2022
4
14
Brazil-Ecuador
3
5/2/2022
4
My pivot table should look like these:
Season
Player
1
Luis Suarez
2
Neymar
3
Lionel Messi
4
Luis Suarez
I want a sql code which create a pivot table which shows which player played most with topscore in which season year. For example Luis Suarez occured most in season 1.
I started coding in sql, but got not the desired solution
SELECT Player_ID, COUNT(*)FROM match GROUP BY Player_ID HAVING COUNT(*) max
The problem is I got an error and it doesn't create a pivot table which show which player played most in which season.
Join the tables, group by season and player to get the number of matches for each player and use FIRST_VALUE() window function to pick the top player of each season:
SELECT DISTINCT m.Season,
FIRST_VALUE(p.Player) OVER (PARTITION BY m.Season ORDER BY COUNT(*) DESC) Player
FROM match m INNER JOIN person p
ON p.Player_ID = m.Player_ID
GROUP BY m.Season, m.Player_ID;
See the demo.
Your clause HAVING count(*) max may cause the error.
The solution may depend on the relational data base you use. Here is a solution with postgresql :
SELECT DISTINCT ON (Season)
m.season, p.player, count(*) AS count
FROM match AS m
INNER JOIN player AS p ON p.player_ID = m.Player_ID
GROUP BY m.Season, p.Player_ID, p.player
ORDER BY m.Season ASC, count DESC
see dbfiddle

Delete Group of Records based on Group Statistic

I have the following two example tables
Teams
Team ID
Team Name
1
Bears
2
Tigers
3
Lions
4
Sharks
Players
Player ID
Name
Team ID
Playtime
1
John
1
5
2
Adam
1
4
3
Smith
1
5
4
Michelle
2
5
5
Stephanie
2
10
6
David
2
10
7
Courtney
2
2
8
Frank
2
7
9
Teresa
2
1
10
Michael
3
3
11
May
4
1
12
Daniel
4
1
13
Lisa
4
4
I need a select statement with the following criteria
Select all teams with less than 4 players
Figure out the total playtime for all players on those teams and sort the resulting table based on this in descending order
Based on the example given, I would want a table that looks like this:
Teams
Team Name
Num Players
Total Playtime
Bears
3
14
Sharks
3
6
Lions
1
3
Finally, I want to cut all even rows from the previous table, so the final table would look like:
Team Name
Num Players
Total Playtime
Bears
3
14
Lions
1
3
You may try the following:
Query #1
SELECT
t."Team Name",
COUNT(p."Player ID") as "Num Players",
SUM(p."Playtime") as "Total Playtime"
FROM
teams t
LEFT JOIN
players p ON t."Team ID"=p."Team ID"
GROUP BY
t."Team Name"
HAVING
COUNT(p."Player ID") < 4
ORDER BY
SUM(p."Playtime") DESC;
Team Name
Num Players
Total Playtime
Bears
3
14
Sharks
3
6
Lions
1
3
Query #2
SELECT
t1."Team Name",
t1."Num Players",
t1."Total Playtime"
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY SUM(p."Playtime") DESC) as rn,
t."Team Name",
COUNT(p."Player ID") as "Num Players",
SUM(p."Playtime") as "Total Playtime"
FROM
teams t
LEFT JOIN
players p ON t."Team ID"=p."Team ID"
GROUP BY
t."Team Name"
HAVING
COUNT(p."Player ID") < 4
) t1
WHERE MOD(rn,2)=1
ORDER BY rn;
Team Name
Num Players
Total Playtime
Bears
3
14
Lions
1
3
View on DB Fiddle
Let me know if this works for you.

Counting current and longest streaks of a given value

I have 2 tables Person (ID, NAME, CLAN_ID) and DailyScore (PERSON_ID, CLAN_ID, DAY_NUMBER, SCORE).
SCORE can take the values "A", "B", "C" or "-" ("-" means absent).
I need to make 2 separate queries to get, for a given CLAN_ID:
the current streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
the longest ever streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
An important constraint is that "-" SCORES are ignored, as they represent absences, not real Scores.
Example data:
Table Person:
_ID NAME CLAN_ID
1 John 11
2 Alice 11
3 Bob 12
4 Sara 12
Table DailyScore:
PERSON_ID CLAN_ID DAY_NUMBER SCORE
1 11 1 A
1 11 2 A
1 11 3 A
1 11 4 C
1 11 5 A
2 11 1 B
2 11 2 C
2 11 3 B
2 11 4 A
2 11 5 A
3 12 1 A
3 12 2 A
3 12 3 A
3 12 4 A
3 12 5 B
4 12 1 C
4 12 2 B
4 12 3 C
4 12 4 A
4 12 5 -
Desired result example 1 (CLAN_ID=11, SCORE=A):
Current streak:
Alice 2
John 1
Longest ever streak:
John 3
Alice 2
Desired result example 2 (CLAN_ID=12, SCORE=A):
Current streak:
Sara 1*
Bob 0
*since "-" are ignored, Sara has a current streak of 1 A score
Longest ever streak:
Bob 4
Sara 1
Edit:
In case it helps, here's this example in SQL Fiddle: http://sqlfiddle.com/#!7/2ed69/2
The first query can be:
select
id, name, max(s) as streak
from (
select
p.id,
p.name,
count(*) over(partition by p.id order by s.day_number desc) as c,
sum(case when s.score = 'A' then 1 else 0 end)
over(partition by p.id order by s.day_number desc) as s
from person p
join dailyscore s on s.person_id = p.id
) x
where c = s
group by id, name

SQL query for 3 tables by tour

tourid
tourname
1
PARIS
2
LANDON
3
MADRID
tour_detail_ID
tourid
custid
1
1
3
2
2
3
3
3
3
4
1
4
5
2
4
6
3
4
7
1
5
8
2
5
custid
custname
custPhoneNo
3
PETER
22332422
4
MARY
22332422
5
PAUL
22332422
enter image description here
How can I get all customers booked all tours?
If I understand what your data is and what your question is, this will involve finding out who has not scheduled all of the tours and then finding all customers who aren't in the previous list.
I'm not sure I like it, but something like this gets just Peter and Mary, since they are the only two who have scheduled all 3 tours:
SELECT c1.custid, c1.custName
FROM #customers c1
LEFT OUTER JOIN (
SELECT c2.custid
FROM #tours t
CROSS JOIN #customers c2
LEFT OUTER JOIN #tour_detail td ON td.tourid = t.tourid
AND td.custid = c2.custid
WHERE td.custid IS NULL
) noCustTour ON c1.custid = noCustTour.custid
WHERE noCustTour.custid IS NULL