How to identify entities which have repeated values in sequence using MySQL? - mysql

I have a table:
UNIT_ID | YEAR | MONTH | VAR
---------+------+-------+------
1 | 2015 | 1 | 0
1 | 2015 | 2 | 0
1 | 2015 | 3 | 0
2 | 2015 | 1 | 10
2 | 2015 | 2 | 10
2 | 2015 | 3 | 10
1 | 2015 | 4 | 5
1 | 2015 | 5 | 5
1 | 2015 | 6 | 5
2 | 2015 | 4 | 10
2 | 2015 | 5 | 3
2 | 2015 | 6 | 3
3 | 2016 | 1 | 3
3 | 2016 | 2 | 3
3 | 2016 | 3 | 3
3 | 2016 | 4 | 3
2 | 2016 | 6 | 0
2 | 2016 | 7 | 0
2 | 2016 | 8 | 0
I want to know which units have a sequence bigger than 3 zeros or bigger than 4 values repeated. Grouped by year. So, my result table would be like this:
1 | 2015 | true
2 | 2015 | true
2 | 2016 | true
I have found this solution but unfortunately I could not adapt to my case. I need also that the query is in MySQL.

You could just join them 4 times. Last join is a left join to allow the case for 3 0's.
select a.unit_id, a.year, 'true'
from tbl a
join tbl b on a.unit_id = b.unit_id and a.year = b.year and a.month+1 = b.month and a.var = b.var
join tbl c on b.unit_id = c.unit_id and b.year = c.year and b.month+1 = c.month and b.var = c.var
left join tbl d on c.unit_id = d.unit_id and c.year = d.year and c.month+1 = d.month and c.var = d.var
where a.var = 0 or d.var is not null;
Faster and more generic solution. It scans the table only once, and uses user defined variables (#pu for previous unit_id, #py for previous year, etc) to remember the previous row:
select distinct unit_id, year
from (
select unit_id, `year`, `month`, `var`,
if(unit_id=#pu and `year`=#py and `month`=#pm+1 and `var`=#pv, #i:=#i+1, #i:=1)*
if(#pu:=unit_id,1,1)*if(#py:=`year`,1,1)*if(#pm:=`month`,1,1)*if(#pv:=`var`,1,1) as c
from table1 a
join (select #pu:=null, #py:=null, #pm:=null, #pv:=null, #i:=1) b
order by unit_id, `year`, `month`, `var`) a
group by unit_id, `year`, `var`
having (`var` = 0 and max(c) >= 3) or (`var` != 0 and max(c) >= 4);
fiddle

Related

Select N previous and M next items around current item id

I have a table with photos
id | year| comm_count
0 2015 1
1 2016 2
2 2017 5
3 2018 7
4 2019 1
5 2020 9
6 2021 1
7 2022 1
I select photo with a given id, somewhere in the middle of all photos. For example like this:
SELECT *
FROM photo
WHERE year > '2017'
ORDER BY comm_count DESC, year DESC
This will give me:
5,3,7,6,4
This gives me list of all photos. Now, I write this list on my web, but user can click on one certain photo. After that, detailed page opens. But from this detailed page, I would like to be able to go to "next" M and "previous" N photos. This means, I need to select neihboring IDs based on currenttly selected one. How can this be done?
Now I select id = 7 and I want neighbors to be:
prev: 5,3 and next: 6,4. How can this be selected?
SqlFiddle - http://sqlfiddle.com/#!9/4f3f42/4/0
I cannot run the same query and filter results in PHP, because query can contain LIMITS (eg. with LIMIT 2, 4 I still the need correct neighbors)
Once you have the year and comm_count values for the selected row with id=7, you can make two simple queries:
SELECT * FROM photo
WHERE year > 2017 AND (comm_count = 1 AND year <= 2022 OR comm_count < 1)
ORDER BY comm_count DESC, year DESC LIMIT 3 OFFSET 1
+----+------+------------+
| id | year | comm_count |
+----+------+------------+
| 6 | 2021 | 1 |
| 4 | 2019 | 1 |
+----+------+------------+
SELECT * FROM photo
WHERE year > 2017 AND (comm_count = 1 AND year >= 2022 OR comm_count > 1)
ORDER BY comm_count ASC, year ASC LIMIT 3 OFFSET 1;
+----+------+------------+
| id | year | comm_count |
+----+------+------------+
| 3 | 2018 | 7 |
| 5 | 2020 | 9 |
+----+------+------------+
If you use MySQL 8.0, you can use the LAG() and LEAD() functions.
SELECT id, year,
LAG(id, 1) OVER w AS next,
LAG(id, 2) OVER w AS next_next,
LEAD(id, 1) OVER w AS prev,
LEAD(id, 2) OVER w AS prev_prev
FROM photo
WHERE year > 2017
WINDOW w AS (ORDER BY comm_count DESC, year DESC)
+----+------+------+-----------+------+-----------+
| id | year | next | next_next | prev | prev_prev |
+----+------+------+-----------+------+-----------+
| 5 | 2020 | NULL | NULL | 3 | 7 |
| 3 | 2018 | 5 | NULL | 7 | 6 |
| 7 | 2022 | 3 | 5 | 6 | 4 |
| 6 | 2021 | 7 | 3 | 4 | NULL |
| 4 | 2019 | 6 | 7 | NULL | NULL |
+----+------+------+-----------+------+-----------+

Sum childrens in two tables of a table

I Have 3 tables:
a (id,date,ckey) b(id,a.ckey,hht,hha) c(id,a.ckey,date_ini,date_fin)
where B keeps all the activities to be done and their respective hours in 2 places (hht,hha), while c saves the activities carried out with its initial and final date (to determine the hours executed the dates are subtracted).
Now I need to know, for each record in A how many hours you have assigned (B) and how many hours you have completed (C)
actually i have this:
a:
+----------+----------+------------+
| id | date | ckey |
+----------+----------+------------+
| 1 |2018-01-20| 18 |
|----------|----------|------------|
b:
+----------+----------+--------+--------+
| id | a.ckey | hht | hht |
+----------+----------+--------+--------+
| 1 | 18 | 2 | 3 |
| 2 | 18 | 2 | 5 |
| 3 | 18 | 0 | 7 |
+----------+----------+--------+--------+
c:
+----------+----------+----------------------+----------------------+
| id | a.ckey | date_ini | date_fin |
+----------+----------+----------------------+----------------------+
| 1 | 18 | 2019-01-23 13:30:00 | 2019-01-23 14:00:00 |
| 1 | 18 | 2019-01-23 14:00:00 | 2019-01-23 14:30:00 |
+----------+----------+----------------------+----------------------+
I need this:
+----------+----------+----------------------+----------------------+
| id | a.ckey | hours | hours2 |
+----------+----------+----------------------+----------------------+
| 1 | 18 | 19 | 1 |
+----------+----------+----------------------+----------------------+
I get this:
+----------+----------+----------------------+----------------------+
| id | a.ckey | hours | hours2 |
+----------+----------+----------------------+----------------------+
| 1 | 18 | 38 | 37.5 |
+----------+----------+----------------------+----------------------+
This is my query:
SELECT
(b.hht+b.hha) AS hours,
(SUM(b.hht+b.hha) -
FORMAT(IFNULL((TIMESTAMPDIFF(MINUTE, c.date_ini, c.date_fin)/60),0),2)) AS hours2
FROM a
LEFT JOIN b ON a.key=b.akey
INNER JOIN c ON a.key=c.akey
GROUP a.ckey
Because you have multiple rows in tables b and c for each value of ckey you need to do the aggregation within a subquery, otherwise you get duplicated rows leading to incorrect sums.
SELECT a.id, a.key, b.hours, FORMAT(c.minutes/60, 2) AS hours2
FROM a
LEFT JOIN (SELECT akey, SUM(hht+hha) AS hours
FROM b
GROUP BY akey) b ON b.akey = a.key
LEFT JOIN (SELECT akey, SUM(TIMESTAMPDIFF(MINUTE, date_ini, date_fin)) AS minutes
FROM c
GROUP BY akey) c ON c.akey = a.key
ORDER BY a.id
Output:
id key hours hours2
1 18 19 1.00
Demo on SQLFiddle
You're doing an m-to-n-join, try UNION ALL instead:
select ckey, sum(hours) as hours, sum(hours) - sum(hours2) as hours2
from
(
SELECT ckey, (b.hht+b.hha) AS hours, NULL as hours2
FROM b
UNION ALL
SELECT ckey, NULL AS hours,
FORMAT(IFNULL((TIMESTAMPDIFF(MINUTE, c.date_ini, c.date_fin)/60),0),2)) as hours2
FROM c
) as dt
group by ckey
If you actually need columns from table a put this Select in a Derived Table and join to it.
please check this
SELECT
(SELECT SUM(hha + hht) from b where b.ckey = a.ckey) hours,
FORMAT((SELECT SUM(TIMESTAMPDIFF(MINUTE, c.date_ini, c.date_fin)/60) from c where c.ckey = a.ckey),2) as hours2
FROM A
Fiddle

Subquery mysql with where clause

Hello, Im stuck with mysql subquery, this is the table I have
table detail_order
==============================
id_detail | id_order | id_toko
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
table ket_detail
==================================
id_ket | id_detail | id_size | qty
1 | 1 | 7 | 3
2 | 1 | 9 | 1
3 | 1 | 5 | 2
4 | 2 | 7 | 8
table size
=================================
id_size | size | id_color | stock
7 | 40 | 6 | 30
9 | 42 | 6 | 20
5 | 39 | 5 | 30
table color
==========================
id_color | color
6 | green
5 | red
Im trying in subquery to show qty on table ket_detail with where clause, but when Im try it subquery return more than one row.
this is my query
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
(SELECT ket.qty FROM ket_detail AS ket, t_size AS u
WHERE u.id_size=ket.id_size AND u.size = 40) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail
but when I change size to 39 the data like this
id_detail | tot_order | tot_color | size_40
============================================
1 | 6 | 2 | 2
2 | 8 | 1 | 2
3 | NULL | 0 | 2
4 | NULL | 0 | 2
what do I want is the data like this
id_detail | tot_order | tot_color | size_40
============================================
1 | 6 | 2 | 3
2 | 8 | 1 | 8
3 | NULL | 0 | NULL
4 | NULL | 0 | NULL
You don't need a subquery to get the size = 39 or size = 40 data. You can use conditional aggregation instead:
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
SUM(CASE
WHEN u.size = 39 THEN tk.qty
ELSE 0
END) AS size_39,
SUM(CASE
WHEN u.size = 40 THEN tk.qty
ELSE 0
END) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail;
Demo here
The proper way to do it with a subquery is:
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
(SELECT SUM(ket.qty)
FROM ket_detail AS ket
JOIN t_size AS u ON u.id_size=ket.id_size
WHERE ket.id_detail = dt.id_detail AND u.size = 40) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail;
Demo here
If you need to select the total quantity for more than one sizes, then you have to repeat the subquery for each required size. Hence, I think, the first query provides a solution that is cleaner, easier to extend and more efficient.

Count total number of occurrence of two consecutive values in a table

My table structure
+----+--------+
| id | status |
+----+--------+
| 1 | 10 |
| 2 | 21 |
| 3 | 22 |
| 4 | 29 |
| 5 | 30 |
| 6 | 32 |
| 7 | 33 |
| 8 | 21 |
| 9 | 22 |
| 10 | 23 |
| 11 | 21 |
| 12 | 22 |
| 13 | 23 |
+----+--------+
I want to count total number of times when status 22 comes just after status 21.
In this case the query should return 3.
sql fiddle
Just use a Self Join with Conditional Aggregate
SELECT Sum(CASE WHEN a.status = 22 AND b.status = 21 THEN 1 END) As Stat_Count
FROM testTable a
LEFT OUTER JOIN testTable b
ON a.id = b.id + 1
SQLFIDDLE DEMO
If you can have gaps in your id's you can use a subquery to check whether the previous status of a 22 row is 21
select count(*)
from testtable a
where a.status = 22 and (select status from testtable b
where b.id < a.id order by id desc limit 1) = 21
http://sqlfiddle.com/#!2/9d567/2
Another way gets all id's of previous rows of rows with a status of 22 in derived table and then joins the ids to count how many have a status of 21
select count(*) from (
select max(b.id) max_b_id
from testtable a join testtable b on b.id < a.id
where a.status = 22
group by a.id
) t1 join testtable a on a.id = t1.max_b_id
where a.status = 21
I have tried to solve it in php
$q="";
$q= mysqli_query("select *from testTable");
while($r=mysqli_fetch_assoc($q)){
$rows[]=$r;
}
$success=0;
for ($i=0;$i<count($rows);$i++){
if($rows[$i]['status']==21 and $rows[$i+1]['status']==22 ){
$success+=1;
}
}
echo $success;

Getting UNIX TIME with number

MySQL Query:
SELECT c.day,
COUNT(site_id)
FROM calendar c
LEFT JOIN
(
SELECT *
FROM visitors
WHERE site_id = 16
) d ON DAYOFMONTH(d.created) = c.day
WHERE c.day BETWEEN DAYOFMONTH('2012-10-01') AND DAYOFMONTH('2012-10-31')
GROUP BY c.day
ORDER BY c.day
My Tables
Calendar
id | day
---------
1 | 1
2 | 2
3 | 3
...
31 | 31
Visitors
id | site_id | created
-----------------------------------
1 | 16 | 2012-10-18 11:14:39
2 | 16 | 2012-10-18 11:15:17
3 | 11 | 2012-10-18 11:49:14
4 | 11 | 2012-10-18 11:49:43
5 | 16 | 2012-10-19 11:54:37
6 | 1 | 2012-10-19 05:56:31
7 | 2 | 2012-10-19 05:57:56
I used the above query to retrieve a daily result of visits to a site. The query solved my question here.
Results:
day | COUNT(*)
-------------
1 | 0
2 | 0
3 | 0
....
18 | 2
19 | 1
...
31 | 0
Although, now, I am having problems retrieving UNIX_TIMESTAMP from the day which I need for graphing purposes.
How do I retrieve it from the c.day in the query?
Edited:
SELECT
UNIX_TIMESTAMP('2012-10-01' + INTERVAL c.day - 1 DAY) unix_ts_day,
COUNT(v.site_id)
FROM
calendar c
LEFT JOIN (
SELECT * FROM visitors
WHERE site_id = 16 AND DATE(created) BETWEEN '2012-10-01' AND '2012-10-31'
) v
ON DAYOFMONTH(v.created) = c.day
GROUP BY
unix_ts_day