How can I eliminate duplicate subquery/expression in DATEDIFF? - mysql

My database is a sample of login dates. I want to calculate a datediff between consecutive login dates on different rows. For example:
user_id login_date
1 2012-05-22
1 2012-05-25
/* difference is 3 days */
I was able to figure out two queries to do this calculation, but in both queries I needed to duplicate a subquery/expression to get my desired results.
I tried to use 'nextdate' in the datediff, but get an error:
#1054 - Unknown column 'nextdate' in 'field list'
Is there a way to eliminate the duplication? A completely new query is acceptable if it produces the desired results.
Sample database
CREATE TABLE IF NOT EXISTS `tbl` (
`user_id` int(11) DEFAULT NULL,
`login_date` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `tbl` (`user_id`, `login_date`) VALUES
(1, '2012-04-01'),
(1, '2012-04-25'),
(1, '2012-05-03'),
(1, '2012-05-04'),
(1, '2012-05-05'),
(1, '2012-05-06'),
(1, '2012-05-07'),
(1, '2012-05-09'),
(1, '2012-05-10'),
(1, '2012-05-11'),
(1, '2012-05-12'),
(1, '2012-05-16'),
(1, '2012-05-19'),
(1, '2012-05-20'),
(1, '2012-05-21'),
(1, '2012-05-22'),
(1, '2012-05-25'),
(2, '2012-04-02'),
(2, '2012-04-03'),
(2, '2012-04-04'),
(2, '2012-05-04'),
(2, '2012-05-06'),
(2, '2012-05-08'),
(2, '2012-05-09'),
(2, '2012-05-11'),
(2, '2012-05-17'),
(2, '2012-05-18'),
(2, '2012-05-19'),
(2, '2012-05-20'),
(2, '2012-05-21'),
(2, '2012-05-22'),
(2, '2012-05-25'),
(2, '2012-05-26'),
(2, '2012-05-27'),
(2, '2012-05-28'),
(2, '2012-05-29'),
(2, '2012-05-30'),
(2, '2012-05-31'),
(2, '2012-06-01'),
(2, '2012-06-02');
working query #1
SELECT
a.`user_id`,
a.`login_date`,
(SELECT
MIN(b.`login_date`)
FROM `tbl` b
WHERE a.`login_date` < b.`login_date`
AND a.`user_id` = b.`user_id`
) AS `nextdate`,
DATEDIFF((SELECT
MIN(b.`login_date`)
FROM `tbl` b
WHERE a.`login_date` < b.`login_date`
AND a.`user_id` = b.`user_id`
), a.`login_date`) AS `timespan`
FROM `tbl` a
WHERE 1=1
AND (a.`login_date` >= '2012-05-10' AND a.`login_date` <= '2012-05-25')
HAVING `nextdate` IS NOT NULL
ORDER BY a.`user_id` ASC, a.`login_date` ASC
working query #2
SELECT
a.`user_id`,
a.`login_date`,
MIN(b.`login_date`) AS `nextdate`,
DATEDIFF(MIN(b.`login_date`), a.`login_date`) AS `timespan`
FROM
(
SELECT
`user_id`,
`login_date`
FROM `tbl`
) a
JOIN
(
SELECT
`user_id`,
`login_date`
FROM `tbl`
) b
ON a.`user_id` = b.`user_id`
AND a.`login_date` < b.`login_date`
WHERE 1=1
AND (a.`login_date` >= '2012-05-10' AND a.`login_date` <= '2012-05-25')
GROUP BY a.`user_id`,a.`login_date`
desired results
user_id login_date nextdate timespan
1 2012-05-10 2012-05-11 1
1 2012-05-11 2012-05-12 1
1 2012-05-12 2012-05-16 4
1 2012-05-16 2012-05-19 3
1 2012-05-19 2012-05-20 1
1 2012-05-20 2012-05-21 1
1 2012-05-21 2012-05-22 1
1 2012-05-22 2012-05-25 3
2 2012-05-11 2012-05-17 6
2 2012-05-17 2012-05-18 1
2 2012-05-18 2012-05-19 1
2 2012-05-19 2012-05-20 1
2 2012-05-20 2012-05-21 1
2 2012-05-21 2012-05-22 1
2 2012-05-22 2012-05-25 3
2 2012-05-25 2012-05-26 1

This query is basically the same as your query #2, but just using a simple self join. The self join with group by and min(login_date) is about the simplest you can reduce this query down to, range scan on tbl a and then a key lookup on tbl b.
select a.user_id, a.login_date, min(b.login_date), datediff(min(b.login_date), a.login_date)
from tbl a
join tbl b on a.user_id = b.user_id and a.login_date < b.login_date
where (a.login_date >= '2012-05-10' AND a.login_date <= '2012-05-25')
group by a.user_id, a.login_date
order by a.user_id, a.login_date, b.login_date
;

Related

Select the oldest record of a certain group until it changes pattern, in SQL

I am trying to get the oldest record for every status update/change in the following table.
Table (status_updates) :
id
entity_id
status
date
7
2
Approved
2022-02-10
6
2
Approved
2022-02-05
5
2
Approved
2022-02-04
4
2
OnHold
2022-02-04
3
2
OnHold
2022-02-03
2
2
Approved
2022-02-02
1
2
Approved
2022-02-01
Result Needed :
id
entity_id
status
date
5
2
Approved
2022-02-04
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Tried :
select
`status`,
`created_at`
from
`status_updates`
left join
(select
`id`,
row_number() over (partition by status_updates.entity_id, status_updates.status order by status_updates.created_at asc) as sequence
from
`status_updates`)
as `oldest_history`
on
`oldest_history`.`id` = `shipper_credit_histories`.`id`
where `sequence` = 1
Result Achived :
id
entity_id
status
date
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Just using lag:
select s.*
from (
select id, status<>coalesce(lag(status) over (partition by entity_id order by id),'') status_change
from status_updates
) ids
join status_updates s using (id)
where status_change
here are the queries:
create table status_updates
(entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (2, 'Approved', '2022-02-05');
insert into status_updates values (2, 'Approved', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 'Approved', '2022-02-02');
insert into status_updates values (2, 'Approved', '2022-02-01');
select b.*
from status_updates a
right join status_updates b
on a.status=b.status and a.date=(b.date - interval 1 day)
where a.entity_id is null;
or this query(if you prefer left join)
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.date=(b.date + interval 1 day)
where b.entity_id is null;
in both you will see the expected result
the second solution is almost the same, but join by id instead of date
create table status_updates
(id integer,
entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (7, 2, 'Approved', '2022-02-10');
insert into status_updates values (6, 2, 'Approved', '2022-02-05');
insert into status_updates values (5, 2, 'Approved', '2022-02-04');
insert into status_updates values (4, 2, 'On Hold', '2022-02-04');
insert into status_updates values (3, 2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 2, 'Approved', '2022-02-02');
insert into status_updates values (1, 2, 'Approved', '2022-02-01');
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.id=b.id + 1
where b.entity_id is null;
result is the same what you expected

MYSQL set column based first record for each group

I have a table which can be recreated as
create table test
(employeeID int,
date Date,
TookTest int
);
insert into test(employeeID, date, TookTest)
values
(1, '2014-01-01', 1),
(1, '2014-01-02', 1),
(1, '2014-01-03', 1),
(2, '2014-01-01', 1),
(2, '2014-01-20', 1),
(3, '2014-01-01', 1),
(3, '2014-01-21', 1),
(4, '2014-01-03', 1),
(4, '2014-01-27', 1)
I am trying to set the first record of every group to be 0 in the newCol
employeeID date TookTest newCol
----------------------------------------
1 2014-01-01 1 0
1 2014-01-02 1 1
1 2014-01-03 1 1
2 2014-01-01 1 0
2 2014-01-20 1 1
3 2014-01-01 1 0
3 2014-01-21 1 1
4 2014-01-03 1. 0
4 2014-01-27 1. 1
How do I go about this?
UPDATE test t1
JOIN ( SELECT t2.employeeID, MIN(t2.`date`) `date`
FROM test t2
GROUP BY 1 ) t3 USING (employeeID, `date`)
SET t1.TookTest = 0;

Mysql query to decide the type if group by value is greater than 1 consider as type 2 else 1

I want to write a case condition in mysql query when grouped by channelvalue if the count is more than 1 it is considered as type_id 3 if there is no duplicates for the channelvalue then the type_id should be 2
else 0
select b.ChannelValue
case
when count(*),ChannelValue from tableb group by ChannelValue having count(*)=1 then 2
when count(*),ChannelValue from tableb group by ChannelValue having count(*)>1 then 3
else 0 END AS type_id
from tablea a inner join tableb b
on a.ChannelValue = b.ChannelValue;
Help me out !
You could use something like below:
select ChannelValue,
case
when (
select count(*) from tableb
group by tableb.ChannelValue
having tableb.ChannelValue = tablea.ChannelValue) = 1 then 2
when(
select count(*) from tableb
group by tableb.ChannelValue
having tableb.ChannelValue = tablea.ChannelValue) > 1 then 3
else 0
END as type_id
from tablea;
Say if you have this test data:
INSERT INTO tablea (PersonID, ChannelValue)
VALUES (1, 100),
(2, 200),
(3, 300);
INSERT INTO tableb (testID, ChannelValue, characterid)
VALUES (1, 100, 1),
(2, 100,2),
(3, 300, 3);
The query would return:
ChannelValue
type_id
100
3
200
0
300
2

Get summary grouped by category with three tables on mysql

category
---------------------------
id_category primary key
category
id_user foreign key
counterpart
---------------------------
id_counterpart primary key
counterpart
id_category foreign key
id_user foreign key
transaction
---------------------------
transaction primary key
date
id_counterpart foreign key
amount
id_card foreign key
id_user foreign key
Hello,
I have thoses table on mysql database and i want to have summary of each category (with 0 if there is any transaction) by month and year based on id_user.
I tried this command to have grouped by counterpart and it works but cannot reach when i add category and group by id_category.
select counterpart, s2.total from counterpart as s1
left join (select coalesce(sum(amount),0) as total, id_counterpart from transaction where year(date) = 2019 and month(date) = 7 and id_user = 2 group by id_counterpart) as s2
on s1.id_counterpart = s2.id_counterpart
left join category on s1.id_category = category.id_category
group by counterpart;
Do you have any idea to do that ? Else, i will do with php.
Thank you.
Edit : Add example
INSERT INTO `category` (`id_category`, `category`, `id_user`) VALUES
(1, 'cat_a', 1),
(2, 'cat_b', 1),
(3, 'cat_c', 1);
INSERT INTO `counterpart` (`id_counterpart`, `counterpart`, `id_category`, `id_user`) VALUES
(1, 'cp_a', 1, 1),
(2, 'cp_b', 2, 1),
(3, 'cp_c', 2, 1);
INSERT INTO `transaction` (`id_transaction`, `date`, `id_counterpart`, `amount`, `id_card`, `id_user`) VALUES
(1, '2019-07-01 00:00:00', 1, 400.00, 2, 1),
(2, '2019-07-01 00:00:00', 1, -24.95, 2, 1),
(3, '2019-07-31 00:00:00', 2, -20.04, 2, 1);
(4, '2019-07-30 00:00:00', 2, -1.00, 2, 1);
(5, '2019-07-29 00:00:00', 3, -2.00, 2, 1);
(6, '2019-07-28 00:00:00', 1, -3.00, 2, 1);
(7, '2019-07-27 00:00:00', 3, 2.00, 2, 1);
(8, '2019-07-26 00:00:00', 2, 5.00, 2, 1);
On july 2019 i want to have this, for user 1 :
cat_a 372.05
cat_b 16.04
cat_c 0.00
Join the tables and then group by category:
select c.category, coalesce(sum(t.amount), 0) total
from category c
left join counterpart as cp
on c.id_category = cp.id_category and c.id_user = cp.id_user
left join transaction t
on t.id_counterpart = cp.id_counterpart and t.id_user = cp.id_user and year(t.date) = 2019 and month(t.date) = 7 and t.id_user = 1
group by c.id_category, c.category
See the demo.
Results:
| category | total |
| -------- | ------ |
| cat_a | 372.05 |
| cat_b | -16.04 |
| cat_c | 0 |

mySQL - SUM and COUNT and JOIN to display all records [duplicate]

This question already has answers here:
How can I do a FULL OUTER JOIN in MySQL?
(15 answers)
Closed 4 years ago.
This is a following question to this one Join two tables with SUM and COUNT.
What I try to do is to have all values displayed as some are in history table and not in rota table or vice-versa (999 and 777)
So my tables are:
create table history (
code int(10) primary key,
PN varchar(10) not null,
Qty int(10) not null,
LOC_ID int(10));
insert into history values (1, 'T1', 1, 1);
insert into history values (2, 'A1', 2,2);
insert into history values (3, 'J1', 3,3);
insert into history values (4, 'A2', 1,4);
insert into history values (5, 'J2', 2,1);
insert into history values (6, 'A3', 3,2);
insert into history values (7, 'J3', 4,3);
insert into history values (8, 'T1', 5,4);
insert into history values (9, 'A1', 1,1);
insert into history values (10, '999', 3,2);
insert into history values (11, 'J2', 4,3);
insert into history values (12, 'A1', 3,4);
insert into history values (13, 'J2', 5,1);
create table rota (
code int(10) primary key,
PN varchar(10) not null,
SN varchar(10) not null,
LOC_ID int(10));
insert into rota values (1, 'T1', 't1a',1);
insert into rota values (2, 'A1', 'a1a',2);
insert into rota values (3, 'J1', 'j1a',3);
insert into rota values (4, 'A2', 'a2a',4);
insert into rota values (5, 'J2', 'j2a',1);
insert into rota values (6, 'A3', 'a3a',2);
insert into rota values (7, 'J3', 'j3a',3);
insert into rota values (8, '777', 't1b',4);
insert into rota values (9, 'A1', 'a1b',1);
insert into rota values (10, 'J2', 'j2b',2);
insert into rota values (11, 'J2', 'j2c',3);
insert into rota values (12, 'A1', 'a1c',4);
insert into rota values (13, 'J2', 'j2d',1);
insert into rota values (14, 'J2', 'j2e',2);
insert into rota values (15, 'J2', 'j2f',3);
create table loca (
code1 int(10) primary key,
LOC varchar(10) not null);
insert into loca values (1, 'AAA');
insert into loca values (2, 'BBB');
insert into loca values (3, 'CCC');
insert into loca values (4, 'DDD');
The code I have got is
select CASE WHEN a.pn IS NULL THEN b.pn ELSE a.pn END AS PN
, a.q
, b.c
, a.LOC_ID
, b.LOC_ID
from
(select
h.pn
, sum(qty) q
, h.LOC_ID
from
history h
group by h.pn, h.LOC_ID) a
RIGHT JOIN
(select
r.pn
, count(sn) c
, r.LOC_ID
from
rota r
group by r.pn, r.LOC_ID) b
on a.pn = b.pn WHERE a.LOC_ID = b.LOC_ID
order by a.pn;
The above code works great for all PN that are in both tables. The problem is for values that are specific to one of the tables. I can remove the WHERE clause from JOIN but it is not corect. The question is - how to get all PNs from history and rota where some of them are present i just one table. I had some luck with RIGHT JOIN but that did not cover unique values from the other table. Any one came across solution before?
Results shoud look like the following table
PN LOC_ID Count Qty
T1 1 1 1
A1 2 1 2
J1 3 1 3
A2 4 1 1
J2 1 2 2
A3 2 1 3
J3 3 1 4
777 4 1 NULL
A1 1 1 1
J2 2 2 NULL
J2 3 2 4
A1 4 1 3
J2 1 2 2
J2 2 2 NULL
J2 3 2 4
999 2 NULL 3
use another join and that is left and make them union
select t.PN,t.q,t.c,t.LOC_ID,t.LOC_ID_b from
(
select CASE WHEN a.pn IS NULL THEN b.pn ELSE a.pn END AS PN
, a.q
, b.c
, a.LOC_ID
, b.LOC_ID as LOC_ID_b
from
(select
h.pn
, sum(qty) q
, h.LOC_ID
from
history h
group by h.pn, h.LOC_ID) a
RIGHT JOIN
(select
r.pn
, count(sn) c
, r.LOC_ID
from
rota r
group by r.pn, r.LOC_ID) b
on a.pn = b.pn and a.LOC_ID = b.LOC_ID
) as t
union
select t2.PN,t2.q,t2.c,t2.LOC_ID,t2.LOC_ID_b from
(
select CASE WHEN a.pn IS NULL THEN b.pn ELSE a.pn END AS PN
, a.q
, b.c
, a.LOC_ID
, b.LOC_ID as LOC_ID_b
from
(select
h.pn
, sum(qty) q
, h.LOC_ID
from
history h
group by h.pn, h.LOC_ID) a
left JOIN
(select
r.pn
, count(sn) c
, r.LOC_ID
from
rota r
group by r.pn, r.LOC_ID
) b
on a.pn = b.pn and a.LOC_ID = b.LOC_ID
) t2
http://sqlfiddle.com/#!9/c20c81/20