count number of items in a row in mysql - mysql

I have a list of students that shows whether they were present or absent from a particular class.
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int)
;
CREATE TABLE student
(`id` int, `name` varchar(4))
;
CREATE TABLE subject
(`id` int, `name` varchar(4))
;
CREATE TABLE classStatus
(`id` int, `name` varchar(8))
;
INSERT INTO classlist
(`id`, `studentid`, `subjectid`, `presentid`)
VALUES
(1, 111, 1, 1),
(2, 222, 3, 0),
(3, 333, 2, 1),
(4, 111, 4, 0),
(5, 111, 1, 1),
(6, 222, 3, 0),
(7, 333, 2, 1),
(8, 111, 4, 0),
(9, 111, 4, 0),
(10, 111, 4, 0),
(11, 111, 1, 1),
(12, 333, 3, 1),
(13, 333, 2, 1),
(14, 333, 3, 1)
;
INSERT INTO student
(`id`, `name`)
VALUES
(111, 'John'),
(222, 'Kate'),
(333, 'Matt')
;
INSERT INTO subject
(`id`, `name`)
VALUES
(1, 'MATH'),
(2, 'ENG'),
(3, 'SCI'),
(4, 'GEO')
;
INSERT INTO classStatus
(`id`, `name`)
VALUES
(0, 'Absent'),
(1, 'Present')
;
See Fiddle http://sqlfiddle.com/#!2/a2d93/5
I can count who is absent and present overall with something like the below.
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME
But I want to know how many classes in a row a student has attended/missed, so that the teachers can easily see if someone has missed a lot of time or someone is rewarded for good attendance etc. I have seen some posts which talk about streaks but none of them seem to match the way the data has been presented to me so I am not sure how to acheive this?
Just to be clear based on my sample data below the out put for the streaks should be.
(1, 111, 1, 1), /* John Present 1 times in a row */
(2, 222, 3, 0), /* Kate Absent 1 times in a row */
(3, 333, 2, 1), /* Matt Present 1 times in a row */
(4, 111, 4, 0), /* John Absent 1 times in a row */
(5, 111, 1, 1), /* John Present 1 times in a row */
(6, 222, 3, 0), /* Kate Absent 2 times in a row */
(7, 333, 2, 1), /* Matt Present 2 times in a row */
(8, 111, 4, 0), /* John Absent 1 times in a row */
(9, 111, 4, 0), /* John Absent 2 times in a row */
(10, 111, 4, 0), /* John Absent 2 times in a row */
(11, 111, 1, 1), /* John Present 1 times in a row */
(12, 333, 3, 1), /* Matt Present 3 times in a row */
(13, 333, 2, 1), /* Matt Present 4 times in a row */
(14, 333, 3, 1) /* Matt Present 5 times in a row */
/*Showing the latest status for each user*/
/* John Present 1 times in a row */
/* Kate Absent 2 times in a row */
/* Matt Present 5 times in a row */
John present 1.
Kate absent 2.
Matt Present 5.

This should give the count of the consecutive rows with the same value as the last row:
select
classlist.studentid,
student.name,
classStatus.name status,
count(*) presentcnt
from
classlist inner join student
on classlist.studentid=student.id
inner join classstatus
on classlist.presentid=classstatus.id
inner join (
select
studentid,
max(case when presentid=0 then id end) max_0,
max(case when presentid=1 then id end) max_1
from classlist
group by studentid
) s
on coalesce(classlist.id>least(max_0,max_1) and
classlist.id<=greatest(max_0,max_1),1)
and s.studentid=classlist.studentid
group by classlist.studentid
On the subquery I'm extracting the max id where presentid = 0 and the max id where presentid = 1.
On the outer query I'm extracting and counting all rows where id is > than the least of max_0 and max_1, and <= than the greatest of those two. No matter what the last value is, these are all the rows with the same value as the last one.
If either max_0 or max_1 is null, it means that all rows have just one value, 1 or 0, and we have to get all of them. If one of max_0 or max_1 is null, the whole conditions will be null as well. Using Coalesce( condition, 1) I'm returning all rows in that case.
Notice that I am grouping by classlist.studentid and showing some nonaggregated columns, but this is a situation in which it is allowed since all nonaggregated columns have the same value.

Do you want this?:
SELECT
studentid,
name,
SUM(present = 1) AS present,
SUM(present = 0) AS absent,
count(subject) as classTimes,
subject
FROM your_table GROUP BY studentid, name,subject

