SQL join problems - users betting on matches - sql

I have the following table:
scores:
user_id | match_id | points
1 | 110 | 4
1 | 111 | 3
1 | 112 | 3
2 | 111 | 2
Users bet on matches and depending on the result of the match they are awarded with points. Depending on how accurate the bet was you are either awarded with 0, 2, 3 or 4 points for a match.
Now I want to rank the users so that i can see who is in 1st, 2nd place etc...
The ranking order is firstly by total_points. If these are equal its ordered by the amount of times a user has scored 4 points then by the amount of times a user scored 3 points and so on.
For that i would need the following table:
user_id | total_points | #_of_fours | #_of_threes | #_of_twos
1 | 10 | 1 | 2 | 0
2 | 2 | 0 | 0 | 1
But i cant figure out the join statements which would help me get it.
This is as far as i get without help:
SELECT user_id, COUNT( points ) AS #_of_fours FROM scores WHERE points = 4 GROUP BY user_id
Which results in
user_id | #_of_fours
1 | 1
2 | 0
Now i would have to do that for #_of_threes and twos aswell as total points and join it all together, but i cant figure out how.
BTW im using MySQL.
Any help would be really apreciated. Thanks in advance

SELECT user_id
, sum(points) as total_points
, sum(case when points = 4 then 1 end) AS #_of_fours
, sum(case when points = 3 then 1 end) AS #_of_threes
, sum(case when points = 2 then 1 end) AS #_of_twos
FROM scores
GROUP BY
user_id

Using mysql syntax, you can use SUM to count the matching rows easily;
SELECT
user_id,
SUM(points) AS total_points,
SUM(points=4) AS no_of_fours,
SUM(points=3) AS no_of_threes,
SUM(points=2) AS no_of_twos
FROM Table1
GROUP BY user_id;
Demo here.

Related

Django: Is there a way to apply an aggregate function on a window function?

I have already made a raw SQL of this query as a last resort.
I have a gaps-and-islands problem, where I get the respective groups with two ROW_NUMBER -s. Later on I use a COUNT and a MAX like so:
SELECT id, name, MAX(count)
FROM (
SELECT id, name, COUNT(*)
FROM (
SELECT players.id, players.name,
(ROW_NUMBER() OVER(ORDER BY match_details.id, goals.time) -
ROW_NUMBER() OVER(PARTITION BY match_details.id, players.id ORDER BY match_details.id, goals.time)) AS grp
FROM match_details
JOIN players
ON players.id = match_details.player_id
JOIN goals
ON goals.match_detail_id = match_details.id
ORDER BY match_details.id, goals.time
) AS x
GROUP BY grp, id, name
ORDER BY count DESC
) AS y
GROUP BY id, name
ORDER BY MAX(count) DESC, name
players example:
id | name
----+-------
1 | John
2 | Mark
match_details example:
id | player_id
----+------------
1 | 1
2 | 1
3 | 2
4 | 2
goals example:
id | match_detail_id | time
----+------------------+---------
1 | 1 | 2
2 | 1 | 10
3 | 2 | 2
4 | 3 | 1
5 | 3 | 5
6 | 4 | 6
output example:
id | name | max
----+--------+---------
1 | John | 2
2 | Mark | 2
So far, I have finished the innermost query with Django ORM, but when I try to annotate over group , it throws an error:
django.db.utils.ProgrammingError: aggregate function calls cannot contain window function calls
I haven't yet wrapped my head around using Subquery, but I'm also not sure if that would work at all. I do not need to filter over the window function, only use aggregates on it.
Is there a way to solve this with plain Django, or do I have to resort to hybrid raw-ORM queries, perhaps to django-cte ?

SQL Calculate a sum by replacement

