Group by IDs and TIMESTAMPDIFF one column in same table - mysql

I am trying to find out "how many unique messages has been sent to a person on a specific boat within a timeframe, and what is the minimum days between those texts" and display it including the count.
A person is represented by 'id', boat by 'id2' and message by 'text'.
CREATE TABLE `stacktable` (
`timestamp` DATETIME NOT NULL,
`id` VARCHAR(15) NOT NULL,
`id2` VARCHAR(3) NULL DEFAULT NULL,
`text` VARCHAR(255) NULL DEFAULT NULL,
`id3` INT(10) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id3`)
);
insert into stacktable (timestamp,id,id2,text) VALUES
('2015-01-01 00:00:01',1,10,'ABC'),
('2015-01-01 00:00:01',2,11,'ABC'),
('2015-01-01 00:00:01',3,12,'ABC'),
('2015-01-01 00:00:02',3,12,'ABC'),
('2015-01-01 00:00:02',1,10,'ABC'),
('2015-01-04 00:00:01',1,10,'ABC'),
('2015-01-04 00:00:01',1,10,'BCD'),
('2015-01-04 00:00:01',2,11,'ABC'),
('2015-01-04 00:00:01',2,11,'BCD'),
('2015-01-04 00:00:01',3,12,'ABC'),
('2015-01-04 00:00:01',3,12,'BCD'),
('2015-01-04 00:00:01',3,13,'CDE'),
('2015-01-07 00:00:01',2,11,'BCD'),
('2015-01-07 00:00:01',3,12,'BCD'),
('2015-01-07 00:00:01',3,13,'CDE'),
('2015-01-07 00:00:01',3,13,'DEF'),
('2015-01-08 00:00:01',3,12,'ABC'),
('2015-01-08 00:00:01',4,14,'EFG'),
('2015-01-09 00:00:01',4,14,'EFG'),
('2015-01-09 00:00:02',4,15,'FGH'),
('2015-01-10 00:00:01',4,14,'EFG'),
('2015-01-10 00:00:01',4,14,'FGH'),
('2015-01-10 00:00:01',4,15,'FGH'),
('2015-01-11 00:00:01',4,14,'EFG'),
('2015-01-15 00:00:01',4,14,'EFG');
To show what I am trying to achieve:
select * from stacktable where id = 1
timestamp id id2 text id3
2015-01-01 00:00:01 1 10 ABC 1 First entry for id+id2+text (ABC)
2015-01-01 00:00:02 1 10 ABC 5 Second entry for same keys id+id2+text 1 second later
2015-01-04 00:00:01 1 10 ABC 6 Third entry for same keys id+id2+text 2 days later
2015-01-04 00:00:01 1 10 BCD 7 First entry for id+id2+text (BCD)
I only want to count records that has "same id,id2 and text within a period of 2 days", but also show the "minimum diffdate in days between the hits".
The output I want from this would be:
id id2 text count(*) mindiffdatebetweenhits
-------------------------------------------
1 10 ABC 3 0 count id3s 1,5 and 6, minimumdaydiff is between id3 1 and 5 = 0 days
3 12 ABC 3 0 count id3s 3,4 and 10, minimumdaydiff is between id3 3 and 4 = 0 days
4 14 EFG 4 1 count id3s 18,19,21 and 24, minimumdaydiff is equal between all hits = 1 day
4 15 FGH 2 0 count id3s 20 and 23, minimumdaydiff is between id3 20 and 23 = 0 days
How can I get the desired output?

This should do it, assuming sequences of only one row are to be discarded:
select id, id2, text, seq, count(id) as total, min(diff) as mindiff
from (
select t1.row, t2.row row2, t1.id, t1.id2, t1.text, t1.id3,
TIMESTAMPDIFF(DAY, t1.timestamp, t2.timestamp) as diff,
IF (TIMESTAMPDIFF(DAY, t1.timestamp, t2.timestamp) > 2, #seq * (1 and #seq := #seq +1), #seq) as seq
from (select (#row := #row + 1) as row, id, id2, text, id3, timestamp
from (select id, id2, text, id3, timestamp
from stacktable
order by id, id2, text) sorted,
(select #row := 0) setup) t1
left join (select (#row2 := #row2 + 1) as row, id, id2, text, id3, timestamp
from (select id, id2, text, id3, timestamp
from stacktable
order by id, id2, text) sorted,
(select #row2 := 0) setup) t2
on (t1.id = t2.id and t1.id2 = t2.id2 and t1.text=t2.text and t1.row = t2.row - 1),
(select #seq := 1) setup_sequence
) t3
group by id, id2, text, seq
having total > 1
To facilitate reading, the query uses the same subquery tow times, t1 and t2, and all it does is sort and subsequently number the rows of the table:
select (#row := #row + 1) as row, id, id2, text, id3, timestamp
from (select id, id2, text, id3, timestamp
from stacktable
order by id, id2, text) sorted,
(select #row := 0) setup
See fiddle. Note that the sequence counter is really not unique between all sequences. It's not a bug. It's only unique between sequences of same id,id2,text.
The sequence counter update is a bit tricky: #seq * (1 and #seq := #seq +1). It relies on the first #seq being set up for the multiplication before being updated. I'm not sure this is deterministic or consistent accross engines. However, the query can also be changed to avoid it by joining the records of t1 with the previous record instead of the next record (in t2). (not tried out)

Related

How to return rows that sum(field) <= value

The concept is to find the rows in which sum(fCurrAmt) may higher than the entered amount but should not lower than entered amount. I dont know how to explain indetail this creteria.
Lets say I have a table demo
Scenario : 1
id fCurrAmt price
------------------
1 1 10
2 1 20
3 2 25
4 3 30
If the entered amount is 3, I need to return first 3 rows
id fCurrAmt price
------------------
1 1 10
2 1 20
3 2 25
In the above scenario, sum(fCurrAmt) is 4 which is higher than entered amount.
Scenario : 2
id fCurrAmt price
------------------
1 1 10
2 1 20
If the entered amount is 3, I need to return there is no records.
In the above scenario, sum(fCurrAmt) is 2 which is lower than entered amount.
I have tried with below code in scenario 1
SELECT a.id,a.price,a.total,a.fCurrAmt from (
select b.id,b.price,b.fCurrAmt,(
select sum(fCurrAmt) from demo c where c.id <= b.id order by c.id
) as total from demo b
) a where a.total <= 3
It returns first 2 records only
Try This You need to use subquery and min with group by. Using subquery we can return the minimum id where the sum is satisfied with given numbers and then join the id to retrieve full rows upto the id
SELECT *
FROM test t
INNER JOIN(
SELECT MIN(id) valId
FROM (
SELECT t.id,
(SELECT SUM(t1.fCurrAmt)
FROM test t1
WHERE t1.id <= t.id) AS Rowsum
FROM test t) t2
WHERE Rowsum >= 3) t1 ON t1.valId >= t.id;
SQL Fiddle http://www.sqlfiddle.com/#!9/a1d07/13
Try this
DECLARE #sumOfFCurrAmt int
DECLARE #sumOfEnteredAmt int
set #sumOfFCurrAmt=(select Sum(fCurrAmt) from demoB)
set #sumOfEnteredAmt=(select sum(fCurrAmt) from demoC)
IF(#sumOfFCurrAmt>#sumOfEnteredAmt)
BEGIN
SELECT top(#sumOfEnteredAmt)* FROM demoB
END
A slightly lengthy way but it works.
First, I would store the sum of fCurrAmt up to the number entered in a temporary table. Hence, the first three statements are DROP, CREATE and INSERT. I would then take that value to check if the sum of those rows until the number entered are greater or lesser, if it is greater, then return all the rows until the threshold else return nothing. Here, sof12 is the same table as scenario 1 and sof14 is the same table as yours in scenario 2.
SCENARIO 1:
DROP TABLE IF EXISTS `tempsum`;
CREATE TABLE tempsum (`sum` integer(13));
INSERT INTO tempsum (SELECT SUM(fCurrAmt) FROM
(SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof12
WHERE #total <= 3) as new2);
SELECT id, fCurrAmt, price FROM (
SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof12
WHERE #total <= 3) As new3 HAVING (SELECT SUM(SUM) FROM tempsum) >= 3;
Output of this case:
id fCurrAmt price
1 1 10
2 1 20
3 2 25
SCENARIO 2:
DROP TABLE IF EXISTS `tempsum`;
CREATE TABLE tempsum (`sum` integer(13));
INSERT INTO tempsum (SELECT SUM(fCurrAmt) FROM
(SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof14
WHERE #total <= 3) as new2);
SELECT id, fCurrAmt, price FROM (
SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof14
WHERE #total <= 3) As new3 HAVING (SELECT SUM(SUM) FROM tempsum) >= 3;
Output of this case: No records returned.

Mysql - Create view combined 2 tables but only merged by select without merge tables

In MySQL, I have 2 tables, named table_rebate and table_bonus
table_rebate ( have 3 same columns, 2 different columns )
a_id a_value a_time
1 1000 2018-05-05 10:25:15
2 3000 2018-05-05 11:35:15
table_bonus ( have 3 same columns, 3 different colums )
b_id b_value b_time
01 500 2018-05-05 11:20:15
02 700 2018-05-05 12:30:15
I need to select that 3 same columns into 1 tables to my PHP (CI) views.
Number from Values Time
1 Rebate 1000 2018-05-05 10:25:15
2 Bonus 500 2018-05-05 11:20:15
3 Bonus 700 2018-05-05 11:35:15
4 Rebate 3000 2018-05-05 12:30:15
How can I do this ? It's not to be merged, but need to print like merged table and can be sort by (a_time & b_time) ascending.
EXPLAIN select (#rn := #rn + 1) as id, `from`, `values`, `time`
from ((select 'rebate' as `from`, a_value as `values`, a_time as `time`
from table_rebate
) union all
(select 'bonus' as `from`, b_value, b_time
from table_bonus
)
) br cross join
(select #rn := 0) params
order by `time`;
You can use union all:
select (#rn := #rn + 1) as id, `from`, `values`, `time`
from ((select 'rebate' as `from`, a_value as `values`, a_time as `time`
from table_rebate
) union all
(select 'bonus' as `from`, b_value, b_time
from table_bonus
)
) br cross join
(select #rn := 0) params
order by `time`;
Note that from, values, and time are all keywords in SQL (even if not reserved). That makes them very bad names for columns.

How to get temporal sequence by mysql

In my table there is an id column, a date column and a status column 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
I want to get a temporal sequence of each ID. Which means if in the neighboring time one id is in the same status, then the former ones will be omitted. The query result is like:
ID DATE STATUS
1 0112 A
1 0130 B
1 0201 A
2 0107 C
How can I realize it by MySQL?
You have to use variable to do this:
select `id`, `date`, `status`
from (
select *, #rowno:=if(#grp = `STATUS`, #rowno + 1 , 1) as rowno, #grp := `STATUS`
from yourtable
cross join (select #grp := null, #rowno := 0) t
order by `id`, `date` desc
) t1
where rowno = 1
order by `id`, `date`
SqlFiddle Demo

how to find repeated value and count in mysql

I have table with 3 columns, now how find value if it appears next 3 times immediately
i.e 1st trnas_value appears in next 3 consecutive times (repeaded 4 times) and 2nd and 6th also rows also repeated the same.date column is sorted from A_Z
date tran_val name
23mar 22 mark
24mar 22 mark
25mar 22 mark
26mar 22 mark
27mar 22 mark
28jan 99 john
29jan 99 john
30jan 99 john
31jan 99 john
output
name trans_value consecutive_count
mark 22 2
john 99 1
we have a code which is not giving the above output..
SELECT name,
tran_val,
MAX(cnt - 3) AS consecutive_count
FROM
(
SELECT date,
tran_val,
name,
#cnt:=IF(#tran_val=tran_val AND #name=name, #cnt + 1, 1) AS cnt,
#tran_val:=tran_val,
#name:=name
FROM some_table
CROSS JOIN (SELECT #cnt:=0, #tran_val:=0, #name:='') sub0
ORDER BY `date`
) sub1
GROUP BY name,
tran_val
any modification in the above code which will get desired output.thanks
Try this:
SELECT `tran_val`, `name`, COUNT(*) - 3
FROM (
SELECT `date`, `tran_val`, `name`, rn - seq AS grp
FROM (
SELECT `date`, `tran_val`, `name`,
#rn := #rn + 1 AS rn,
#seq := IF(#name = `name` And #val = `tran_val`, #seq+1, 1) AS seq,
#name := name,
#val := tran_val
FROM mytable
CROSS JOIN (SELECT #rn := 0, #seq := 0, #name = '', #val = 0) AS vars
ORDER BY `date`) AS t ) AS s
GROUP BY `tran_val`, `name`, grp
HAVING COUNT(*) > 3
You need two separate variable to enumerate sequences:
#rn just enumerates consecutive table rows
#seq enumerates consecutive table rows having the same name, tran_val values.
The difference between these two variables, i.e. #rn - #seq, identifies islands of consecutive table rows having the same name, tran_val values.
Edit: I added a HAVING clause to the query so as to filter out islands having a population of 3 or less consecutive rows.
Demo here

Select from a select statement from a defined value

I've the following table structure:
id |name |date
1 a 2012-01-01
2 a 2011-01-01
3 a 2010-01-01
4 a 2014-01-01
5 a 2011-01-01
I'd like to perform a select order by date (desc), and after select the first 3 rows from the results by a condition which would be where id = 1. So the second part of the query would be "give me the first 3 rows starting from the row whose id equals to 1"
EDIT:
After the first "part" the result would be:
SELECT id, name, date FROM table ORDER BY date DESC
id |name |date
4 a 2014-01-01
1 a 2012-01-01
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
After the second part it should look like this (so the first 3 after the row whose id is 1):
id |name |date
2 a 2011-01-01
5 a 2011-01-01
3 a 2010-01-01
I have no any idea how could I solve it, please help me.
EDIT:
This is the concrete code I'd like to re-write:
SELECT `id`, `questions`.`userid`, `categories`.`name`, `user`.`username`, `title`,
`details`, `date` FROM `questions`
LEFT JOIN `user`
ON `questions`.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON `questions`.`categoryid` = `categories`.`categoryid`
ORDER BY `date` DESC LIMIT 10
SELECT *
FROM table
WHERE date < (SELECT date FROM table WHERE id = 1)
ORDER BY date DESC
LIMIT 3
This isn't pretty because MySQL doesn't support row_number() or common table expressions, but it should work. Basically, get the row number ordered by the date, then select those whose row number is greater than an arbitrary value (in this case 1). Finally use limit to select the number of records you want.
SELECT id, name, mydate
FROM (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
WHERE rn > (
select rn
from (
SELECT id, name, mydate, #rn:=#rn+1 rn
FROM mytable, (select #rn:=0) t
ORDER BY mydate DESC
) t2
where id = 1
)
LIMIT 3
SQL Fiddle Demo
This is what you want to do... if finds the first id thats equal to 4 and then selects those out. then limit the offset to go to the next row and pull out 3
SELECT id, name, m_date from(
SELECT id, name, m_date, #a := id, if(#a = 4, #b := 1, #b) AS join_id
FROM test
join(SELECT #a := 0, #b := 0) t
ORDER BY m_date DESC
) AS tt
WHERE join_id = 1
LIMIT 1,3
SELECT temp.`id`, temp.`userid`, `categories`.`name`, `user`.`username`, temp.`title`,
temp.`details`, temp.`date` FROM (
SELECT `id`, `categoryid`, `details`, `title`, `userid`, `date`, #a := id, if(#a = 11, #b := 1, #b) AS join_id
FROM `questions`
join(SELECT #a := 0, #b := 0) t
ORDER BY `date` DESC
) as temp
LEFT JOIN `user`
ON temp.`userid` = `user`.`userid`
LEFT JOIN `categories`
ON temp.`categoryid` = `categories`.`categoryid`
WHERE join_id = 1
LIMIT 1,10;
SEE FIDDLE for clarification