Using SELECT with UNION - sql

CREATE TABLE members
(
name varchar(60),
ID char(6) PRIMARY KEY
);
CREATE TABLE ratings
(
memberID char(6) REFERENCES members(ID),
rating SMALLINT CHECK(rating >= 1 AND rating <= 8),
gameID integer REFERENCES games(ID),
PRIMARY KEY (memberID, gameID)
);
Hi guys, I'm trying to list all the members who have rated a game or not with showing the Max rate, Min rate,average rate, and how many times they have rated.
I tried :
(SELECT MAX(rating), MIN(rating), AVG(rating), COUNT(rating), name
FROM ratings, members
WHERE ratings.memberID = members.ID
GROUP BY name)
UNION
(SELECT MAX(rating), MIN(rating), AVG(rating),COUNT(distinct rating), name
FROM ratings, members
WHERE
members.ID NOT IN (SELECT memberID
FROM ratings, members
WHERE ratings.memberID = members.ID)
GROUP BY name);
This first part gives me a correct values; it gives the correct names followed by Max, min, and count, and the average. But the second part gives a correct names but wrong values of Max, Min, Average. It gives a Max of 9 and Min of 2 for all members who didn't rate any game! Which is not true. How can i fix the second part , so it gives a value of zero instead of 9 and 2 ?

I think you can get the result set you are looking for by using either a LEFT or RIGHT join
SELECT
M.name,
MAX(rating),
MIN(rating),
AVG(rating),
COUNT(rating)
FROM
[members] M
LEFT OUTER JOIN
[ratings] R
ON
M.ID = R.memberID
GROUP BY
M.name
This would then give a results like
name max | min | average | count
name1 8 | 2 | 5 | 3
name2 NULL | NULL | NULL | 0

I'd rewrite that query using INNER JOIN syntax, as it is clearer why the joins bring back unexpected results. As you'd written it, the second query would join all results against all members that had no results, per:
SELECT MAX(rating), MIN(rating), AVG(rating),COUNT(distinct rating), name
FROM ratings, members
WHERE
members.ID NOT IN (SELECT memberID
FROM ratings, members
WHERE ratings.memberID = members.ID)
I suspect you were after something more like:
SELECT MAX(rating),
MIN(rating),
AVG(rating),
COUNT(rating),
name
FROM ratings
INNER JOIN members
ON ratings.memberID = members.ID
GROUP BY name
UNION
SELECT MAX(rating),
MIN(rating),
AVG(rating),
COUNT(distinct rating),
name
FROM ratings
WHERE ratings.memberID NOT IN (SELECT memberID FROM members)
GROUP BY name

Related

Sql query for the lowest score per country

