I have a student mark table where i putted student marks by subject. I wanted to take sum of maximum 3 subjects for each student. And also wanted to see subject count or how many subjects mark entry exist on this table group by a student. This is my table structure.
students
----------------------
id | name | roll
----------------------
1 | Rahim | 201
2 | Kalas | 203
----------------------
student_marks
--------------------------------
id | student_id | subject | mark
--------------------------------
1 | 1 | A | 10
2 | 1 | B | 5
3 | 1 | C | 10
4 | 1 | D | 5
5 | 2 | A | 10
6 | 2 | B | 10
--------------------------------
my_expected_table
----------------------------------
student_id | student_name | sum
----------------------------------
1 | Rahim | 25
2 | Kalas | 20
----------------------------------
I am trying but can't understand how would i give limit on join table my sample query here
SELECT students.id as student_id,
students.name as student_name,
sum(student_marks.mark) as sum
From students
inner join student_marks on student_marks.student_id = students.id
Group by student_marks.student_id
the query output you know, it will show sum of all row. but i want like previous table "my_expected_table"
----------------------------------
student_id | student_name | sum
----------------------------------
1 | Rahim | 30
2 | Kalas | 20
----------------------------------
This is painful to do in MySQL. The best way uses variables:
select sm.student_id, count(*) as num_marks,
sum(case when rn <= 3 then mark end) as random_3_sum
from (select sm.*,
(#rn := if(#s = student_id, #rn + 1,
if(#s := student_id, 1, 1)
)
) as rn
from student_marks sm cross join
(select #rn := 0, #s := -1) params
order by student_id, id
) sm
group by sm.student_id;
Notes:
I didn't both with the join to the student table, that is obvious to add.
This defines 3 marks as "the 3 marks on the rows with the lowest id". This is determined by the second order by key.
Related
I have a table like so (I'm not sure how to format tables)
Category / Products / Purchases
1 | A | 12
1 | B | 13
1 | C | 11
2 | A | 1
2 | B | 2
2 | C | 3
Expected output:
1 | B | 13
2 | C | 3
However I keep on getting
1 | A | 13
2 | A | 3
ie. It just selects the first occurrence of the second column.
Here is my code:
SELECT Category, Products, MAX(Purchases) FROM myTable GROUP BY Category;
Use filtering in the where clause:
select t.*
from t
where t.purchases = (select max(t2.purchases) from t t2 where t2.category = t.category);
With NOT EXISTS:
select m.* from myTable m
where not exists (
select 1 from myTable
where category = m.category and purchases > m.purchases
)
See the demo.
Results:
| Category | Products | Purchases |
| -------- | -------- | --------- |
| 1 | B | 13 |
| 2 | C | 3 |
You can use row_number() to identify max purchase for each group or replace rownumber() to rank() if there are ties of max purchases for each group
Select Category, Products,
Purchases from (Select Category,
Products,
Purchases,
row_number() over (partition by
category, products order by
purchases desc) rn from table) t
where t.rn=1
)
I have an abstract problem which can be simplified as the following problem: Assume that we have two tables persons and names that look as follows:
SELECT * FROM persons;
+----+-------+--------+
| id | name | fan_of |
+----+-------+--------+
| 1 | alice | 2 |
| 2 | bob | 4 |
| 3 | carol | 1 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+-------+--------+
and
SELECT * FROM names;
+----+-------+--------+
| id | name | active |
+----+-------+--------+
| 1 | alice | 1 |
| 2 | bob | 1 |
| 3 | carol | 0 |
| 4 | dave | 1 |
+----+-------+--------+
Every person (a row in the persons) table is a fan of itself or another person (represented by that other persons id in the fan_of column). The names table contains names that can be active or inactive.
For a given offset k, I want to SELECT the persons (rows of persons) that have the k+1-th active name as their name or that have one of these people as their fans. For example, if the offset is 1, the second active name is bob and hence I want to select all people with the name bob plus the people that have one of these bobs as their fans, which is in this example the row of persons with id=4. This means that I want to have the result:
+----+------+--------+
| id | name | fan_of |
+----+------+--------+
| 2 | bob | 4 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+------+--------+
What I have so far is the following query:
1 SELECT * FROM persons WHERE
2 EXISTS (
3 SELECT * FROM (
4 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
5 ) AS selectedname WHERE (selectedname.name=persons.name)
6 )
7 OR
8 EXISTS (
9 SELECT * FROM(
10 SELECT * FROM persons WHERE EXISTS (
11 SELECT * FROM (
12 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
13 ) AS selectedname WHERE (selectedname.name=persons.name)
14 )
15 ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of
16 );
It gives me the desired result from above but please note that it is inefficient because the lines 3-5 and 11-13 are the same.
I have the following two questions:
What can be done to avoid this inefficiency?
I actually need to distinguish between those rows that came from the
name condition (here the rows with name=bob) and those that came
from the fan_of condition (here the row with name=dave). This
could be done in the application code but then I would need another
database query before to find out the k+1-th active name and this might
be slow (please correct me if this is the better solution). I would
rather prefer an additional column z that helps me to distinguish
like
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 4 | dave | 3 | 0 |
| 5 | bob | 2 | 1 |
+----+------+--------+---+
How can such an output be achieved?
It looks like I can get the minimum you want to achieve using parameters (should this be an option).
It's not pretty, but I can't see a simple way of achieving what you're asking for, so this is what I have so far....(set #offset to suit 'k')
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*
From persons a
where (a.name = #name) OR (a.id IN (SELECT fan_of from persons where name = #name));
If you still don't have an answer by the time I've had food, I'll look at part 2.
(hopefully I've read your brief correctly)
P.S. I've kept the #name SQL in a single line as it seems to read better in this context.
Edit: Here's a pretty messy but functional indicator of source, using your example. Z = 1 is where the row is from the name, '0' is from fan_of
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*,'1' as z
From persons a
where (a.name = #name)
union
select
a.*,'0' as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name));
Distinct ID Query:
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
SELECT id, name, fan_of, z FROM
(select
distinct a.id,
a.name,
a.fan_of,
1 as z
From persons a
where (a.name = #name)
union
select
distinct a.id,
a.name,
a.fan_of,
0 as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name))
ORDER BY z desc) qry
GROUP BY id;
This produces:
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 5 | bob | 2 | 1 |
| 4 | dave | 3 | 0 |
+----+------+--------+---+
I have a big MySQL table on which I'd like to calculate a cumulative product. This product has to be calculated for each group, a group is defined by the value of the first column.
For example :
name | number | cumul | order
-----------------------------
a | 1 | 1 | 1
a | 2 | 2 | 2
a | 1 | 2 | 3
a | 4 | 8 | 4
b | 1 | 1 | 1
b | 1 | 1 | 2
b | 2 | 2 | 3
b | 1 | 2 | 4
I've seen this solution but don't think it would be efficient to join or subselect in my case.
I've seen this solution which is what I want except it does not partition by name.
This is similar to a cumulative sum:
select t.*,
(#p := if(#n = name, #p * number,
if(#n := name, number, number)
)
) as cumul
from t cross join
(select #n := '', #p := 1) params
order by name, `order`;
I have very much similar kind of requirement as described in this question.
Rank users in mysql by their points
The only difference is in my data. The above problem has the data where table has only row per student. But in my case there may be a possibility that table contains multiple rows for a single student like this
Student 1 points 80
Student 2 points 77.5
Student 2 points 4.5
Student 3 points 77
Student 4 points 77
So now rank should be calculated based on the SUM of points (total) that user has. So in this case result would be.
Student 2 Rank 1 with 82 points
Student 1 Rank 2 with 80 points
Student 3 Rank 3 with 77 points
Student 4 Rank 3 with 77 points
SQL Fiddle for data
I tried couple of things with the solution of above question but couldn't get the result. Any help would be appreciated.
Using the same query in my previous answer just change the table student for a subquery to combine all records of every student
change [student er] for
(SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er
SQL DEMO
select er.*,
(#rank := if(#points = points,
#rank,
if(#points := points,
#rank + 1,
#rank + 1
)
)
) as ranking
from (SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er cross join
(select #rank := 0, #points := -1) params
order by points desc;
OUTPUT
| id | points | ranking |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 5 |
| 7 | 66 | 6 |
| 8 | 15 | 7 |
Try this:
select id, points, #row := ifnull(#row, 0) + diff rank
from (select *, ifnull(#prev, 0) != points diff, #prev := points
from (select id, sum(points) points
from students
group by 1
order by 2 desc) x) y
See SQLFiddle
EDITED:
(This should work)
SELECT I.Id, I.Points, Rk.Rank
FROM
(SELECT Id, Points, #Rk := #Rk+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T,
(SELECT #Rk := 0) AS Rk) As I
INNER JOIN
(SELECT *
FROM (
SELECT Id, Points, #Rk2 := #Rk2+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T1,
(SELECT #Rk2 := 0) AS Rk) AS T2
GROUP BY Points) As Rk
USING(Points)
The output will be:
| Id | Points | Rank |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 6 |
| 7 | 66 | 7 |
| 8 | 15 | 8 |
After two Ids in 4th position you'll get the 6th position because 5 Ids are before of the 6th.
TL;DR: How to combine two differently ordered SELECTs from unrelated tables into one result?
Let's say I have 2 non-related tables - cats and games.
Table "cats":
+----+----------------+-----+
| id | name | age |
+----+----------------+-----+
| 1 | Balthazar | 3 |
| 2 | Milkman | 7 |
| 3 | The Dark Angel | 4 |
+----+----------------+-----+
Table "games"
+----+----------+-------+
| id | name | plays |
+----+----------+-------+
| 1 | Snake | 18 |
| 2 | Lemmings | 234 |
| 3 | Ludo | 33 |
+----+----------+-------+
For each of the tables respecitively I need a SELECT to order them, and get different columns:
SELECT age FROM `cats` ORDER BY age DESC;
and
SELECT plays FROM `games` ORDER BY plays DESC;
Thing is I want to get both results using one query, instead of two. The expected result:
+-----+-------+
| age | plays |
+-----+-------+
| 7 | 234 |
| 4 | 33 |
| 3 | 18 |
+-----+-------+
No point for a JOIN as the tables share no relation. I was thinking of using UNION but I can't figure out how to handle the fact that both queries:
select different columns
order the result by different columns
Any ideas how to achieve this?
One method is to calculate a row number and then join on that value:
select rn, max(age) as age, max(plays) as plays
from ((select c.age, NULL as plays, (#rnc := #rnc + 1) as rn
from cats c cross join (select #rnc := 0) params
) union all
(select NULL as age, g.plays, (#rng := #rng + 1) as rn
from games g cross join (select #rng := 0) params
)
) cg
group by rn;
select age,plays from cats inner join games on cats.id=games.id