So I have this table of badges (kinda' like STO has)
| user_id | grade |
---------------------
| 1 | bronze |
| 1 | silver |
| 2 | bronze |
| 1 | gold |
| 1 | bronze |
| 3 | gold |
| 1 | gold |
And I want to calculate the total sum of badge-points for user id 1.
Every bronze badge should be equal to 5, every silver - 50, gold - 200.
So in the end I need to get 460 for this sample.
For a specific user_id:
select
sum(case grade
when 'bronze' then 5
when 'silver' then 50
when 'gold' then 200
end
)
from tablename
where user_id = 1
Pretty basic conditional aggregation:
sum (case when grade = 'bronze' then 5
when grade = 'silver' then 50
when grade = 'gold' then 200
else 0 end)
You would use case and sum():
select user_id,
sum(case when grade = 'bronze' then 5
when grade = 'silver' then 50
when grade = 'gold' then 200
end)
from t
group by user_id;
Put those badge values into a another table and join to it. Or at least use a table expression.
SELECT user_id, SUM(BadgeVal) AS Total
FROM T
INNER JOIN
(VALUES ('bronze',5)
,('silver',50)
,('gold',200))
AS BadgeValues(grade, BadgeVal)
ON T.grade = BadgeValues.grade
GROUP BY user_id
ORDER BY user_id;
Note that this syntax works with SQL Server, does not work with MySQL to my knowledge, and I have no idea about Oracle or Postgres or any other DBMS.
Here you go using decode function :
SELECT SUM(t.grade_point)
FROM (SELECT id,grade,decode(grade,'bronze',5,'silver',50,'gold',200) AS grade_point
FROM badges where id =1) t;

Aggregate rows between two rows with certain value

I'm trying to formulate a query to aggregate rows that are between rows with a specific value: in this example I want to collapse and sum time of all rows that have an ID other than 1, but still show rows with ID 1.
This is my table:
ID | Time
----+-----------
1 | 60
2 | 10
3 | 15
1 | 30
4 | 100
1 | 20
This is the result I'm looking for:
ID | Time
--------+-----------
1 | 60
Other | 25
1 | 30
Other | 100
1 | 20
I have attempted to SUM and add a condition with CASE, or but so far my solutions only get me to sum ALL rows and I lose the intervals, so I get this:
ID | Time
------------+-----------
Other | 125
1 | 110
Any help or suggestions in the right direction would be greatly appreciated, thanks!
You need to define the groupings. SQLite is not great for this sort of manipulation, but you can do it by summing the "1" values up to each value.
In SQLite, we can use the rowid column for the ordering:
select (case when id = 1 then '1' else 'other' end) as which,
sum(time)
from (select t.*,
(select count(*) from t t2 where t2.rowid <= t.rowid and t2.id = 1) as grp
from t
) t
group by (case when id = 1 then '1' else 'other' end), grp
order by grp, which;

How to select for all cases within a group?

I’d like to get metrics on the number of cases in my table where my data fits different criteria. Currently, I have groups of 3+ that we’d like to inspect based on certain criteria. For the time being, I’d like to get counts of the following:
The breakdown of records in a group of 3+ where CHESS = White.
The breakdown of records in a group of 3+ where DATE like ‘%16’
The breakdown of records in a group of 3+ with all combinations of chess = white and DATE like ‘%16’
+------------------------------------------------+
| GroupID | CHESS | DATE |
| 1 | White | n16 |
| 1 | Black | n16 |
| 1 | Black | n03 |
| 2 | White | n16 |
| 2 | White | n10 |
| 2 | White | n11 |
| 3 | Black | n12 |
| 3 | White | n14 |
| 3 | Black | n16 |
---------------------------------------------------
The output would be something like:
Chess count of 1 = 2
Chess count of 2 = 0
Chess count of 3 = 1
Date count of 1 = 2
Date count of 2 = 1
Date count of 3 = 0
Cases with Chess count of 1 and Date count of 1 = 1
Cases with Chess count of 2 and Date count of 2 = 0
Cases with Chess count of 3 and Date count of 3= 0
Cases with Chess count of 1 and Date of count 2 = 1
Cases with Chess count of 1 and Date of count 3 = 0
Cases with Chess count of 2 and Date of count 1 = 0
Cases with Chess count of 2 and Date of count 3= 0
Cases with Chess count of 3 and Date of count 1 = 1
Cases with Chess count of 3 and Date of count 2 = 0
Can this be done in a way that takes into account groups of any sizes, or would it have to be specific to the group size (for example would the query only work on groups of 3)?
You could do it like here, using group by cube and others:
with groups as (select count(case when chess = 'White' then 1 end) cnt_white,
count(case when tdate = 'n16' then 1 end) cnt_n16
from t group by groupid),
numbers as (select level grp from dual connect by level <= 3)
select white, n16, cnt
from (
select n1.grp white, n2.grp n16, count(cnt_white) cnt, grouping_id(n1.grp, n2.grp) gid
from numbers n1
cross join numbers n2
left join groups on n1.grp = cnt_white and n2.grp = cnt_n16
group by cube(n1.grp, n2.grp)
having grouping_id(n1.grp, n2.grp) <> 3 )
order by case when gid = 1 then 1 when gid = 2 then 2 when gid = 0 then 3 end, white, n16
rextester demo
Modify level 3 in subquery numbers to change number of groups.

SQL: Find top-rated article in each category

I have a table articles, with fields id, rating (an integer from 1-10), and category_id (an integer representing to which category it belongs).
How can I, in one MySQL query, find the single article with the highest rating from each category? ORDER BY and LIMIT would usually be how I would find the top-rated article, I suppose, but I'm not sure how to mix that with grouping to get the desired result, if I even can. (A dependent subquery would likely be an easy answer, but ewwww. Is there something better?)
For the following data:
id | category_id | rating
---+-------------+-------
1 | 1 | 10
2 | 1 | 8
3 | 2 | 7
4 | 3 | 5
5 | 3 | 2
6 | 3 | 6
I would like the following to be returned:
id | category_id | rating
---+-------------+-------
1 | 1 | 10
3 | 2 | 7
6 | 3 | 6
Try These
SELECT id, category_id, rating
FROM articles a1
WHERE rating =
(SELECT MAX(a2.rating) FROM articles a2 WHERE a1.category_id = a2.category_id)
OR
SELECT * FROM (SELECT * FROM articles ORDER BY rating DESC) AS a1 GROUP BY a1.rating;
You can use a subselect as the target of a FROM clause, too, which reads funny but makes for a slightly easier-to-understand query.
SELECT a1.id, a1.category_id, a1.rating
FROM articles as a1,
(SELECT category_id, max(rating) AS mrating FROM articles AS a2
GROUP BY a2.category_id) AS a_inner
WHERE
a_inner.category_id = a1.category_id AND
a_inner.mrating = a1.rating;