I have a database with 3 tables. In table country I have id and name columns. The sport table also has id and name columns. Finally, the table match has id, player1, and player2(that are ids of country that play one against other), winner_id (id of country that won the match) and sport_id of the sport which was played. The least wins means that I just need in which sport country had the least wins, no matther on played matches.
I want to show the sport per country with the least wins. It should look like this:
Country
Sport
Wins
France
Basketball
2
How can I construct this query? I'm using SQL Server.
Data in table look like this. Table countries:
country_id
name
1
France
2
England
Table sport:
sport_id
name
1
Footbal
2
Basketball
Table match:
match_id
player1
player2
winner_id
sport_id
1
3
1
3
1
2
6
4
4
2
I want to note that the used wording with least wins is not clear, in my solution with least wins means most matches played with least wins.
To get this ranking, we need to know how many matches a country has played in each sport and how many of those have been won.
SELECT
country.name AS country,
sport.name AS sport,
sport_wins.wins
FROM
country
OUTER APPLY (
SELECT TOP 1
t.match_count,
COALESCE(t.wins, 0) AS wins,
t.sport_id
FROM (
SELECT
COUNT(*) AS match_count,
m_c.sport_id,
t.wins
FROM match m_c
OUTER APPLY (
SELECT
COUNT(*) AS wins,
match.sport_id
FROM match
WHERE country.country_id = match.winner_id
AND match.sport_id = m_c.sport_id
GROUP BY match.sport_id
) t
WHERE country.country_id IN (m_c.player1, m_c.player2)
GROUP BY m_c.sport_id, t.wins
) t
ORDER BY t.wins ASC, t.match_count DESC
) sport_wins
JOIN sport ON sport.sport_id = sport_wins.sport_id
Please, check a demo.
If you do not take into account losses, but only the number of wins is of interest, you can use a query like this one
WITH cte AS (
SELECT
country.country_id,
sport.sport_id,
SUM(CASE WHEN match.winner_id = country.country_id THEN 1 ELSE 0 END) AS wins
FROM country
CROSS JOIN sport
JOIN match ON match.sport_id = sport.sport_id
AND country.country_id IN (match.player1, match.player2)
GROUP BY country.country_id, sport.sport_id
)
SELECT
country.name,
sport.name,
t.min_wins AS wins
FROM (
SELECT
country_id,
MIN(wins) AS min_wins
FROM cte
GROUP BY country_id
) t
JOIN cte ON cte.country_id = t.country_id AND cte.wins = min_wins
JOIN country ON cte.country_id = country.country_id
JOIN sport ON cte.sport_id = sport.sport_id
This query takes into account the fact that the country participates in matches in sport, so if a country does not compete in a sport, that sport will not be included in the statistics as it will have 0 wins and this will be the minimum value.
Please, check a demo
You need to first cross-join the sports with the countries, then get the total.
Then you can use a row-number approach to get the bottom country in each sport
SELECT
c.Country,
c.Sport,
c.Wins
FROM (
SELECT
c.name Country,
s.name Sport,
COUNT(m.winner_id) Wins,
ROW_NUMBER() OVER (PARTITION BY s.sport_id, s.name ORDER BY COUNT(m.winner_id)) rn
FROM country c
CROSS JOIN sport s
LEFT JOIN [match] m
ON s.sport_id = m.sport_id AND m.winner_id = c.country_id
GROUP BY
s.sport_id,
s.name,
c.country_id,
c.name
) c
WHERE c.rn = 1;

The difference between the minimum and maximum number of games

Question: Show the names of all players who have the following:
the difference between the minimum and maximum number of games
this players is greater than 5.
select p.name
from player p
join competition c
on c.playerID = p.playerID
where (
(select count(*) from competition
where count(games) > 1
group by playerID
) - (
select count(*) from competition
where count(games) <= 1
group by playerID
))> 5;
I'm kind of lost. I'm not so sure is this the right way, how I should proceed: should I use count and find the minimum and maximum number of games and compare with greater than 5 or should I use instead of count, min and max functions. Would be very grateful, if someone can explain me the logic of this.
Tables:
player competition
------- --------
playerID playerID
name games
birthday date
address
telefon
SELECT
P.Name,
MIN(C.Games) MinGame,
MAX(C.Games) MaxGame,
FROM Player P
INNER JOIN Competition C
ON C.PlayerId = P.PlayerId
GROUP BY P.Id, P.Name
HAVING MAX(C.Games) - MIN(C.Games) > 5
It should be a simple query:
With tab1 AS (Select player.name, min(games) mx_game, max(games) min_game,
max(games) - min(games) diff
from player JOIN competition ON player.player_id = competition.id
group by player.player_id, player.name)
Select tab1.name from tab1
WHERE diff >5;
I am adding player_id in the group by as player_name could be similar for 2 person.

How to get value from one table based on distinct values from another table?

