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.
Related
I want to extract by limiting the number of rows that meet the condition to n in the result row
There are tow tables
tb_info
--------------------------
id(PK) | number | status |
--------------------------
1 | 1 | A |
2 | 1 | B |
3 | 2 | B |
4 | 2 | A |
5 | 3 | B |
6 | 3 | A |
7 | 4 | C |
8 | 4 | A |
9 | 5 | C |
10 | 6 | A |
tb_status
---------------------
st_id(PK) | status
---------------------
1 | A
2 | B
3 | C
Then run the query to get the following result:
SELECT id, number, status
FROM tb_info
WHERE number <= 5
ORDER BY id
--------------------------
id | number | status |
--------------------------
1 | 1 | A |
2 | 1 | B |
3 | 2 | B |
4 | 2 | A |
5 | 3 | B |
6 | 3 | A |
7 | 4 | C |
8 | 4 | A |
9 | 5 | C |
I want to get the two rows with the lowest number for each status value in tb_status in this result table.
The result I want to achieve is shown in the table below
--------------------------
id | number | status |
--------------------------
1 | 1 | A |
4 | 2 | A |
2 | 1 | B |
3 | 2 | B |
7 | 4 | C |
9 | 5 | C |
No matter how much I think about it, the answer does not come up. What should I do?
This is more the less the same solution as #forpas with the inclusion of tb_status
drop table if exists tb_info,tb_status;
create table tb_info
(id int primary key, number int, status varchar(1));
insert into tb_info values
( 1 , 1 , 'A' ),
( 2 , 1 , 'B' ),
( 3 , 2 , 'B' ),
( 4 , 2 , 'A' ),
( 5 , 3 , 'B' ),
( 6 , 3 , 'A' ),
( 7 , 4 , 'C' ),
( 8 , 4 , 'A' ),
( 9 , 5 , 'C' ),
( 10 , 6 , 'A' ),
( 11 , 1 , 'd' ),
( 12 , 3 , 'd' );
create table tb_status
( st_id int primary key, status varchar(1));
insert into tb_status values
( 1 , 'A'),
( 2 , 'B'),
( 3 , 'C');
select s.id,s.number,s.status
from tb_status
join
(select t.id,t.number,t.status,
if(t.`status` <> #p , #rn:=1,#rn:=#rn+1) rn,
#p:=t.`status` p
from tb_info t
cross join (select #rn:=0,#p:=0) r
order by t.status,t.number
) s
on s.status = tb_status.status
where s.rn <= 2
order by s.status,s.rn;
+----+--------+--------+
| id | number | status |
+----+--------+--------+
| 1 | 1 | A |
| 4 | 2 | A |
| 2 | 1 | B |
| 3 | 2 | B |
| 7 | 4 | C |
| 9 | 5 | C |
+----+--------+--------+
6 rows in set (0.00 sec)
Note d is dropped.
Use variables to rank the numbers of every status:
SET #rn := 0;
SET #status := '';
SELECT id, number, `status` FROM (
SELECT #rn := CASE
WHEN #status = `status` THEN #rn + 1
ELSE 1
END AS rn, number, `status`, id,
#status := `status`
FROM tb_info
WHERE number <= 5
ORDER BY `status`, number
) t
WHERE rn <= 2
See the demo.
Results:
| id | number | status |
| --- | ------ | ------ |
| 1 | 1 | A |
| 4 | 2 | A |
| 2 | 1 | B |
| 3 | 2 | B |
| 7 | 4 | C |
| 9 | 5 | C |
you need to order the results by status column so
SELECT id, number, status FROM tb_info WHERE number <= 5 ORDER BY id, status
or just
SELECT id, number, status FROM tb_info WHERE number <= 5 ORDER BY status
should do
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.
My table foobar has the following columns:
val: tinyint NOT NULL
date: timestamp NOT NULL
type: enum('A', 'B', 'C') NOT NULL
extra: tinyint NOT NULL
For each type I would like to find the row that matches an arbitrary condition on the columns (e.g. extra > 12 AND val > 0), that minimizes val and, in case of equal val, minimizes date. I assume that for each type such a row exists and is unique. Finally, I'd like the result (as many rows as there are different type values) to be ordered by val, date.
If foobar contains the following rows:
+------+---------------------+------+-------+
| val | date | type | extra |
+------+---------------------+------+-------+
| -1 | 2014-04-10 00:00:00 | A | 40 |
| 1 | 2014-04-15 00:00:00 | A | 15 |
| 2 | 2014-04-12 00:00:00 | A | 77 |
| 1 | 2014-04-11 00:00:00 | A | 2 |
| 1 | 2014-04-14 00:00:00 | A | 22 |
| 1 | 2014-04-10 00:00:00 | B | 40 |
| 1 | 2014-04-15 00:00:00 | B | 15 |
| 1 | 2014-04-12 00:00:00 | B | 77 |
| 1 | 2014-04-11 00:00:00 | B | 2 |
| 1 | 2014-04-14 00:00:00 | B | 22 |
| 4 | 2014-04-10 00:00:00 | C | 40 |
| 3 | 2014-04-15 00:00:00 | C | 15 |
| 3 | 2014-04-12 00:00:00 | C | 77 |
| 1 | 2014-04-11 00:00:00 | C | 2 |
| 3 | 2014-04-14 00:00:00 | C | 22 |
+------+---------------------+------+-------+
the query shall return:
+------+---------------------+------+-------+
| val | date | type | extra |
+------+---------------------+------+-------+
| 1 | 2014-04-10 00:00:00 | B | 40 |
| 1 | 2014-04-14 00:00:00 | A | 22 |
| 3 | 2014-04-12 00:00:00 | C | 77 |
+------+---------------------+------+-------+
This seems to work:
SELECT a.* FROM (
SELECT MIN(val * 4294967296 + UNIX_TIMESTAMP(date)) AS score
FROM foobar WHERE extra > 12 AND val > 0
GROUP BY type
) AS b
INNER JOIN foobar AS a
ON a.val * 4294967296 + UNIX_TIMESTAMP(a.date) = b.score
ORDER BY val, date;
but I find it over-complicated and I suspect that there must be a better way. Moreover, transforming my multi-columns criteria in a single numeric value (val * 4294967296 + UNIX_TIMESTAMP(date)) works in this simple case but may be more difficult in more complex scenarios.
Are there other, more generic schemes, that would do the same?
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(val INT SIGNED NOT NULL
,date TIMESTAMP NOT NULL
,type CHAR(1) NOT NULL
,extra TINYINT NOT NULL
,PRIMARY KEY(val,date,type)
);
INSERT INTO my_table VALUES
(-1,'2014-04-10 00:00:00','A',40),
( 1,'2014-04-15 00:00:00','A',15),
( 2,'2014-04-12 00:00:00','A',77),
( 1,'2014-04-11 00:00:00','A', 2),
( 1,'2014-04-14 00:00:00','A',22),
( 1,'2014-04-10 00:00:00','B',40),
( 1,'2014-04-15 00:00:00','B',15),
( 1,'2014-04-12 00:00:00','B',77),
( 1,'2014-04-11 00:00:00','B', 2),
( 1,'2014-04-14 00:00:00','B',22),
( 4,'2014-04-10 00:00:00','C',40),
( 3,'2014-04-15 00:00:00','C',15),
( 3,'2014-04-12 00:00:00','C',77),
( 1,'2014-04-11 00:00:00','C', 2),
( 3,'2014-04-14 00:00:00','C',22);
SELECT a.*
FROM my_table a
JOIN
( SELECT x.val
, x.type
, MIN(x.date) date
FROM my_table x
JOIN
( SELECT MIN(val) val
, type
FROM my_table
WHERE extra > 12
AND val > 0
GROUP
BY type
) y
ON y.type = x.type
AND y.val = x.val
WHERE x.extra > 12
GROUP
BY val
, type
) b
ON b.val = a.val
AND b.type = a.type
AND b.date = a.date;
+-----+---------------------+------+-------+
| val | date | type | extra |
+-----+---------------------+------+-------+
| 1 | 2014-04-14 00:00:00 | A | 22 |
| 1 | 2014-04-10 00:00:00 | B | 40 |
| 3 | 2014-04-12 00:00:00 | C | 77 |
+-----+---------------------+------+-------+
I am looking to get all values from first table along with joinned values from second table.
Table 1 is fee_category with fields:
id | Category
1 | A
2 | B
3 | C
4 | D
Table 2 is fee_charge with fields:
id | std_id | particularID | CategoryID | assign | amount
1 | 1 | 1 | 1 | 0 | 1000
2 | 1 | 1 | 2 | 1 | 12000
3 | 1 | 2 | 3 | 0 | 3000
4 | 1 | 2 | 4 | 0 | 10
5 | 2 | 1 | 2 | 0 | 100
6 | 2 | 2 | 3 | 0 | 120
Base table is "fee_category" from which I need all values left joining with "fee_charge" from where I need values or NULL for a particular std_id and particularID
SELECT fee_category.id, fee_category.Category, fee_charge.std_id
, fee_charge.particularID, fee_charge.CategoryID, fee_charge.assign, fee_charge.amount FROM fee_category
LEFT join fee_charge on fee_category.id=fee_charge.CategoryID
where (fee_charge.std_id = 1 OR fee_charge.std_id IS NULL)
AND (fee_charge.particularID = 1 OR fee_charge.particularID IS NULL)
group By fee_category.id
order By fee_charge.assign DESC
Here I am trying to get all categories of std_id=1 and particularID=1
Correct result should be
id | Category | std_id | particularID | CategoryID | assign | amount
1 | A | 1 | 1 | 1 | 0 | 1000
1 | B | 1 | 1 | 2 | 1 | 12000
1 | C | 1 | NULL | NULL | NULL | NULL
1 | D | 1 | NULL | NULL | NULL | NULL
I am trying various versions of the above query but not getting proper result. Please help
SELECT fee_category.id
, fee_category.Category
, X.std_id
, X.particularID
, X.CategoryID
, X.assign
, X.amount
FROM fee_category
LEFT JOIN
(SELECT * FROM fee_charge
WHERE fee_charge.std_id = 1
AND fee_charge.particularID = 1) AS X
ON x.CategoryID = fee_category.id
It's very hard to follow when the fiddle doesn't match the question, so I may have misunderstood, but perhaps you're after something like this...
SELECT x.id
, z.category
, x.std_id
, y.particularID
, y.categoryID
, y.assign
, y.amount
FROM fee_charge x
LEFT
JOIN fee_charge y
ON y.id = x.id
AND y.particularID = 1
JOIN fee_category z
ON z.id = x.categoryID
WHERE x.std_id = 1;
I have two tables:
mysql> select * from orders;
+------+---------------------+------------+---------+
| id | created_at | foreign_id | data |
+------+---------------------+------------+---------+
| 1 | 2010-10-10 10:10:10 | 3 | order 1 |
| 4 | 2010-10-10 00:00:00 | 6 | order 4 |
| 5 | 2010-10-10 00:00:00 | 7 | order 5 |
+------+---------------------+------------+---------+
mysql> select * from activities;
+------+---------------------+------------+------+
| id | created_at | foreign_id | verb |
+------+---------------------+------------+------+
| 1 | 2010-10-10 10:10:10 | 3 | get |
| 2 | 2010-10-10 10:10:15 | 3 | set |
| 3 | 2010-10-10 10:10:20 | 3 | put |
| 4 | 2010-10-10 00:00:00 | 6 | get |
| 5 | 2010-10-11 00:00:00 | 6 | set |
| 6 | 2010-10-12 00:00:00 | 6 | put |
+------+---------------------+------------+------+
Now I need to join activities with orders on foreign_id column: select only one activity (if exists) for every order such that ABS(TIMESTAMPDIFF(SECOND, orders.created_at, activities.created_at)) is minimal. E.g. the order and the activity were created approximately at the same time.
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+
The following query produces set of rows that includes the desired rows. If GROUP BY statement is included then it's not possible to control which row from activities is joined.
SELECT o.id AS order_id
, o.data AS data
, o.created_at AS order_created_at
, a.id AS activity_id
, a.verb AS verb
, a.created_at AS activity_created_at
FROM orders AS o
LEFT JOIN activities AS a ON a.foreign_id = o.foreign_id;
Is it possible to write such a query? Ideally I'd like to avoid using group by because this section is a part of larger reporting querty.
Because both tables reference some mysterious foreign key there's potential for errors with the query below, but it may give you a principle which you can adapt for your purposes...
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(id INT NOT NULL PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,data VARCHAR(20) NOT NULL
);
INSERT INTO orders VALUES
(1 ,'2010-10-10 10:10:10',3 ,'order 1'),
(4 ,'2010-10-10 00:00:00',6 ,'order 4'),
(5 ,'2010-10-10 00:00:00',7 ,'order 5');
DROP TABLE IF EXISTS activities;
CREATE TABLE activities
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,verb VARCHAR(20) NOT NULL
);
INSERT INTO activities VALUES
(1,'2010-10-10 10:10:10',3,'get'),
(2,'2010-10-10 10:10:15',3,'set'),
(3,'2010-10-10 10:10:20',3,'put'),
(4,'2010-10-10 00:00:00',6,'get'),
(5,'2010-10-11 00:00:00',6,'set'),
(6,'2010-10-12 00:00:00',6,'put');
SELECT o.id order_id
, o.data
, o.created_at order_created_at
, a.id activity_id
, a.verb
, a.created_at activity_created_at
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
JOIN
( SELECT a.foreign_id
, MIN(ABS(TIMEDIFF(a.created_at,o.created_at))) x
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
GROUP
BY a.foreign_id
) m
ON m.foreign_id = a.foreign_id
AND m.x = ABS(TIMEDIFF(a.created_at,o.created_at))
UNION DISTINCT
SELECT o.id
, o.data
, o.created_at
, a.id
, a.verb
, a.created_at
FROM orders o
LEFT
JOIN activities a
ON a.foreign_id = o.foreign_id
WHERE a.foreign_id IS NULL;
;
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+