If id from classlist can be used for ordering then you will get your expected result with
SELECT
s.name,
CASE t1.presentid
WHEN 0 THEN 'absent'
WHEN 1 THEN 'present'
END state,
t1.pc
FROM (
SELECT
c.id,
c.studentid,
c.presentid,
#pcount := IF( #pid = presentid AND #sid = studentid, #pcount + 1, 1 ) as pc,
#pid := presentid,
#sid := studentid
FROM
classlist c
ORDER BY
studentid, id ) t1
JOIN student s
ON t1.studentid = s.id
WHERE
( t1.studentid, t1.id ) IN ( SELECT
studentid,
MAX( id )
FROM classlist
GROUP BY studentid );
SQL Fiddle DEMO

Try this:
SELECT A.studentid, s.name, IF(presentid = 1, 'present', 'absent') STATUS,
ABS(SUM(IF(presentid = 1, 1, -1))) AS presentcnt
FROM classlist A
INNER JOIN student s ON A.studentid = s.id
LEFT JOIN (SELECT MAX(id) id, studentid
FROM classlist GROUP BY studentid
) B ON A.studentid = B.studentid AND A.id = B.id
GROUP BY A.studentid
Check this SQL FIDDLE DEMO
OUTPUT
| STUDENTID | NAME | STATUS | PRESENTCNT |
-------------------------------------------
| 111 | John | present | 1 |
| 222 | Kate | absent | 2 |
| 333 | Matt | present | 5 |

I'm not sure what point about your question.
If this you want
John present 1.
Kate absent 2.
Matt Present 5.
U may try this:
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent,
IF( SUM(presentid = 1)-SUM(presentid = 0)>=0,SUM(presentid = 1)-SUM(presentid = 0),SUM(presentid = 0)-SUM(presentid = 1)) AS aliase
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME

Related

Check if a pair of records belong to multiple group IDs

