Select longest streak in days from mysql database? - php

First off, I have found quite a few similar questions on both Google and Stackoverflow but I can't seem to grasp how to do this correctly.
I have a table that looks like this:
id ms_date
------------------
1 2018-11-18
2 2018-11-18
3 2018-11-20
4 2018-11-22
5 2018-11-25
6 2018-11-26
7 2018-11-26
8 2018-11-27
9 2018-11-28
10 2018-11-29
What i need to do is to get the longest streak in Days from that table.
So in the above example, the longest streak is 4 days.
I found this: https://dzone.com/articles/how-to-find-the-longest-consecutive-series-of-even
and it explains what he's trying to do which is SIMILAR to what I am trying to achieve but then it is so badly explained/written that I cannot make sense of it.
I also need to find the GAP between those dates and start counting the streak again. So again in the example above, because there's a gap between the dates, the Current Streak should be 3 Days.
I've tried to used the code from the link above but that is based on SQL and also it has some strange WITH words in the code which doesn't make sense at all.
Is there an easy way to achieve this using PHP and Mysql ?
Could someone please advice on this issue?
Thanks in advance.
EDIT:
I can't seem to find the software version in my PHPMYADMIN but this is what i can see under the Database server section:
Server: Localhost via UNIX socket
Server type: MariaDB
Server connection: SSL is not being used Documentation
Server version: 10.0.37-MariaDB-0+deb8u1 - (Debian)
Protocol version: 10
User: freemind#localhost
Server charset: UTF-8 Unicode (utf8)
Second EDIT:
Tried the following based on the below answer and I get nothing at all:
$sql_COUNT = "SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = val - 1 THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=val
FROM
( SELECT DISTINCT ms_date FROM MY_TABLE ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
ORDER
BY ms_date
) a
GROUP
BY i
ORDER
BY max_streak DESC LIMIT 1";
$query_COUNT = mysqli_query($db_conx, $sql_COUNT);
$productCount_COUNT = mysqli_num_rows($query_COUNT); // count the output amount
echo $productCount_COUNT;
Third Edit:
The following code echo's 1 on my page BUT based on what I have in MYSQL database, it should echo 4:
$sql_COUNT = "SELECT COUNT(*) AS max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = ms_date - 1 THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=ms_date
FROM
( SELECT DISTINCT ms_date FROM MY_TABLE ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
ORDER
BY ms_date
) a
GROUP
BY i
ORDER
BY max_streak DESC LIMIT 1";
$query_COUNT = mysqli_query($db_conx, $sql_COUNT);
$productCount_COUNT = mysqli_num_rows($query_COUNT); // count the output amount
echo $productCount_COUNT;
Fourth EDIT:
I'm not sure if the answers below was tested but it doesn't work for me.
i tried the folowings and even though I get no errors, I only see a blank page which means the code is not working:
$sql_COUNT = "SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = ms_date - INTERVAL 1 DAY THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=ms_date
FROM
( SELECT DISTINCT ms_date FROM MY_TABLE ORDER BY ms_date ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
) a
GROUP
BY i
ORDER
BY max_streak DESC";
$query_COUNT = mysqli_query($db_conx, $sql_COUNT);
$count = mysqli_fetch_array($query_COUNT);
echo $count[0];

Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,val INT NOT NULL
);
INSERT INTO my_table VALUES
(1 , 11),
(2 , 11),
(3 , 12),
(4 , 13),
(5 , 14),
(6 , 17),
(7 , 18),
(8 , 20),
(9 , 21),
(10 , 22);
SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = val - 1 THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=val
FROM
( SELECT DISTINCT val FROM my_table ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
ORDER
BY val
) a
GROUP
BY i
ORDER
BY max_streak DESC LIMIT 1;
+------------+
| max_streak |
+------------+
| 4 |
+------------+
1 row in set (0.01 sec)
EDIT:
If you're using dates, then the logic is the same, but you just have to substitute a bit of date arithmetic...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,val DATE NOT NULL
);
INSERT INTO my_table VALUES
(1 , '2018-11-11'),
(2 , '2018-11-11'),
(3 , '2018-11-12'),
(4 , '2018-11-13'),
(5 , '2018-11-14'),
(6 , '2018-11-17'),
(7 , '2018-11-18'),
(8 , '2018-11-20'),
(9 , '2018-11-21'),
(10 , '2018-11-22');
SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = val - INTERVAL 1 DAY THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=val
FROM
( SELECT DISTINCT val FROM my_table ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
ORDER
BY val
) a
GROUP
BY i
ORDER
BY max_streak DESC LIMIT 1;
+------------+
| max_streak |
+------------+
| 4 |
+------------+
Purists would argue (correctly) that I'm using #variable assignments in an incorrect manner - but it's just a bad habit I picked up. That said, I think a correct assignment method is as follows, but no doubt someone will let me know if I'm mistaken...
SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = val - INTERVAL 1 DAY THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=val
FROM
( SELECT DISTINCT val FROM my_table ORDER BY val ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
) a
GROUP
BY i
ORDER
BY max_streak DESC LIMIT 1;
If you want to bundle this up inside some PHP, I guess it might look like this (although it should be obvious from what follows that application code is not really my forte)...
<?php
require('path/to/connection/stateme.nts');
$query = "
SELECT COUNT(*) max_streak
FROM
( SELECT x.*
, CASE WHEN #prev = val - INTERVAL 1 DAY THEN #i:=#i ELSE #i:=#i+1 END i
, #prev:=val
FROM
( SELECT DISTINCT val FROM MY_TABLE ORDER BY val ) x
JOIN
( SELECT #prev:=null,#i:=0 ) vars
) a
GROUP
BY i
ORDER
BY max_streak DESC
LIMIT 1;
";
$result = mysqli_query($conn, $query) or die(mysqli_error($conn));
$row = mysqli_fetch_assoc($result);
print_r($row);
?>
-- outputs
Array ( [max_streak] => 4 )

Related

PHP and MySQL query output different

I have the following query. When I run the query straight in MySQL I get all the information but when I run the same query in my PHP script it's missing some information. I ran the query in MySQL first to make sure that I I get everything I need and then copied the query into my php script. Can anyone please help:
$get_attendance_profile_stmt = $conn->prepare('SELECT
p_info.fname AS \'First name\',
p_info.lname AS \'Lastname\',
stud_info.t_belt_id,
t_b.short_descr,
t_b.long_descr,
stud_info.h_belt_id,
h_b.short_descr,
h_b.long_descr,
AB.*,
ROUND(
(
AB.Considered_Attended / AB.Classes_Tought * 100
),
0
) AS \'Attendance %\'
FROM
(
SELECT
student_id,
SUM(
CASE WHEN attendance_reason = 1 THEN 1 ELSE 0
END
) AS \'Sick\',
SUM(
CASE WHEN attendance_reason = 2 THEN 1 ELSE 0
END
) AS \'Injured\',
SUM(
CASE WHEN attendance_reason = 3 THEN 1 ELSE 0
END
) AS \'Work\',
SUM(
CASE WHEN attendance_reason = 4 THEN 1 ELSE 0
END
) AS \'School\',
SUM(
CASE WHEN attendance_reason = 5 THEN 1 ELSE 0
END
) AS \'Did not attend\',
SUM(
CASE WHEN attendance_reason = 6 THEN 1 ELSE 0
END
) AS \'Present\',
SUM(
CASE WHEN attendance_reason != 5 THEN 1 ELSE 0
END
) AS \'Considered_Attended\',
MAX(DateCounter) AS \'Classes_Tought\'
FROM
attendance a
JOIN(
SELECT COUNT(
DISTINCT CONVERT(creation_date, DATE)
) AS DateCounter
FROM
attendance
WHERE
creation_date <= (SELECT DATE_SUB(NOW(), INTERVAL -6 MONTH))
) DT
WHERE
creation_date <= (SELECT DATE_SUB(NOW(), INTERVAL -1 MONTH))
GROUP BY
student_id
) AB
JOIN student_info stud_info ON
stud_info.student_id = AB.student_id
JOIN users u ON
stud_info.uid = u.uid
JOIN personal_info p_info ON
u.uid = p_info.uid
LEFT JOIN t_belts t_b ON
stud_info.t_belt_id = t_b.t_belt_id
LEFT JOIN h_belts h_b ON
stud_info.h_belt_id = h_b.h_belt_id
WHERE
u.account_status = \'Active\'');
My output running the query inside MySQL is like this:
The output of the query in php looks like this:
Any help will be greatly appreciated.
PHP array's need to have unique indices. Your query returns 2 columns with the same name so the second values are what you get (they overwrite the first during assignment, simple example https://3v4l.org/sbs41 )). As can be seen in the screenshot your second columns are empty. Use an alias on the first columns (or the second) so your indices in PHP are unique. e.g.
SELECT
p_info.fname AS \'First name\',
p_info.lname AS \'Lastname\',
stud_info.t_belt_id,
t_b.short_descr,
t_b.long_descr,
stud_info.h_belt_id,
h_b.short_descr as short_descr_dontcare,
h_b.long_descr as long_descr_dontcare
or
SELECT
p_info.fname AS \'First name\',
p_info.lname AS \'Lastname\',
stud_info.t_belt_id,
t_b.short_descr as short_descr_real,
t_b.long_descr as long_descr_real,
stud_info.h_belt_id,
h_b.short_descr,
h_b.long_descr
with the latter approach you'll need to modify the indices in your PHP array with _real.

Single query that allows alias with it's own limit

I would like to better optimize my code. I'd like to have a single query that allows an alias name to have it's own limit and also include a result with no limit.
Currently I'm using two queries like this:
// ALL TIME //
$mikep = mysqli_query($link, "SELECT tasks.EID, reports.how_did_gig_go FROM tasks INNER JOIN reports ON tasks.EID=reports.eid WHERE `priority` IS NOT NULL AND `partners_name` IS NOT NULL AND mike IS NOT NULL GROUP BY EID ORDER BY tasks.show_date DESC;");
$num_rows_mikep = mysqli_num_rows($mikep);
$rating_sum_mikep = 0;
while ($row = mysqli_fetch_assoc($mikep)) {
$rating_mikep = $row['how_did_gig_go'];
$rating_sum_mikep += $rating_mikep;
}
$average_mikep = $rating_sum_mikep/$num_rows_mikep;
// AND NOW WITH A LIMIT 10 //
$mikep_limit = mysqli_query($link, "SELECT tasks.EID, reports.how_did_gig_go FROM tasks INNER JOIN reports ON tasks.EID=reports.eid WHERE `priority` IS NOT NULL AND `partners_name` IS NOT NULL AND mike IS NOT NULL GROUP BY EID ORDER BY tasks.show_date DESC LIMIT 10;");
$num_rows_mikep_limit = mysqli_num_rows($mikep_limit);
$rating_sum_mikep_limit = 0;
while ($row = mysqli_fetch_assoc($mikep_limit)) {
$rating_mikep_limit = $row['how_did_gig_go'];
$rating_sum_mikep_limit += $rating_mikep_limit;
}
$average_mikep_limit = $rating_sum_mikep_limit/$num_rows_mikep_limit;
This allows me to show an all-time average and also an average over the last 10 reviews. Is it really necessary for me to set up two queries?
Also, I understand I could get the sum in the query, but not all the values are numbers, so I've actually converted them in PHP, but left out that code in order to try and simplify what is displayed in the code.
All-time average and average over the last 10 reviews
In the best case scenario, where your column how_did_gig_go was 100% numeric, a single query like this could work like so:
SELECT
AVG(how_did_gig_go) AS avg_how_did_gig_go
, SUM(CASE
WHEN rn <= 10 THEN how_did_gig_go
ELSE 0
END) / 10 AS latest10_avg
FROM (
SELECT
#num + 1 AS rn
, tasks.show_date
, reports.how_did_gig_go
FROM tasks
INNER JOIN reports ON tasks.EID = reports.eid
CROSS JOIN ( SELECT #num := 0 AS n ) AS v
WHERE priority IS NOT NULL
AND partners_name IS NOT NULL
AND mike IS NOT NULL
ORDER BY tasks.show_date DESC
) AS d
But; Unless all the "numbers" are in fact numeric you are doomed to sending every row back from the server for php to process unless you can clean-up the data in MySQL somehow.
You might avoid sending all that data twice if you establish a way for your php to use only the top 10 from the whole list. There are probably way of doing that in PHP.
If you wanted assistance in SQL to do that, then maybe having 2 columns would help, it would reduce the number of table scans.
SELECT
EID
, how_did_gig_go
, CASE
WHEN rn <= 10 THEN how_did_gig_go
ELSE 0
END AS latest10_how_did_gig_go
FROM (
SELECT
#num + 1 AS rn
, tasks.EID
, reports.how_did_gig_go
FROM tasks
INNER JOIN reports ON tasks.EID = reports.eid
CROSS JOIN ( SELECT #num := 0 AS n ) AS v
WHERE priority IS NOT NULL
AND partners_name IS NOT NULL
AND mike IS NOT NULL
ORDER BY tasks.show_date DESC
) AS d
In future (MySQL 8.x) ROW_NUMBER() OVER(order by tasks.show_date DESC) would be a better method than the "roll your own" row numbering (using #num+1) shown before.

how should i do this query in mysql

I want to do the following. I have a table in the database, I am working on a table called asistencia and this table has 3 columns
id_asistencia as a int AUTOINCREMENT
nro_matricula as an int which I took it from another table called
alumnos
fecha as a date
This is a sketch of the database
id_asistencia | nro_matricula | fecha
1 | 0001| 2015-01-10
2 | 0002| 2015-01-10
3 | 0002| 2015-02-10 (another date )
The thing is I have to do a percentage
select all id_1 records in my nro_matricula column and see how many times its repeated in my rows and do a percentage respect all the dates in my database
EG : id_1 came to class day(whatever day) and he/she did not came to class the next day so id_1 has 50% assistance
Expected result
nro_matricula | percentage
0001| 50
0002| 100
The question is how can I make this query. If can be done in PHP its even better but i feel that this can be done in SQL
PS : The Database wasn't created by me
And excuse my English is not the better and i expect it to be understandable for you to help me
You can use sql statement like this:
select (
sum (if nro_matricula = '001' ,1,0 )
/ count(*)
from asistencia
--where nro_matricula = '001'
Maybe just simply:
select al.nro_matricula,
100 * count(distinct al.fecha) / (select count(distinct al1.fecha) from alumnos al1) as percentage
from alumnos al
group by al.nro_matricula
I did found the answer to my question. Thank you all for helping me out
SELECT
asistencia.nro_matricula as matricula,
COUNT( DISTINCT asistencia.fecha)* 100 /
COUNT( DISTINCT asistencia.nro_matricula) / (SELECT COUNT(DISTINCT asistencia.fecha)
FROM asistencia
ORDER BY COUNT(*) DESC
LIMIT 1 )
as porcentaje_asistencia
FROM asistencia
JOIN alumno
WHERE asistencia.nro_matricula = alumno.nro_matricula AND alumno.id_curso = 'basica6a'
Tried this in Oracle. Should work in MySQL too.
SELECT aa.NRO_MATRICULA , days_present/total_count* 100 FROM
(SELECT DISTINCT NRO_MATRICULA,
COUNT(*) as days_present FROM ASISTENCIA GROUP BY NRO_MATRICULA ) AA
,
(SELECT COUNT(*) as total_count FROM (SELECT DISTINCT(FECHA) FROM ASISTENCIA GROUP BY FECHA)) BB
Ouptut
nro_matricula percentage
0001 50
0002 100
The query (SELECT COUNT(*) FROM (SELECT DISTINCT(FECHA) FROM ASISTENCIA AA GROUP BY FECHA)) will give count of distinct date (2 in your case). Then we are getting distinct nro_matricula group by nro_matricula to get its count which will give the days it was present. Then divide both values from above steps to get percentage.

How to find neighbour order by another colum mysql

My problem is,
I have a table in mysql
which colums are
id Student score
1 A 55
2 B 86
3 C 65
4 D 23
5 E 84
6 F 45
7 G 80
I want to find rank of any student in whole class based on score, with the student who scored just greater them him and an another student who scored just less them him.
for example if I am searching for student E
then output should be
id User score rank_in_classs
2 B 86 1
5 E 84 2
7 G 80 3
An another example can be that if I am looking for student A
id User score rank_in_classs
3 c 65 4
1 A 55 5
6 F 45 6
How can I find it using mysql query.
Thanks
Query
SELECT id, Student, score,
FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC )
FROM tbl )
) AS rank_in_class
FROM tbl
ORDER BY rank_in_class
LIMIT 3;
DEMO
Using the FIND_IN_SET() based solution proposed by Ullas you can locate a particular student +/- 1 rank this way:
set #this_rank := (
SELECT
FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC )
FROM tbl
)
) AS rank_in_class
FROM tbl
where student = 'A'
);
select
*
from (
SELECT
id
, Student
, score
, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC )
FROM tbl
)
) AS rank_in_class
FROM tbl
) ric
where rank_in_class between #this_rank-1 AND #this_rank+1
ORDER BY rank_in_class
LIMIT 3;
Note: IF you stored the rank value in the table, then this would not be so cumbersome and could perform way better.
The best way to do that is probably with a mix of two queries and a little of PHP.
The first query retrieves the rank of the "focused" student you want;
The second query retrieves the range of all the students around the focused "student" with a dynamic LIMIT statement (this is where the use of PHP is unavoidable);
So something like this would probably do the job :
$user = 'C'; // Student user we want to "focus"
$range = 1; // Range around the "focus" : 1 before and 1 after (could be changed to anything else)
// First query : retrieving the rank of the "focused" student
$stmt = $mysqli->prepare('SELECT COUNT(*) AS Rank FROM Student AS Focused INNER JOIN Student as Others ON Others.Score > Focused.Score OR (Others.Score = Focused.Score AND Others.Id > Focused.Id) WHERE Focused.user = ?');
$stmt->bind_param('s',$user);
$stmt->execute();
$res = $stmt->get_result()->fetch_assoc();
$startRank = $res['Rank'];
// Computing the dynamic LIMIT
if (($startRank - $range) < 1) {
$offset = 0;
$rowCount = $startRank + $range + 1;
} else {
$offset = $startRank - $range;
$rowCount = ($range * 2)+1;
}
// Second query : retrieving the rank of all the students around the "focused" student
$stmt = $mysqli->prepare('SELECT id, user, score, #curRank := #curRank + 1 AS rank FROM Student, (SELECT #curRank := ?) Rank ORDER BY Score DESC, id DESC LIMIT ?, ?');
$stmt->bind_param('iii',$offset,$offset,$rowCount);
$stmt->execute();
This is probably the most optimized way to query the database to get what you want. As a bonus, you can change the range to whatever you like.