I'm trying to write sql query which return me subject_id(result table) where primary_skill(student table) is unique.
Result table has column (student_id, subject_id, mark)
My query:
SELECT r.subject_id
FROM result r
JOIN student s ON r.student_id = s.student_id
WHERE s.primary_skill IN (SELECT DISTINCT primary_skill
FROM student)
GROUP BY 1;
I have this result:
subject_id
1
2
3
4
5
6
7
8
9
10
1001
But I should return only id 1001, because only this subject has unique student primary_skill, in other ids primary skill are repeated.
What am I doing wrong? How it improve?
Please try following:
select result.subject_id from student
join result on student.id =result.student_id where
student.primary_skill in
(select primary_skill from student group by primary_skill having COUNT(*)=1)
If I understand this right, you want results for students only having one skill. You can use GROUP BY and a HAVING clause checking for the count of skill being equal to one for this.
SELECT r.subject_id
FROM result r
INNER JOIN (SELECT s.student_id
FROM student s
GROUP BY s.student_id
HAVING count(DISTINCT s.primary_skill) = 1) x
ON x.student_id = r.student_id;

calculate average rating in sql server

this is my table:
I want to fetch records of Those Vendor which contain ServiceDescription "Plaster" or Skills "Plaster" or
is in Category "Plaster" and also want to calculate averagerating of those Vendor.
note:If there is no review Of any vendor then also that records should come.
this is my query:
select * from UserDetails u
,VendorInCategory v
,CategoryMaster c
,Review rv
where v.CategoryId=c.Id
and u.Id=r.UserId
and u.Id=rv.VendorId
and v.VendorId=u.Id
and ((u.ServiceDescription like '%Plaster%' )
or (u.Skills like '%Plaster%')
or (c.Name like '%Plaster%'))
here problem in above query is i am not getting that vendor whose review is not there.
but i also want that vendor which does not contain review but matches my criteria.
UserDetails:
id Servicedescription Skills
1 Plaster plaster
2 construction construvtion
3 plaster plaster
4 null null(not vendor)
5 null null(not vendor)
Review
id CustomerId Vendorid rating
1 4 1 3
2 5 1 3
Expected output:
VendorId ServiceDescription Skills averagerating
1 plaster plaster 3
3 plaster plaster 0
Note:final output should in descending order of average rating
Here, try this:
SAMPLE DATA
create table UserDetails(
Id int,
ServiceDescription varchar(20),
Skills varchar(20)
)
create table Review(
Id int,
CustomerId int,
VendorId int,
Rating int
)
insert into UserDetails values(1, 'Plaster', 'plaster'),(2, 'construction', 'construction'),(3, 'plaster', 'plaster');
insert into Review values(1, 4, 1, 3),(2, 5, 1, 3);
SOLUTION
select
u.Id as VendorId,
u.ServiceDescription,
u.Skills,
isnull(sum(r.rating)/count(r.rating), 0) as AverageRating
from UserDetails u
left join Review r
on r.VendorId = u.id
where
u.ServiceDescription like '%plaster%'
or u.Skills like '%plaster%'
group by
u.Id,
u.ServiceDescription,
u.Skills
order by AverageRating desc
Many users have used the AVERAGE function to calculate the average of a series of data. But what do you do when you have summary data instead of individual responses and need to calculate an average? (For example, counts of the number of people who selected each rating on a 5-point rating scale like the rating of a product.)
How to Calculate a Weighted Average
Let’s say you want to get the average overall rating for each product:
For rating 1, (9) nine people.
For rating 2, (13) Thirteen people.
For rating 3, (1) one people.
Using the AVERAGE function would result in an average of 7.7. Of course, this doesn’t make any sense. We should expect an average within the range of the scale (1 to 5).
In order to correctly calculate the average overall response to each question, we need to:
Multiply the number of individuals selecting each rating by the
corresponding rating value (1 – 5)
Add the results of those calculations together.
Divide that result by the total number of responses to the question.
SAMPLE DATA:
create table #tableRatings(
Id int,
Rating numeric(18,6)
)
insert into #tableRatings values(1, 4.3),(2,3.3),(3,4.8);
SOLUTION:
SELECT
SUM(
case
WHEN FLOOR(rating) = 1 THEN rating
WHEN FLOOR(rating) = 2 THEN rating *2
WHEN FLOOR(rating) = 3 THEN rating *3
WHEN FLOOR(rating) = 4 THEN rating *4
WHEN FLOOR(rating) = 5 THEN rating *5
end
) / SUM(rating)
FROM #tableRatings
RESULT:
3.733870
Use aggregate function AVG():
Try this:
SELECT u.id, u.ServiceDescription, u.Skills, u.fullname, u.email, AVG(ISNULL(rv.rating, 0)) averagerating
FROM UserDetails u
INNER JOIN VendorInCategory v ON v.VendorId=u.Id
INNER JOIN CategoryMaster c ON v.CategoryId=c.Id
LEFT JOIN Review rv ON u.Id=rv.VendorId
WHERE (u.ServiceDescription LIKE '%Plaster%' OR u.Skills LIKE '%Plaster%' OR
c.Name LIKE '%Plaster%')
GROUP BY u.id, u.ServiceDescription, u.Skills, u.fullname, u.email
ORDER BY averagerating DESC;
EDIT
Other solution to implement this:
SELECT u.id, u.ServiceDescription, u.Skills, u.fullname, u.email,
ISNULL(rv.averagerating, 0) averagerating
FROM UserDetails u
INNER JOIN VendorInCategory v ON v.VendorId=u.Id
INNER JOIN CategoryMaster c ON v.CategoryId=c.Id
LEFT JOIN (SELECT rv.VendorId, AVG(rv.rating) averagerating FROM Review rv GROUP BY rv.VendorId) rv ON u.Id=rv.VendorId
WHERE (u.ServiceDescription LIKE '%Plaster%' OR u.Skills LIKE '%Plaster%' OR
c.Name LIKE '%Plaster%')
ORDER BY ISNULL(rv.averagerating, 0) DESC;