I have a table that contains 2 IDs - UserID and GroupID. I need to pull a list of all UserIDs that "share" the same GroupID at least 4 times
So, based on the following data set:
CREATE TABLE IF NOT EXISTS `tableA` (
`UserID` int(11) unsigned NOT NULL,
`GroupID` int(11) unsigned NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `tableA` (`UserID`, `GroupID`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(1, 2),
(2, 2),
(3, 2),
(1, 3),
(2, 3),
(3, 3),
(1, 4),
(2, 4),
(3, 4),
(1, 5),
(3, 5);
I'm trying to generate the following result:
UserID A
UserID B
NumberOfOccurrences
1
2
4
2
3
4
1
3
5
I've created an SQLFiddle for it. I've tried to achieve this via JOINs and sub-queries, but I'm not entirely sure how to properly proceed with something like this.
Do a self join. GROUP BY. Use HAVING to make sure at least 4 common GroupID's.
select a1.userid, a2.userid
from tablea a1
join tablea a2
on a1.GroupID = a2.GroupID and a1.userid < a2.userid
group by a1.userid, a2.userid
having count(*) >= 4

How do i get a limited number of rows for each value in a field? (db is mysql but could be changed if easier)

I found this difficult to search for this question. I have a table of sports fixtures (tbl_fixture) and a table of sports participants (tbl_participant) which have a many-to-many relationship via a linking table (tbl_fixture_participant)
I need to return the most recent 3 fixtures (ie latest tbl_fixture.start_datetime) of multiple participants and whether they won each of the fixtures, (eg more recent 3 fixtures of participant 1 and most recent 3 fixtures of participant 2, and most recent 3 fixtures of participant 3, with each record returning the fixture_id, participant_id, start_datetime and is_winner fields).
The number of participants that i need to get the data for could be between 1 and 100.
If there's a better way to structure my data, or a better database for this type of query (graph db?) then i'm happy to look into those.
Here's a sample schema:
CREATE TABLE tbl_fixture (
fixture_id INT AUTO_INCREMENT PRIMARY KEY,
start_datetime DATETIME NOT NULL
);
CREATE TABLE tbl_participant (
participant_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE tbl_fixture_participant (
fixture_id INT NOT NULL,
participant_id INT NOT NULL,
is_winner TINYINT NOT NULL,
FOREIGN KEY (fixture_id)
REFERENCES tbl_fixture (fixture_id)
ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (participant_id)
REFERENCES tbl_participant (participant_id)
ON UPDATE RESTRICT ON DELETE CASCADE
);
INSERT INTO tbl_fixture (fixture_id, start_datetime)
VALUES (1, "2021-01-14 15:00:00"),
(2, "2021-01-13 16:00:00"),
(3, "2021-01-12 17:00:00"),
(4, "2021-01-11 15:00:00"),
(5, "2021-01-19 16:00:00"),
(6, "2021-01-18 17:00:00"),
(7, "2021-01-05 15:00:00"),
(8, "2021-01-03 16:00:00"),
(9, "2021-01-03 17:00:00"),
(10, "2021-01-11 15:00:00"),
(11, "2021-01-12 16:00:00"),
(12, "2021-01-13 17:00:00"),
(13, "2021-01-14 15:00:00"),
(14, "2021-01-19 16:00:00");
INSERT INTO tbl_participant (participant_id, name) VALUES
( 1,"Team 1"),
( 2,"Team 2"),
( 3,"Team 3");
INSERT INTO tbl_fixture_participant (fixture_id, participant_id, is_winner)
VALUES (1, 1, 0)
,(2, 1, 1)
,(2, 2, 0)
,(3, 1, 1)
,(12, 2, 0)
,(4, 3, 1)
,(4, 2, 0)
,(6, 3, 1)
,(1, 2, 1)
,(10, 1, 1)
,(5, 2, 0)
,(6, 1, 0)
,(11, 1, 1)
,(14, 1, 0)
,(7, 2, 0)
,(7, 3, 1)
,(3, 3, 0)
,(8, 1, 0)
,(5, 3, 1)
,(13, 2, 0)
,(8, 3, 1)
,(13, 3, 1)
,(9, 1, 0)
,(9, 2, 1)
,(10, 2, 0)
,(11, 3, 0)
,(12, 3, 1)
,(14, 3, 1);
And SQL Fiddle of same.
I would like the data to come back like:
fixture_id
start_datetime
participant_id
is_winner
14
2021-01-19T16:00:00Z
1
0
6
2021-01-18T17:00:00Z
1
0
1
2021-01-14T15:00:00Z
1
0
5
2021-01-19T16:00:00Z
2
0
13
2021-01-14T15:00:00Z
2
0
1
2021-01-14T15:00:00Z
2
1
EDITED TO REFLECT FACT THAT DATES ARE NOT NECESSARILY SEQUENTIAL...
E.g. (for older versions of MySQL)...
SELECT x.*
, fx.start_datetime
FROM fixture_participant x
JOIN fixture fx
ON fx.fixture_id = x.fixture_id
JOIN fixture_participant y
ON y.participant_id = x.participant_id
JOIN fixture fy
ON fy.fixture_id = y.fixture_id
AND fy.start_datetime > fx.start_datetime
GROUP
BY x.fixture_id
, x.participant_id
, x.is_winner
, fx.start_datetime
HAVING COUNT(x.fixture_id) <=3
ORDER
BY participant_id,fixture_id;
...or something like that.

Get summary grouped by category with three tables on mysql

category
---------------------------
id_category primary key
category
id_user foreign key
counterpart
---------------------------
id_counterpart primary key
counterpart
id_category foreign key
id_user foreign key
transaction
---------------------------
transaction primary key
date
id_counterpart foreign key
amount
id_card foreign key
id_user foreign key
Hello,
I have thoses table on mysql database and i want to have summary of each category (with 0 if there is any transaction) by month and year based on id_user.
I tried this command to have grouped by counterpart and it works but cannot reach when i add category and group by id_category.
select counterpart, s2.total from counterpart as s1
left join (select coalesce(sum(amount),0) as total, id_counterpart from transaction where year(date) = 2019 and month(date) = 7 and id_user = 2 group by id_counterpart) as s2
on s1.id_counterpart = s2.id_counterpart
left join category on s1.id_category = category.id_category
group by counterpart;
Do you have any idea to do that ? Else, i will do with php.
Thank you.
Edit : Add example
INSERT INTO `category` (`id_category`, `category`, `id_user`) VALUES
(1, 'cat_a', 1),
(2, 'cat_b', 1),
(3, 'cat_c', 1);
INSERT INTO `counterpart` (`id_counterpart`, `counterpart`, `id_category`, `id_user`) VALUES
(1, 'cp_a', 1, 1),
(2, 'cp_b', 2, 1),
(3, 'cp_c', 2, 1);
INSERT INTO `transaction` (`id_transaction`, `date`, `id_counterpart`, `amount`, `id_card`, `id_user`) VALUES
(1, '2019-07-01 00:00:00', 1, 400.00, 2, 1),
(2, '2019-07-01 00:00:00', 1, -24.95, 2, 1),
(3, '2019-07-31 00:00:00', 2, -20.04, 2, 1);
(4, '2019-07-30 00:00:00', 2, -1.00, 2, 1);
(5, '2019-07-29 00:00:00', 3, -2.00, 2, 1);
(6, '2019-07-28 00:00:00', 1, -3.00, 2, 1);
(7, '2019-07-27 00:00:00', 3, 2.00, 2, 1);
(8, '2019-07-26 00:00:00', 2, 5.00, 2, 1);
On july 2019 i want to have this, for user 1 :
cat_a 372.05
cat_b 16.04
cat_c 0.00
Join the tables and then group by category:
select c.category, coalesce(sum(t.amount), 0) total
from category c
left join counterpart as cp
on c.id_category = cp.id_category and c.id_user = cp.id_user
left join transaction t
on t.id_counterpart = cp.id_counterpart and t.id_user = cp.id_user and year(t.date) = 2019 and month(t.date) = 7 and t.id_user = 1
group by c.id_category, c.category
See the demo.
Results:
| category | total |
| -------- | ------ |
| cat_a | 372.05 |
| cat_b | -16.04 |
| cat_c | 0 |

Multiple INNER JOIN subqueries sql

I have the below query which comes from this post count number of items in a row in mysql which counts how many times in a row a student has been present/absent from a class.
SELECT
classlist.studentid,
student.name,
classStatus.name status,
COUNT(*) presentcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN classstatus ON classlist.presentid=classstatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN presentid=0 THEN id END) max_0,
max(CASE WHEN presentid=1 THEN id END) max_1
FROM classlist
GROUP BY studentid
) s
ON coalesce(classlist.id>least(max_0,max_1) AND classlist.id<=greatest(max_0,max_1),1) AND s.studentid=classlist.studentid
GROUP BY classlist.studentid
This works as expected,
STUDENTID NAME STATUS PRESENTCNT
111 John Present 1
222 Kate Absent 2
333 Matt Present 5
I want to extend the query so that I have a column showing if the student particpated in the class.
If I run an independent query I get the results I want
SELECT
classlist.studentid,
student.name,
participatedStatus.name status,
COUNT(*) participatedcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN participatedStatus ON classlist.participatedid=participatedStatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN participatedid=0 THEN id END) max_0,
max(CASE WHEN participatedid=1 THEN id END) max_1
FROM classlist
group by studentid
) s
ON coalesce(classlist.id>least(max_0,max_1)
AND classlist.id<=greatest(max_0,max_1),1)
AND s.studentid=classlist.studentid
group by classlist.studentid
STUDENTID NAME STATUS PARTICIPATEDCNT
111 John Yes 1
222 Kate No 2
333 Matt Yes 2
However I want to merge them into the one query so I get
STUDENTID NAME STATUS PRESENTCNT STATUS2 PARTICIPATEDCNT
111 John Present 1 Yes 1
222 Kate Absent 2 No 2
333 Matt Present 5 Yes 2
I am confused about how this can be achieved as I am selecting count *, how can I acheive this?
A sample of the data I am using is in this fiddle and below
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int, `participatedid` int);
CREATE TABLE student
(`id` int, `name` varchar(4));
CREATE TABLE subject
(`id` int, `name` varchar(4));
CREATE TABLE classStatus
(`id` int, `name` varchar(8));
CREATE TABLE participatedStatus
(`id` int, `name` varchar(8));
INSERT INTO classlist (`id`, `studentid`, `subjectid`, `presentid`, `participatedid`)
VALUES (1, 111, 1, 1, 0), (2, 222, 3, 0, 0), (3, 333, 2, 1, 0), (4, 111, 4, 0, 0), (5, 111, 1, 1, 0), (6, 222, 3, 0, 0), (7, 333, 2, 1, 1), (8, 111, 4, 0, 0), (9, 111, 4, 0, 0), (10, 111, 4, 0, 0), (11, 111, 1, 1, 1), (12, 333, 3, 1, 0), (13, 333, 2, 1, 1), (14, 333, 3, 1, 1);
INSERT INTO student (`id`, `name`)
VALUES (111, 'John'),(222, 'Kate'),(333, 'Matt');
INSERT INTO subject (`id`, `name`)
VALUES (1, 'MATH'),(2, 'ENG'),(3, 'SCI'),(4, 'GEO');
INSERT INTO classStatus (`id`, `name`)
VALUES (0, 'Absent'), (1, 'Present');
INSERT INTO participatedStatus (`id`, `name`)
VALUES (0, 'No'),(1, 'Yes');
SELECT
studid,
studname,
status,
presentcnt,
status1,
participatedcnt FROM
(SELECT
classlist.studentid studid,
student.name studname,
classStatus.name status,
COUNT(*) presentcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN classstatus ON classlist.presentid=classstatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN presentid=0 THEN id END) max_0,
max(CASE WHEN presentid=1 THEN id END) max_1
FROM classlist
GROUP BY studentid
) s
ON coalesce(classlist.id>least(max_0,max_1) AND classlist.id<=greatest(max_0,max_1),1) AND s.studentid=classlist.studentid
GROUP BY classlist.studentid)x
JOIN
(SELECT
classlist.studentid,
student.name,
participatedStatus.name status1,
COUNT(*) participatedcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN participatedStatus ON classlist.participatedid=participatedStatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN participatedid=0 THEN id END) max_0,
max(CASE WHEN participatedid=1 THEN id END) max_1
FROM classlist
group by studentid
) s
ON coalesce(classlist.id>least(max_0,max_1)
AND classlist.id<=greatest(max_0,max_1),1)
AND s.studentid=classlist.studentid
group by classlist.studentid)y
ON x.studid=y.studentid
Fiddle