Select 2 random rows from a table from each category MySQL [duplicate]

I have a table with records and it has a row called category. I have inserted too many articles and I want to select only two articles from each category.
I tried to do something like this:
I created a view:
CREATE VIEW limitrows AS
SELECT * FROM tbl_artikujt ORDER BY articleid DESC LIMIT 2
Then I created this query:
SELECT *
FROM tbl_artikujt
WHERE
artikullid IN
(
SELECT artikullid
FROM limitrows
ORDER BY category DESC
)
ORDER BY category DESC;
But this is not working and is giving me only two records?
LIMIT only stops the number of results the statement returns. What you're looking for is generally called analytic/windowing/ranking functions - which MySQL doesn't support but you can emulate using variables:
SELECT x.*
FROM (SELECT t.*,
CASE
WHEN #category != t.category THEN #rownum := 1
ELSE #rownum := #rownum + 1
END AS rank,
#category := t.category AS var_category
FROM TBL_ARTIKUJT t
JOIN (SELECT #rownum := NULL, #category := '') r
ORDER BY t.category) x
WHERE x.rank <= 3
If you don't change SELECT x.*, the result set will include the rank and var_category values - you'll have to specify the columns you really want if this isn't the case.
SELECT * FROM (
SELECT VD.`cat_id` ,
#cat_count := IF( (#cat_id = VD.`cat_id`), #cat_count + 1, 1 ) AS 'DUMMY1',
#cat_id := VD.`cat_id` AS 'DUMMY2',
#cat_count AS 'CAT_COUNT'
FROM videos VD
INNER JOIN categories CT ON CT.`cat_id` = VD.`cat_id`
,(SELECT #cat_count :=1, #cat_id :=-1) AS CID
ORDER BY VD.`cat_id` ASC ) AS `CAT_DETAILS`
WHERE `CAT_COUNT` < 4
------- STEP FOLLOW ----------
1 . select * from ( 'FILTER_DATA_HERE' ) WHERE 'COLUMN_COUNT_CONDITION_HERE'
2. 'FILTER_DATA_HERE'
1. pass 2 variable #cat_count=1 and #cat_id = -1
2. If (#cat_id "match" column_cat_id value)
Then #cat_count = #cat_count + 1
ELSE #cat_count = 1
3. SET #cat_id = column_cat_id
3. 'COLUMN_COUNT_CONDITION_HERE'
1. count_column < count_number
4. ' EXTRA THING '
1. If you want to execute more than one statement inside " if stmt "
2. IF(condition, stmt1 , stmt2 )
1. stmt1 :- CONCAT(exp1, exp2, exp3)
2. stmt2 :- CONCAT(exp1, exp2, exp3)
3. Final "If" Stmt LIKE
1. IF ( condition , CONCAT(exp1, exp2, exp3) , CONCAT(exp1, exp2, exp3) )
share
Use group by instead of order by.

Categories