Better way to demand, in SQL, that a column contains every specified value

Imagine you have two tables, with a one to many relationship.
For this example, I will suggest that there are two tables: Person, and Homes.
The person table holds a persons name, and gives them an ID. The homes table, holds the association of homes to a person. PID joins to "Person.ID"
And, in this tiny DB, a person can have no homes, or many homes.
I hope I drew that right.
How do I write a select, that returns everyone with every specified house type?
Let's say these are valid "Types" in the homes table:
Cottage, Main, Mansion, Spaceport.
I want to return everyone, in the Person table, who has a spaceport and a Cottage.
The best I could come up with was this:
SELECT DISTINCT( p.name ) AS name
FROM person p
INNER JOIN homes h ON h.pid = p.id
WHERE 'spaceport' in (
SELECT DISTINCT( type ) AS type
FROM homes
WHERE pid = p.id
)
AND 'cottage' in (
SELECT DISTINCT( type ) AS type
FROM homes
WHERE pid = p.id
)
When I wrote that, it works, but I'm pretty sure there has to be a better way.
The HAVING clause here will guarantee that the persons returned have both types, not just one or the other.
SELECT p.name
FROM person p
INNER JOIN homes h
ON p.id = h.pid
AND h.type IN ('spaceport', 'cottage')
GROUP BY p.name
HAVING COUNT(DISTINCT h.type) = 2
select * from homes;
home_id person_id type
--
1 1 cottage
2 1 mansion
3 2 cottage
4 3 mansion
5 4 cottage
6 4 cottage
To find the id numbers of every person who has both a cottage and a mansion, group by the id number, restrict the output to cottages and mansions, and count the distinct types.
select person_id
from homes
where type in ('cottage','mansion')
group by person_id
having count(distinct type) = 2;
person_id
--
1
You can use this query in a join to get all the columns from the person table.
select person.*
from person
inner join (select person_id
from homes
where type in ('cottage','mansion')
group by person_id
having count(distinct type) = 2) T
on person.person_id = T.person_id;
Thanks to Joe for pointing out an error in my count().
Not sure about the performance on this one, but here goes:
SELECT PID FROM (
SELECT PID, COUNT(PID) cnt FROM (
SELECT DISTINCT PID, Type FROM Homes
WHERE Type IN ('Type1', 'Type2', 'Type3')
) a
GROUP BY PID
) b
WHERE b.cnt = 3
You'd have to dynamically generate your IN clause as well as the WHERE b.CNT clause.