SELECT data based on result of previous row in table

I have a database of students.
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int)
;
CREATE TABLE student
(`id` int, `name` varchar(4))
;
CREATE TABLE subject
(`id` int, `name` varchar(4))
;
CREATE TABLE classStatus
(`id` int, `name` varchar(8))
;
INSERT INTO classlist
(`id`, `studentid`, `subjectid`, `presentid`)
VALUES
(1, 111, 1, 1),
(2, 222, 3, 0),
(3, 333, 2, 1),
(4, 111, 4, 1),
(5, 111, 1, 0),
(6, 222, 3, 0),
(7, 333, 2, 1),
(8, 111, 4, 1),
(9, 111, 2, 0),
(10, 111, 4, 1),
(11, 111, 1, 1),
(12, 333, 3, 1),
(13, 333, 2, 1),
(14, 333, 3, 1)
;
INSERT INTO student
(`id`, `name`)
VALUES
(111, 'John'),
(222, 'Kate'),
(333, 'Matt')
;
INSERT INTO subject
(`id`, `name`)
VALUES
(1, 'MATH'),
(2, 'ENG'),
(3, 'SCI'),
(4, 'GEO')
;
INSERT INTO classStatus
(`id`, `name`)
VALUES
(0, 'Absent'),
(1, 'Present')
;
And I have a query which shows how many times they have been present or absent.
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME
See this fiddle below.
http://sqlfiddle.com/#!2/fe0b0/1
There seems to be a trend from looking at this sample data that after someone attends subjectid 4 they are often not coming to the next class. How can I capture this in a query. I want to ONLY show data WHERE last subjectid =4. So in my sample data rows matching my criteria would be.
(5, 111, 1, 0),
(9, 111, 2, 0),
(11, 111, 1, 1),
as these rows are all the next row of a studentid who had a subjectid=4.
My output would be
| STUDENTID | NAME | PRESENT | ABSENT|
| 111 | John | 1 | 2 |
To get the next class for a student, use a correlated subquery:
select cl.*,
(select min(cl2.id) from classlist cl2 where cl2.studentid = cl.studentid and cl2.id > cl.id) as nextcl
from classlist cl
Plugging this into your query example tell you you who is present and absent for the next class:
SELECT students.id, students.name AS NAME,
SUM(cl.presentid = 1) AS present, SUM(cl.presentid = 0) AS absent,
sum(clnext.presentid = 1) as presentnext
FROM (select cl.*,
(select min(cl2.id) from classlist cl2 where cl2.studentid = cl.studentid and cl2.id > cl.id) as nextcl
from classlist cl
) cl INNER JOIN
student as students
ON cl.studentid = students.id left outer join
classlist clnext
on cl.nextcl = clnext.id
GROUP BY students.id, students.NAME
Add a where cl.subjectid = 4 to get the answer for subject 4.
I fixed the query. The SQLFiddle is k.
A quick and dirty solution could be to get the Classlist.Id for all lines where subjectid=4 (let's call them n) then select all the lines where Id = n+1