Finding Consecutive Streak and Display Count - php

I have a MySQL table that shows the following:
ID DATE FREQUENCY
-- ---------- ---------
1 2017-08-01 1
2 2017-08-02 1
3 2017-08-03 0
4 2017-08-04 1
5 2017-08-05 1
6 2017-08-06 1
I am trying to get the easiest way to group every time there are consecutive 1's on the frequency column. Then I would like to display them.
Example
2 (There are 2 consecutive 1's)
3 (There are also 3 consecutive 1's)
Thank you

This is a typical gaps-and-island problem.
You can solve it by comparing the overal rank of records to their relative ranks in groups of records having the same frequency. The difference between the ranks gives you the group each record belongs to.
The rest is just filtering and aggregating groups that have a frequency of 1.
Query:
select
min(id) min_id,
max(id) max_id,
min(date) min_date,
max(date) max_date,
count(*) streak_length
from (
select
t.*,
row_number() over(order by date) rn1,
row_number() over(partition by frequency order by date) rn2
from mytable t
) t
where frequency = 1
group by rn1 - rn2
order by min_date
Demo on DB Fiddle with your sample data:
min_id | max_id | min_date | max_date | streak_length
-----: | -----: | :--------- | :--------- | ------------:
1 | 2 | 2017-08-01 | 2017-08-02 | 2
4 | 6 | 2017-08-04 | 2017-08-06 | 3
Note: window function row_number() is available starting MySQL 8.0.

Related

Get rows above and below (neighbouring rows) a certain row, based on two criteria SQL

Say I have a table like so:
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 3 | 1 | 90 | 2021-01-12 15:38:0 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 10| 2 | 99 | 2021-01-15 10:50:38 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+
What I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). Also where level=1. This will be like a score board but only showing the user's score with the two above and two below.
So because scores can be the same, the timestamp column will also need to be used - so if two scores are equal, then the first person to get the score is shown above the other person.
E.g. say the user is id=5, I want to show
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+
Note that id=7 is below id=5
I am wondering does anyone know a way of doing this?
I have tried this below but it is not outputting what I need (it is outputting where level_id=2 and id=5, and the other rows are not in order)
((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
UNION ALL
(SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2))
order by score
If it is easier to output all rows in the table, say where level = 1, so it is a full score board.. and then do the getting a certain row and two above and below it using PHP I'd also like to know please :) ! (possibly thinking this may keep the SQL simpler)?
You can use cte and inner join as follows:
With cte as
(select t.*,
dense_rank() over (order by score) as dr
from your_table t)
Select c.*
From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
Where cu.id = 5
Ordwr by c.dr, c.timestamp
I would suggest window functions:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2;
Note: This returns 5 distinct score values, which may result in more rows depending on duplicates.
Here is a db<>fiddle.
EDIT:
If you want exactly 5 rows using the timestamp, then:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score, timestamp) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;
Note: This still treats equivalent timestamps as the same, but they seem to be unique in your data.

MySQL PHP way to count after a 'reset' record is inputted

I am having trouble trying to work out how to structure my query to allow me to 'reset' and only count records after there has been a reset.
Basic Structure
Log Table
ID | Date | Time | SectorID | personnumber
1 | 2020-02-10 | 13:23:00 | 23 | 66 (This is a row to be counted)
2 | 2020-02-10 | 13:28:00 | 38 | 66 (This is a row to be counted)
3 | 2020-02-10 | 13:30:00 | 5 | 66 (This is a 'reset' row) (SectorID 5 is a reset)
4 | 2020-02-10 | 13:38:00 | 12 | 66 (This is a row to be counted)
5 | 2020-02-10 | 13:42:00 | 56 | 66 (This is a row to be counted)
For the above, there are 2 records, then there was a reset (which is indicated by sector ID of 5), and then 2 more records (the other records can be any other number other than 5).
So I want the 'count' to return 2
The query below is what I have for counting all records without any reset function
SELECT
personnumber,
count(*) as occurrences
FROM log
WHERE personnumber IS NOT NULL
AND sectorid != 5
GROUP BY personnumber
HAVING count(*) > 1
ORDER BY occurrences DESC, personnumber
This would return
Personnumber | Occurrences
66 | 4
I hope this explains my problem sufficiently. Any help would be much appreciated.
Thanks
Jon
You could JOIN the same table by using sectorId and personnumeber in order to count all the records after this id:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber
Output:
+--------------+-------------+
| personnumber | occurrences |
+--------------+-------------+
| 66 | 2 |
+--------------+-------------+
1 row in set (0.01 sec)
If you need only the records after the last occurrence of sector 5 for particular personnumber, then you need to get the maximum id within the derived table:
SELECT
l.personnumber,
COUNT(*) as occurrences
FROM
`log` l
INNER JOIN
(
SELECT MAX(id) AS id, personnumber
FROM `log` ll
WHERE ll.sectorid = 5
GROUP BY personnumber
) AS ll ON l.personnumber = ll.personnumber
WHERE
l.personnumber IS NOT NULL
AND l.id > ll.id
GROUP BY l.personnumber
HAVING COUNT(*) > 1
ORDER BY
occurrences DESC,
l.personnumber

postgreSQL ranking query with the given user_id

I am trying to get rank of a user by their two dimension params: donation sum and total donor count.
My rank formula is: rank of [rank of donation_sum + rank of donor_count / 2]
Sample table:
donation_id | user_id | donor_id | donation_sum
-----------------------------------------------
1 | 1 | 1 | 10
2 | 1 | 2 | 5
3 | 2 | 3 | 10
4 | 3 | 1 | 50
...
As you see, some donors make donation to different users, so I used sum(donation_sum) and count(distinct(donation_id)) to get exact rankings
I am able to get list of ranking separately by donation sum and total donor count with 2 sql but my need is to get a user rank with that formula above by given user_id in postgreSQL v. 9.4
Do you have any solution for it? so I will use that sql query in a Yii2 PHP framework
Thanks
Edit:
We added donation_date to the tbl_donation and modified actual query as below:
is it true usage of where donation_date?
with list as (
select
s.runner_id, sum, count, rank_sum, rank_count,
(rank_sum+ rank_count)::float/ 2 as rank_avg,
row_number() over (order by rank_sum) as rank
from (
select *, rank() over (order by sum desc) rank_sum
from (
select runner_id, sum(donation_sum)
from tbl_donation
where donation_date >= '2015-01-01'
group by 1
) s
) s
join (
select *, rank() over (order by count desc) rank_count
from (
select runner_id, count(distinct(donator_id))
from tbl_donation
where donation_date >= '2015-01-01'
group by 1
) c
) c
using (runner_id)
)
select rank
from list
where runner_id = 251;
Make two rankings in separate subqueries:
select
s.user_id, sum, count, rank_sum, rank_count,
(rank_sum+ rank_count)::float/ 2 as rank_avg,
row_number() over (order by rank_sum) as rank
from (
select *, rank() over (order by sum desc) rank_sum
from (
select user_id, sum(donation_sum)
from donations
group by 1
) s
) s
join (
select *, rank() over (order by count desc) rank_count
from (
select user_id, count(distinct(donation_id))
from donations
group by 1
) c
) c
using (user_id);
user_id | sum | count | rank_sum | rank_count | rank_avg | rank
---------+-----+-------+----------+------------+----------+------
3 | 100 | 1 | 1 | 2 | 1.5 | 1
1 | 30 | 2 | 2 | 1 | 1.5 | 2
2 | 20 | 1 | 3 | 2 | 2.5 | 3
(3 rows)
If you want to select rank for a single user_id use with query, e.g.:
with list as (
-- place here the above query
)
select rank
from list
where user_id = 2;

removing duplicate rows if one of the columns are the same Sql-server

i have a query which links table one two table two and gets the last comment for the result in table one, which works but when there are multiple records from table one with the same ID for example it shows that result again here is an example
Query results
ID | Machine | description | createdtime | product code | work order | qty | comment | Reson
No
129 |1 | A name | 2015-01-08 07:38:41.427 | A code | 12/14/0038 | 4000 | comment | Reason
143 |1 | A name | 2015-01-08 13:30:39.403 | A code | 12/14/0038 | 4000 | comment | Reson
130 |4 | A name | 2015-01-08 07:38:46.540 | A code | 12/14/0045 | 12000 | comment | Reason
131 |5 | A name | 2015-01-08 07:38:50.243 | A code | 01/15/0001 | 4000 | comment | Reason
As you can see here there are two records with the machine number 1, one created in the morning and one in the after noon, however i only want the latest one to show up, Here is my query.
SELECT Qualitycontrol.ID, Qualitycontrol.MachineNo, Qualitycontrol.Description, Qualitycontrol.CreatedTime, Qualitycontrol.ProductCode, Qualitycontrol.WorkOrder,
Qualitycontrol.Quantity, Qc.Comment, Qc.Reason
FROM Qualitycontrol
OUTER APPLY (
SELECT TOP 1 *
FROM QualityControl_Comments
WHERE Qualitycontrol.ID = QCUID
ORDER BY Qualitycontrol.ID DESC -- whatevet defines order in QualityControl_Comments
) AS Qc
WHERE (Qualitycontrol.CreatedTime BETWEEN CAST(GETDATE() AS DATE) AND DATEADD(DAY, 1, CAST(GETDATE() AS DATE)))
ORDER BY Qualitycontrol.MachineNo
Perhaps something like this would work.
SELECT Qualitycontrol.ID, Qualitycontrol.MachineNo, Qualitycontrol.Description, Qualitycontrol.CreatedTime, Qualitycontrol.ProductCode, Qualitycontrol.WorkOrder,
Qualitycontrol.Quantity, Qc.Comment, Qc.Reason
FROM Qualitycontrol
OUTER APPLY (
SELECT TOP 1 *
FROM QualityControl_Comments
WHERE Qualitycontrol.ID = QualityControl_Comments.QCUID
ORDER BY Qualitycontrol.ID DESC -- whatevet defines order in QualityControl_Comments
) AS Qc
INNER JOIN
(
SELECT Qualitycontrol.MachineNo, MAX(Qualitycontrol.ID) MID
FROM Qualitycontrol
GROUP BY Qualitycontrol.MachineNo
) UNQ ON UNQ.MID = Qualitycontrol.ID
WHERE (Qualitycontrol.CreatedTime BETWEEN CAST(GETDATE() AS DATE) AND DATEADD(DAY, 1, CAST(GETDATE() AS DATE)))
ORDER BY Qualitycontrol.MachineNo
You can use somthing like this.
Using row_number you can partition your data by machnine id and sort the numer by date. Than you can just use a simple where clouse to select what you want
;WITH CTE
as
(
SELECT Qualitycontrol.ID, Qualitycontrol.MachineNo, Qualitycontrol.Description, Qualitycontrol.CreatedTime, Qualitycontrol.ProductCode, Qualitycontrol.WorkOrder,
Qualitycontrol.Quantity, Qc.Comment, Qc.Reason
FROM Qualitycontrol
OUTER APPLY (
SELECT TOP 1 *
FROM QualityControl_Comments
WHERE Qualitycontrol.ID = QCUID
) AS Qc
WHERE (Qualitycontrol.CreatedTime BETWEEN CAST(GETDATE() AS DATE) AND DATEADD(DAY, 1, CAST(GETDATE() AS DATE)))
), CTE2 as
(
select *, row_number() over(partition by MachineNo order by CreatedTime desc) as 'row_index' from cte
)
select * from cte2
where row_index = 1
order by MachineNo

SQL query to group by and order by timestamp

Hey guys, I've tried to get this right but I can't, maybe you can point me in the right direction
I have 3 columns, 'url_id', 'timestamp' and 'o'. I need to group by 'url_id' and sort by the most current timestamp.
table "example"
timestamp | url_id | o
----------------------------
2000 | 1 | 50
2007 | 1 | 70
2011 | 1 | 90
2001 | 2 | 20
2006 | 2 | 50
2009 | 2 | 40
2011 | 2 | 10
'o' is the value at the end I want. I was trying to do this with a subquery but kept getting the oldest value (tried order by, and had no luck).
What am I doing wrong? Is what I'm looking for actually require a subquery?
SELECT url_id
, MAX(timestamp) AS currentTS
FROM yourTable
GROUP BY url_id
ORDER BY currentTS DESC
Aftre you last explanation, I think you need to JOIN the above query to your original table, like this:
SELECT y.timestamp
, y.url_id
, y.o
FROM yourTable y
JOIN
( SELECT url_id
, MAX(timestamp) AS currentTS
FROM yourTable
GROUP BY url_id
) AS grp
ON grp.url_id = y.url_id
AND grp.currentTS = y.timestamp
ORDER BY y.timestamp DESC
Note: if there are two (or more) rows with same url_id and same timestamp, they'll both (or all) appear at the results.

Categories