Find missing values in an alphanumeric sequence - mysql

I want to identify missing values in an alphanumeric sequence.
The table is defined as such:
CREATE TABLE `seqtest` (
`ID` int(11) NOT NULL,
`PoleNo` text,
`Pre` char(1) DEFAULT NULL,
`Num` int(3) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The data is as shown below and will always be one letter (A-Z) followed by three numbers from 000 to 999.
| PoleNo | Pre | Num |
|------------------------|
| A000 | A | 000 |
| A001 | A | 001 |
| A002 | A | 002 |
| A004 | A | 003 |
| **** | * | *** |
| A998 | A | 998 |
| A999 | A | 999 |
| B000 | B | 000 |
| B001 | B | 001 |
| B002 | B | 002 |
| **** | * | *** |
| B998 | B | 998 |
| B999 | B | 999 |
| C000 | C | 000 |
| C001 | C | 001 |
| C005 | C | 005 |
| C006 | C | 006 |
|------------------------|
I want the query to find that, for example, C002, C003 AND C004 are missing as shown below.
| Pre | start | stop |
| C | 2 | 4 |
|----------------------|
Im using the following:
SELECT l.Pre, l.Num + 1 as start, min(fr.Num) - 1 as stop
FROM seqtest as l
LEFT OUTER JOIN seqtest as r ON l.Num = r.Num - 1 AND l.Pre = r.Pre
LEFT OUTER JOIN seqtest as fr ON l.Num < fr.Num AND l.Pre = fr.Pre
WHERE r.Num is null AND l.Num < 999
GROUP BY l.Pre, l.Num, r.Num
which is based on this.
It gives me the range that is missing and works well except for one case...when 'Pre' changes from one letter to the next.
IE With the following data:
| PoleNo | Pre | Num |
|------------------------|
| B995 | B | 995 |
| B996 | B | 996 |
| B997 | B | 997 |
| C003 | C | 003 |
| C004 | C | 004 |
| C005 | C | 005 |
| C006 | C | 006 |
|------------------------|
Id like to be able to return this:
| Pre | start | stop |
| B | 998 | 999 |
| C | 0 | 2 |
|----------------------|
Is this possible? Im using the Pre and Num fields which are simply the PoleNo field broken up...but if anyone sees a way to do it just using the PoleNo field, that would work as well.

This is much easier in MySQL 8+, because you have lead(). But, you can do what you want as:
select st.pre,
(st.num + 1) as start,
(st.next_num - 1) as stop
from (select st.*,
(select st2.num
from seqtest st2
where st2.pre = st.pre and
st2.num > st.num
order by st2.num asc
limit 1
) as next_num
from seqtest st
) st
where next_num <> num + 1;
EDIT:
This gets the ranges at the beginning and end as well:
select st.pre,
(st.num + 1) as start,
(st.next_num - 1) as stop
from (select st.pre, num,
coalesce( (select st2.num
from seqtest st2
where st2.pre = st.pre and
st2.num > st.num
order by st2.num asc
limit 1), 1000
) as next_num
from seqtest st
union
select st.pre, 0 as num, min(st.num) as next_num
from seqtest st
group by st.pre
) st
where next_num <> num + 1
order by pre, start;
Here is a db<>fiddle.

Related

How to count and join two tables on mySQL from different database?

I have two databases. Those are core and push.
This is subs table from core database:
+----+---------+----+----+-----+
| id | service | ad | op | act |
+----+---------+----+----+-----+
| 1 | CHO | 96 | x | 1 |
| 2 | CHO | 98 | x | 1 |
| 3 | DANG | 96 | x | 1 |
| 4 | HOORAY | 96 | x | 1 |
| 5 | CHO | 98 | x | 1 |
| 6 | DANG | 96 | x | 1 |
+----+---------+----+----+-----+
and this is the buff table from push database:
+----+---------+-------+
| id | service | sub |
+----+---------+-------+
| 1 | DANG | daily |
| 2 | HOORAY | daily |
| 3 | DANG | daily |
+----+---------+-------+
i want to count subs and buff. i made this query for subs:
select service, ad, op, count(1) as sub where act=1 group by service,ad,op;
RESULT:
+---------+----+----+-----+
| service | ad | op | sub |
+---------+----+----+-----+
| CHOO | 96 | x | 1 |
| CHOO | 98 | x | 2 |
| HOORAY | 96 | x | 1 |
| DANG | 96 | x | 2 |
+---------+----+----+-----+
and this is the query for buff:
select service, count(1) as pushed
from buff a where sub = 'daily'
group by service;
RESULT:
+---------+---------+
| service | pushed |
+---------+---------+
| HOORAY | 1 |
| DANG | 2 |
+---------+---------+
So, my question is how to join those tables from two different databases?
here is the result that i expect:
+---------+----+----+-----+--------+
| service | ad | op | sub | pushed |
+---------+----+----+-----+--------+
| CHOO | 96 | x | 1 | 0 |
| CHOO | 98 | x | 2 | 0 |
| HOORAY | 96 | x | 1 | 1 |
| DANG | 96 | x | 2 | 2 |
+---------+----+----+-----+--------+
One option is to join together your two current subqueries. I have used a full outer join below, because we want to handle the possibity that a service may appear in only the subs or buff table, but not both.
SELECT t1.service,
t1.ad,
t1.op,
t1.sub,
COALESCE(t2.pushed, 0) AS pushed
FROM
(
SELECT service, ad, op, COUNT(*) AS sub
FROM subs
WHERE act = 1
GROUP BY service,ad,op
) t1
LEFT JOIN
(
SELECT service, COUNT(1) AS pushed
FROM buff a
WHERE sub = 'daily'
GROUP BY service
) t2
ON t1.service = t2.service
UNION
SELECT t2.service,
COALESCE(t1.ad, -1),
COALESCE(t1.op, -1),
COALESCE(t1.sub, -1),
t2.pushed
FROM
(
SELECT service, ad, op, COUNT(*) AS sub
FROM subs
WHERE act = 1
GROUP BY service,ad,op
) t1
RIGHT JOIN
(
SELECT service, COUNT(1) AS pushed
FROM buff a
WHERE sub = 'daily'
GROUP BY service
) t2
ON t1.service = t2.service
use database name as table name prefix with dot, I don't check the below sentence, it only show the idea:
SELECT service, ad, op, count(1) AS sub FROM core.subs AS t1
LEFT JOIN push.buff AS t2 ON t1.service=t2.service WHERE t1.act=1
AND sub = 'daily' GROUP BY t1.service,t1.ad,t1.op

How to simplify sequence data in MySQL?

I have asked a question on How to get temporal sequence by mysql before. There I want to get a simplified sequence with the latest date.
Now I want to get the start date and end date of each sequence. Suppose that the table is still like this:
ID DATE STATUS
1 0106 A
1 0107 A
1 0112 A
1 0130 B
1 0201 A
2 0102 C
2 0107 C
and I want to get the result like this:
ID START_DATE END_DATE STATUS
1 0106 0112 A
1 0130 0130 B
1 0201 0201 A
2 0102 0107 C
I tried to adapt the answer of former question to it though but failed. I am wondering how I can realize it.
Given this
SELECT * FROM T;
+------+------+--------+
| ID | DATE | STATUS |
+------+------+--------+
| 1 | 106 | A |
| 1 | 107 | A |
| 1 | 112 | A |
| 1 | 130 | B |
| 1 | 201 | A |
| 2 | 102 | C |
| 2 | 107 | C |
+------+------+--------+
It's quite straightforward to allocate a block and seqno using this
SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS,#RN:=#RN+1,#RN:=#RN) RNBLOCK ,
IF(STATUS = #PREVS,#RN2:=#RN2+1,#RN2:=1) RNSEQ ,
#PREVS:=STATUS PSTATUS
FROM (SELECT #RN:=1) RNBLOCK, (SELECT #RN2:=0) RNSEQ,(SELECT #PREVS:=NULL) P, T
To give this
+------+------+--------+---------+-------+---------+
| ID | DATE | STATUS | RNBLOCK | RNSEQ | PSTATUS |
+------+------+--------+---------+-------+---------+
| 1 | 106 | A | 1 | 1 | A |
| 1 | 107 | A | 1 | 2 | A |
| 1 | 112 | A | 1 | 3 | A |
| 1 | 130 | B | 2 | 1 | B |
| 1 | 201 | A | 3 | 1 | A |
| 2 | 102 | C | 4 | 1 | C |
| 2 | 107 | C | 4 | 2 | C |
+------+------+--------+---------+-------+---------+
So now we have isolated the blocks and know the min seq no (1) and the max seqno and we can push them into a table
drop table t1;
CREATE TABLE `t1` (
`ID` INT(11) NULL DEFAULT NULL,
`DATE` INT(11) NULL DEFAULT NULL,
`STATUS` VARCHAR(1) NULL DEFAULT NULL,
`rnblock` int null default null,
`rnseq` int null default null,
`pstatus` VARCHAR(1) NULL DEFAULT NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
;
and create a simple min max join
SELECT T2.ID,T2.DATE,T3.DATE,T2.STATUS FROM
(
SELECT T1.RNBLOCK,MAX(T1.RNSEQ) MAXSEQ
FROM T1
GROUP BY RNBLOCK
) S
JOIN T1 T2 ON T2.RNBLOCK = S.RNBLOCK AND T2.RNSEQ = 1
JOIN T1 T3 ON T3.RNBLOCK = S.RNBLOCK AND T3.RNSEQ = S.MAXSEQ
to get this
+------+------+------+--------+
| ID | DATE | DATE | STATUS |
+------+------+------+--------+
| 1 | 106 | 112 | A |
| 1 | 130 | 130 | B |
| 1 | 201 | 201 | A |
| 2 | 102 | 107 | C |
+------+------+------+--------+
The downside is that you have to create a table to make it work.
Or you could use this rather ungainly code which does not use an intermediate table
select u.id,u.date,v.date,u.status from
(
select s.rnblock,s.status,min(s.rnseq) minseq,max(s.rnseq) maxseq
from
(
SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS,#RN:=#RN+1,#RN:=#RN) RNBLOCK ,
IF(STATUS = #PREVS,#RN2:=#RN2+1,#RN2:=1) RNSEQ ,
#PREVS:=STATUS PSTATUS
FROM (SELECT #RN:=1) RNBLOCK, (SELECT #RN2:=0) RNSEQ,(SELECT #PREVS:=NULL) P, T
) s
group by s.rnblock,s.status
) T
join
(SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS2,#RN3:=#RN3+1,#RN3:=#RN3) RNBLOCK ,
IF(STATUS = #PREVS2,#RN4:=#RN4+1,#RN4:=1) RNSEQ ,
#PREVS2:=STATUS PSTATUS
FROM (SELECT #RN3:=1) RNBLOCK, (SELECT #RN4:=0) RNSEQ,(SELECT #PREVS2:=NULL) P, T
) u on u.rnblock = t.rnblock and u.rnseq = minseq
join
(SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS3,#RN5:=#RN5+1,#RN5:=#RN5) RNBLOCK ,
IF(STATUS = #PREVS3,#RN6:=#RN6+1,#RN6:=1) RNSEQ ,
#PREVS3:=STATUS PSTATUS
FROM (SELECT #RN5:=1) RNBLOCK, (SELECT #RN6:=0) RNSEQ,(SELECT #PREVS3:=NULL) P, T
) v on v.rnblock = t.rnblock and v.rnseq = maxseq
Oh I have just thought out a method though it seems very stupid. The method is just executing the code from How to get temporal sequence by mysql twice(by different order) and join these two tables.

SQL server JOIN query not working

Hi I have doubt in sql server
dept
+---------+--------+
| deptkey | deptno |
+---------+--------+
| 1 | 100 |
| 2 | 101 |
| 3 | -1 |
+---------+--------+
loc
+--------+-------+
| lockey | locid |
+--------+-------+
| 1 | 200 |
| 2 | 201 |
| 3 | -1 |
+--------+-------+
trans
+----+--------+-------+------+
| id | deptno | locid | Name |
+----+--------+-------+------+
| 1 | 100 | 201 | abc |
| 2 | 101 | 203 | def |
| 3 | 103 | 200 | rav |
| 4 | 105 | 204 | jai |
| 1 | 101 | 200 | kal |
| 4 | 100 | 206 | lo |
+----+--------+-------+------+
here tran deptno= dept.deptno then corresponding key values bring if not match then we need to unmatched deptno assign -1 and corresponding key need to retrive
similar tran locid=loc.locid
based on above tables I want output like below
+----+------+---------+--------+
| id | Name | deptkey | lockey |
+----+------+---------+--------+
| 1 | abc | 1 | 2 |
| 2 | def | 2 | 3 |
| 3 | rav | 3 | 1 |
| 4 | jai | 3 | 3 |
| 1 | kal | 2 | 1 |
| 4 | lo | 1 | 3 |
+----+------+---------+--------+
I tried like below query
SELECT a.[id],a.name ,b.deptkey,c.lockey
FROM [trans] a left join dept b on a.deptno=b.deptno
left join loc c on a.locid=c.locid
above query not given expected result can you please tell me how to write query to achive this task in sql server
SELECT a.[id],a.name ,
(CASE WHEN b.deptkey IS NULL THEN (select deptkey from DEPT WHERE DeptNo = -1)
ELSE b.deptkey END) AS 'deptkey',
(CASE WHEN c.lockey IS NULL THEN (select LocKey from LOC WHERE LocId = -1)
ELSE c.lockey END) AS 'lockey '
FROM [trans] a left join dept b on a.deptno=b.deptno
left join loc c on a.locid=c.locid
http://www.sqlfiddle.com/#!3/389463/2
SELECT a.[id],a.name ,b.deptkey,c.lockey
FROM [trans] a left join dept b on isnull(a.deptno,-1)=isnull(b.deptno,-1)
left join loc c on a.locid=c.locid
try with this...
SELECT
a.[id]
, a.name
, ISNULL(b.deptkey,-1) AS [deptkey]
, ISNULL(b.lockey,-1) AS [lockey]
FROM [trans] a
left join dept b
on a.deptno = b.deptno
left join loc c
on a.locid = c.locid
When the value is not found ISNULL, change the result to -1 instead of NULL. You can just change the -1 with any default value you prefer as unmatched.
OR if you want a query driven default value (get the last record as the default value). You can change your script as presented below.
SELECT
a.[id]
, a.name
, ISNULL(b.deptkey,(SELECT TOP 1 deptno from dept ORDER BY deptkey DESC)) AS [deptkey]
, ISNULL(b.lockey,(SELECT TOP 1 locid from loc ORDER BY lockey DESC)) AS [lockey]
FROM [trans] a
left join dept b
on a.deptno = b.deptno
left join loc c
on a.locid = c.locid

MySQL query for row sequences

Is there a way to express the following query in MySQL:
Let a table have types of rows A, B, C, D, E ... Z and each row represents an event. Find the timestamps and ids of all event sequences A, .. , B, ... , C ordered by timestamp so that timestamp(C) - timestamp(A) < Thresh.
For example consider the following table
| type | timestamp | id |
|------+-----------+-----|
| Z | 19:00 | 20 |
| A | 19:01 | 21 |
| | | |
| . | ... | .. |
| | | |
| A | 20:13 | 50 | *
| B | 20:14 | 51 | *
| D | 20:17 | 52 |
| C | 20:19 | 53 | *
| | | |
| . | ... | .. |
| | | |
| A | 22:13 | 80 | *
| D | 22:14 | 81 |
| B | 22:15 | 82 | *
| K | 22:16 | 83 |
| J | 22:17 | 84 |
| C | 22:19 | 85 | *
| | | |
| . | ... | .. |
| | | |
| A | 23:13 | 100 |
| B | 23:14 | 101 |
| C | 23:50 | 102 |
The rows that the query with Thresh = 10mins should yield something along the lines of:
| A_id | B_id | C_id |
|------+------+------|
| 50 | 51 | 53 |
| 80 | 82 | 85 |
See how the last triplet of A, B and C is not present. The time distance between the last A event and the last C event is more that Thresh.
I suspect that the answer would be something along the lines of "MySQL is not the right tool if you need to ask this kind of question". In that case the followup is, which database is a good candidate to handle this kind of task?
Edit: provided an example
I think you can express this using a self join:
SELECT A.id as A_id, B.id as B_id, C.id as C_id
FROM (
SELECT *
FROM the_table
WHERE type = 'A'
) A
JOIN (
SELECT *
FROM the_table
WHERE type = 'B'
) B
JOIN (
SELECT *
FROM the_table
WHERE type = 'C'
) C ON (
(C.timestamp - A.timestamp) < 10 -- threshold here
AND B.timestamp BETWEEN A.timestamp AND C.timestamp
)

Select most recent MAX() and MIN() - WebSQL

i'm build an exercises web app and i'm working with two tables like this:
Table 1: weekly_stats
| id | code | type | date | time |
|----|--------------|--------------------|------------|----------|
| 1 | CC | 1 | 2015-02-04 | 19:15:00 |
| 2 | CC | 2 | 2015-01-28 | 19:15:00 |
| 3 | CPC | 1 | 2015-01-26 | 19:15:00 |
| 4 | CPC | 1 | 2015-01-25 | 19:15:00 |
| 5 | CP | 1 | 2015-01-24 | 19:15:00 |
| 6 | CC | 1 | 2015-01-23 | 19:15:00 |
| .. | ... | ... | ... | ... |
Table 2: global_stats
| id | exercise_number |correct | wrong |
|----|-----------------|--------|-----------|
| 1 | 138 | 1 | 0 |
| 2 | 246 | 1 | 0 |
| 3 | 988 | 1 | 10 |
| 4 | 13 | 5 | 0 |
| 5 | 5 | 4 | 7 |
| 6 | 5 | 4 | 7 |
| .. | ... | ... | ... |
What i would like is to get MAX(correct-wrong) and MIN(correct-wrong) and now i'm working with this query:
SELECT
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MAX(correct - wrong) from global_stats)
UNION
SELECT
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MIN(correct - wrong) from global_stats);
This query is working good, except for one thing: when "WHERE correct - wrong = (SELECT MIN(correct - wrong)[...]" selects more than one row, the row selected is the first but i would like to have returned the most recent (in other words: ordered by datetime(date, time)). Is it possible?
Thanks!
I think you can solve it like this:
SELECT * FROM (
SELECT
1 as sort_column,
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MAX(correct - wrong) from global_stats)
ORDER BY date DESC, time DESC
LIMIT 1 ) as a
UNION
SELECT * FROM (
SELECT
2 as sort_column,
exercise_number,
date,
time
FROM weekly_stats AS w JOIN global_stats AS g
ON w.id=g.id
WHERE correct - wrong = (SELECT MIN(correct - wrong) from global_stats)
ORDER BY date DESC, time DESC
LIMIT 1) as b
ORDER BY sort_column;
Here is the documentation about how UNION works.