MySQL count changes - mysql

I would like to count number of changes in column Value grouped by Id using MySQL.
Source Table:
create table sequence
(
`Id` int,
`Date` date,
`Value` int not null,
PRIMARY KEY (`Id`,`Date`)
);
insert into sequence
( `Id`,`Date`, `Value` )
values
(1, '2016-01-01' , 0 ),
(1, '2016-01-02' , 10 ),
(1, '2016-01-03' , 0 ),
(1, '2016-01-05' , 0 ),
(1, '2016-01-06' , 10 ),
(1, '2016-01-07' , 15 ),
(2, '2016-01-08' , 15 );
Visualization:
+------------+-------+-------+
| Date | ID | Value |
+------------+-------+-------+
| 2016-01-01 | 1 | 0 |
| 2016-01-02 | 1 | 10 | (change)
| 2016-01-03 | 1 | 0 | (change)
| 2016-01-05 | 1 | 0 |
| 2016-01-06 | 1 | 10 | (change)
| 2016-01-07 | 1 | 15 | (change)
| 2016-01-08 | 2 | 15 |
+------------+-------+-------+
Expected output:
+-------+-------+
| ID | Value |
+-------+-------+
| 1 | 4 |
| 2 | 0 |
+-------+-------+
I would like to ask if there is a way how to do this in SQL.

This is not the very efficient or elegant solution,
but just to show some goals that you can achieve using mysql :-)
http://sqlfiddle.com/#!9/1db14/6
SELECT t1.id, MAX(t1.changes)
FROM (SELECT t.*,
IF (#i IS NULL,#i:=0,IF(#lastId <> id,#i:=0,IF (#lastV <> `value`, #i:=#i+1, #i:=#i))) as changes,
#lastV := `value`,
#lastId := `id`
FROM (
SELECT *
FROM sequence
ORDER BY id, date) t
) t1
GROUP BY t1.id

Related

How to Allocate Random Jobs to List of Agents in Mysql?

I am looking to write a script that will allow me to allocate random jobs to 7 different agents. The following are my 2 tables:
DROP TABLE IF EXISTS jobs;
CREATE TABLE `Jobs` (
`Job_id` SERIAL PRIMARY KEY,
`Start_Date` date DEFAULT NULL,
`End_Date` date DEFAULT NULL,
`Ref_no` int NOT NULL
) ;
INSERT INTO Jobs(Job_id,Start_Date, End_Date, Ref_no) VALUES
(1,'2018-09-01','2021-08-31',123456789),
(2,'2019-10-03','2020-10-02',987654321),
(3,'2020-11-01','2021-10-02',543210123),
(4,'2020-12-01','2022-11-30',481216181),
(5,'2018-04-01','2020-03-31',246810121),
(6,'2019-05-30','2020-05-29',369121518),
(7,'2019-11-01','2020-10-31',581114179);
DROP TABLE IF EXISTS agents;
CREATE TABLE `Agents` (
`Agent_id` SERIAL PRIMARY KEY,
`Agent_Name` varchar(255) NOT NULL UNIQUE
) ;
INSERT INTO Agents(Agent_id, Agent_Name) VALUES
(1,'Humpty'),
(2,'Mickey'),
(3,'Minnie'),
(4,'Daffy'),
(5,'Ellie'),
(6,'Jack'),
(7,'Jill');
Now what I would like to do is write a script that would randomly allocate the jobs across the 7 agents. Would really appreciate it if somebody could advise on how I could start this off.
Thanks in advance.
In MySQL 8.x you can combine RAND() with ROW_NUMBER() to associate random rows. For example:
select *
from (
select *, row_number() over(order by rand()) as rn
from `Jobs`
) j
join (
select *, row_number() over(order by rand()) as rn
from `Agents`
) a on j.rn = a.rn
See running example at DB Fiddle.
Here's a method for versions of MySQL prior to 8.0...
SELECT x.*
, MOD(#i:=#i+1,7) + 1 i -- where '7' is the number of agents.
FROM
( SELECT j.*
FROM jobs j
ORDER
BY RAND()
) x
JOIN (SELECT #i := 0) vars
ORDER
BY i;
Sample output:
+--------+------------+------------+-----------+------+
| Job_id | Start_Date | End_Date | Ref_no | i |
+--------+------------+------------+-----------+------+
| 5 | 2018-04-01 | 2020-03-31 | 246810121 | 1 |
| 3 | 2020-11-01 | 2021-10-02 | 543210123 | 2 |
| 1 | 2018-09-01 | 2021-08-31 | 123456789 | 3 |
| 7 | 2019-11-01 | 2020-10-31 | 581114179 | 4 |
| 4 | 2020-12-01 | 2022-11-30 | 481216181 | 5 |
| 6 | 2019-05-30 | 2020-05-29 | 369121518 | 6 |
| 2 | 2019-10-03 | 2020-10-02 | 987654321 | 7 |
+--------+------------+------------+-----------+------+

MYSQL: Exclude duplicate scan logs within same day

I'm trying to select rows excluding duplicates in a day.
Criteria for duplicate is: SAME USER AND SAME PRODUCT_UPC AND SAME DATE(SCANNED_ON)
So, from the below table, if SCAN_ID = 100 is selected, exclude SCAN_ID = 101 since they belong to same user_id AND same product_upc AND have same DATE(scanned_on).
Here's the table structure:
SCAN_ID USER_ID PRODUCT_UPC SCANNED_ON
100 1 0767914767 2020-08-01 03:49:11
101 1 0767914767 2020-08-01 03:58:28
102 2 0064432050 2020-08-02 04:01:31
103 3 0804169977 2020-08-10 04:08:48
104 4 0875523846 2020-08-10 05:21:32
105 4 0007850492 2020-08-12 07:10:05
Query I've come up so far is:
SET #last_user='', #last_upc='', #last_date='';
SELECT *,
#last_user as last_user , #last_user:=user_id as this_user,
#last_upc as last_upc , #last_upc:=product_upc as this_upc,
#last_date as last_date , #last_date:=DATE(scanned_on) as this_date
FROM scansv2
HAVING this_user != last_user OR this_upc != last_upc OR this_date != last_date
In MySQL 8 you can use ROW_NUMVER for this
CREATE TABLE scansv2 (
`SCAN_ID` INTEGER,
`USER_ID` INTEGER,
`PRODUCT_UPC` INTEGER,
`SCANNED_ON` DATETIME
);
INSERT INTO scansv2
(`SCAN_ID`, `USER_ID`, `PRODUCT_UPC`, `SCANNED_ON`)
VALUES
('100', '1', '0767914767', '2020-08-01 03:49:11'),
('101', '1', '0767914767', '2020-08-01 03:58:28'),
('102', '2', '0064432050', '2020-08-02 04:01:31'),
('103', '3', '0804169977', '2020-08-10 04:08:48'),
('104', '4', '0875523846', '2020-08-10 05:21:32'),
('105', '4', '0007850492', '2020-08-12 07:10:05');
WITH rownum AS (SELECT `SCAN_ID`, `USER_ID`, `PRODUCT_UPC`, `SCANNED_ON`,ROW_NUMBER() OVER (
PARTITION BY `PRODUCT_UPC`
ORDER BY `SCANNED_ON` DESC) row_num FROM scansv2)
SELECT `SCAN_ID`, `USER_ID`, `PRODUCT_UPC`, `SCANNED_ON` FROM rownum WHERE row_num = 1 ORDER BY `SCAN_ID`
SCAN_ID | USER_ID | PRODUCT_UPC | SCANNED_ON
------: | ------: | ----------: | :------------------
101 | 1 | 767914767 | 2020-08-01 03:58:28
102 | 2 | 64432050 | 2020-08-02 04:01:31
103 | 3 | 804169977 | 2020-08-10 04:08:48
104 | 4 | 875523846 | 2020-08-10 05:21:32
105 | 4 | 7850492 | 2020-08-12 07:10:05
db<>fiddle here
in MySQL 5.x you need user defined variables for the same purpose
SELECT `SCAN_ID`, `USER_ID`, `PRODUCT_UPC`, `SCANNED_ON`
FROM
(SELECT `SCAN_ID`, `USER_ID`, `SCANNED_ON`,
IF (#product = `PRODUCT_UPC`,#row_num := #row_num + 1,#row_num := 1) row_num
, #product := `PRODUCT_UPC` PRODUCT_UPC
FROM (SELECT * FROM scansv2 ORDER BY `PRODUCT_UPC`, `SCANNED_ON`) c,(SELECT #row_num := 0,#product := 0) a ) b
WHERE row_num = 1 ORDER BY `SCAN_ID`
SCAN_ID | USER_ID | PRODUCT_UPC | SCANNED_ON
------: | ------: | ----------: | :------------------
100 | 1 | 767914767 | 2020-08-01 03:49:11
102 | 2 | 64432050 | 2020-08-02 04:01:31
103 | 3 | 804169977 | 2020-08-10 04:08:48
104 | 4 | 875523846 | 2020-08-10 05:21:32
105 | 4 | 7850492 | 2020-08-12 07:10:05
db<>fiddle here
In most databases (including MySQL pre-8.0), filtering with a subquery is a supported and and efficient option:
select s.*
from scansv2 s
where s.scanned_on = (
select min(s1.scanned_on)
from scansv2 s1
where
s1.user_id = s.user_id
and s1.product_upc = s.product_upc
and s1.scanned_on >= date(s.scanned_on)
and s1.scanned_on < date(s.scanned_on) + interval 1 day
)
This gives you the first row per user_id, product_upc and day, and filters out the other ones, if any.

how to track score gains in mysql

I would like to display a players current score as well as how many points they have gained within a selected time frame.
I have 2 tables
skills table
+----+---------+---------------------+
| id | name | created_at |
+----+---------+---------------------+
| 1 | skill 1 | 2020-06-05 00:00:00 |
| 2 | skill 2 | 2020-06-05 00:00:00 |
| 3 | skill 3 | 2020-06-05 00:00:00 |
+----+---------+---------------------+
scores table
+----+-----------+----------+-------+---------------------+
| id | player_id | skill_id | score | created_at |
+----+-----------+----------+-------+---------------------+
| 1 | 1 | 1 | 5 | 2020-06-06 00:00:00 |
| 2 | 1 | 1 | 10 | 2020-07-06 00:00:00 |
| 3 | 1 | 2 | 1 | 2020-07-06 00:00:00 |
| 4 | 2 | 1 | 11 | 2020-07-06 00:00:00 |
| 5 | 1 | 1 | 13 | 2020-07-07 00:00:00 |
| 6 | 1 | 2 | 10 | 2020-07-07 00:00:00 |
| 7 | 2 | 1 | 12 | 2020-07-07 00:00:00 |
| 8 | 1 | 1 | 20 | 2020-07-08 00:00:00 |
| 9 | 1 | 2 | 15 | 2020-07-08 00:00:00 |
| 10 | 2 | 1 | 17 | 2020-07-08 00:00:00 |
+----+-----------+----------+-------+---------------------+
my expected results are:-
24 hour query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 7 |
| 1 | skill 2 | 15 | 5 |
+-----------+---------+-------+------+
7 day query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 10 |
| 1 | skill 2 | 15 | 14 |
+-----------+---------+-------+------+
31 day query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 15 |
| 1 | skill 2 | 15 | 14 |
+-----------+---------+-------+------+
so far I have the following, but all this does is return the last 2 records for each skill, I am struggling to calculate the gains and the different time frames
SELECT player_id, skill_id, name, score
FROM (SELECT player_id, skill_id, name, score,
#skill_count := IF(#current_skill = skill_id, #skill_count + 1, 1) AS skill_count,
#current_skill := skill_id
FROM skill_scores
INNER JOIN skills
ON skill_id = skills.id
WHERE player_id = 1
ORDER BY skill_id, score DESC
) counted
WHERE skill_count <= 2
I would like some help figuring out the query I need to build to get the desired results, or is it best to do this with php instead of in the db?
EDIT:-
MYSQL 8.0.20 dummy data id's are primary_key auto increment but I didnt ad that for simplicity:-
CREATE TABLE skills
(
id bigint,
name VARCHAR(80)
);
CREATE TABLE skill_scores
(
id bigint,
player_id bigint,
skill_id bigint,
score bigint,
created_at timestamp
);
INSERT INTO skills VALUES (1, 'skill 1');
INSERT INTO skills VALUES (2, 'skill 2');
INSERT INTO skills VALUES (3, 'skill 3');
INSERT INTO skill_scores VALUES (1, 1, 1 , 5, '2020-06-06 00:00:00');
INSERT INTO skill_scores VALUES (2, 1, 1 , 10, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (3, 1, 2 , 1, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (4, 2, 1 , 11, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (5, 1, 1 , 13, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (6, 1, 2 , 10, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (7, 2, 1 , 12, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (8, 1, 1 , 20, '2020-07-08 00:00:00');
INSERT INTO skill_scores VALUES (9, 1, 2 , 15, '2020-07-08 00:00:00');
INSERT INTO skill_scores VALUES (10, 2, 1 , 17, '2020-07-08 00:00:00');
WITH cte AS (
SELECT id, player_id, skill_id,
FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) score,
FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) - FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at ASC) gain,
ROW_NUMBER() OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) rn
FROM skill_scores
WHERE created_at BETWEEN #current_date - INTERVAL #interval DAY AND #current_date
)
SELECT cte.player_id, skills.name, cte.score, cte.gain
FROM cte
JOIN skills ON skills.id = cte.skill_id
WHERE rn = 1
ORDER BY player_id, name;
fiddle
Ps. I don't understand where gain=15 is taken for 31-day period - the difference between '2020-07-08 00:00:00' and '2020-06-06 00:00:00' is 32 days.
Well i think you need a (temporary) table for this. I will call it "player_skill_gains". Its basically the players skills ordered by created_at and with an auto_incremented id:
CREATE TABLE player_skill_gains
(`id` int PRIMARY KEY AUTO_INCREMENT NOT NULL
, `player_id` int
, skill_id int
, score int
, created_at date)
;
INSERT INTO player_skill_gains(player_id, skill_id, score, created_at)
SELECT player_skills.player_id AS player_id
, player_skills.skill_id
, SUM(player_skills.score) AS score
, player_skills.created_at
FROM player_skills
GROUP BY player_skills.id, player_skills.skill_id, player_skills.created_at
ORDER BY player_skills.player_id, player_skills.skill_id, player_skills.created_at ASC;
Using this table we can relatively easily select the last skill for each row (id-1). Using this we can calculate the gains:
SELECT player_skill_gains.player_id, skills.name, player_skill_gains.score
, player_skill_gains.score - IFNULL(bef.score,0) AS gain
, player_skill_gains.created_at
FROM player_skill_gains
INNER JOIN skills ON player_skill_gains.skill_id = skills.id
LEFT JOIN player_skill_gains AS bef ON (player_skill_gains.id - 1) = bef.id
AND player_skill_gains.player_id = bef.player_id
AND player_skill_gains.skill_id = bef.skill_id
For the different queries you want to have (24 hours, 7 days, etc.) you just have to specify the needed where-part for the query.
You can see all this in action here: http://sqlfiddle.com/#!9/1571a8/11/0

Right join / inner join / multiselect [MYSQL] TABLE RESULTS

I have a big trouble to find a correct way to select a column from another table, and show one results that would contain two tables in the same time.
First table:
id | times | project_id |
12 | 12.24 | 40 |
13 | 13.22 | 40 |
14 | 13.22 | 20 |
15 | 12.22 | 20 |
16 | 13.30 | 40 |
Second table:
id | times | project_id |
32 | 22.24 | 40 |
33 | 23.22 | 40 |
34 | 23.22 | 70 |
35 | 22.22 | 70 |
36 | 23.30 | 40 |
I expect to select all the times from the first table for project_id =40, and join to this times from the second table for the same project_id =40.
The results should be like this below:
id | time | time | project_id |
12 | 12.24 | 22.24 | 40 |
13 | 13.22 | 23.22 | 40 |
16 | 13.30 | 23.30 | 40 |
You need to use UNION ALL between those 2 tables otherwise you will get incorrect results. Once you have all the rows together then you can use variables to carry over "previous values" such as shown below and demonstrated at this SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `times` decimal(6,2), `project_id` int)
;
INSERT INTO Table1
(`id`, `times`, `project_id`)
VALUES
(12, 12.24, 40),
(13, 13.22, 40),
(14, 13.22, 20),
(15, 12.22, 20),
(16, 13.30, 40)
;
CREATE TABLE Table2
(`id` int, `times` decimal(6,2), `project_id` int)
;
INSERT INTO Table2
(`id`, `times`, `project_id`)
VALUES
(32, 22.24, 40),
(33, 23.22, 40),
(34, 23.22, 70),
(35, 22.22, 70),
(36, 23.30, 40)
;
Query 1:
select
project_id, id, prev_time, times
from (
select
#row_num :=IF(#prev_value=d.project_id,#row_num+1,1) AS RowNumber
, d.*
, IF(#row_num %2 = 0, #prev_time, '') prev_time
, #prev_value := d.project_id
, #prev_time := times
from (
select `id`, `times`, `project_id` from Table1
union all
select `id`, `times`, `project_id` from Table2
) d
cross join (select #prev_value := 0, #row_num := 0) vars
order by d.project_id, d.times
) d2
where prev_time <> ''
Results:
| project_id | id | prev_time | times |
|------------|----|-----------|-------|
| 20 | 14 | 12.22 | 13.22 |
| 40 | 13 | 12.24 | 13.22 |
| 40 | 32 | 13.30 | 22.24 |
| 40 | 36 | 23.22 | 23.3 |
| 70 | 34 | 22.22 | 23.22 |
Note: MySQL doe snot currently support LEAD() and LAG() functions when this answer was prepared. When MySQL does support these that approach would be simpler and probably more efficient.
select
d.*
from (
select
d1.*
, LEAD(times,1) OVER(partition by project_id order by times ASC) next_time
from (
select id, times, project_id from Table1
union all
select id, times, project_id from Table2
) d1
) d
where next_time is not null

Select and show according to the year/monts

can you help me to select, and sum all the values from the table, and show them according to the months/this year?
Table looks as follow"
| ID | value | reg_date |
| 1 | 2 | 2017-01-11 02:26:22 |
| 2 | 2 | 2017-03-12 12:22:23 |
| 3 | 2 | 2017-04-13 08:26:33 |
| 4 | 3 | 2017-04-15 12:26:16 |
| 5 | 5 | 2017-05-15 19:26:13 |
| 6 | 2 | 2017-06-14 17:12:16 |
| 7 | 6 | 2017-07-12 14:26:16 |
| 8 | 1 | 2015-09-11 13:23:16 |
| 9 | 1 | 2016-09-05 12:26:34 |
| 10 | 1 | 2017-12-11 19:11:45 |
And I would like to get something like:
| value | reg_date |
| 2 | 2017-01 |
| 0 | 2017-02 |
| 2 | 2017-03 |
| 5 | 2017-04 |
| 5 | 2017-05 |
| 2 | 2017-06 |
| 6 | 2017-07 |
| 0 | 2017-08 |
| 2 | 2017-09 |
| 0 | 2017-10 |
| 0 | 2017-11 |
| 1 | 2017-12 |
I tried following approach to solve your problem:
--created sample table datte which will contain following records
create table datte (id int identity(1,1), value int, reg_date datetime);
insert into datte(value,reg_date) values (2,'2017-01-11 02:26:22');
insert into datte(value,reg_date) values (2,'2017-03-12 12:22:23');
insert into datte(value,reg_date) values (2,'2017-04-13 08:26:33');
insert into datte(value,reg_date) values (3,'2017-04-15 12:26:16');
insert into datte(value,reg_date) values (5,'2017-05-15 19:26:13');
insert into datte(value,reg_date) values (2,'2017-06-14 17:12:16');
insert into datte(value,reg_date) values (6,'2017-07-12 14:26:16');
insert into datte(value,reg_date) values (1,'2015-09-11 13:23:16');
insert into datte(value,reg_date) values (1,'2016-09-05 12:26:34');
insert into datte(value,reg_date) values (1,'2017-12-11 19:11:45');
then i created a table type to store the year list
create type [values] as table(
[year] int
);
You can also use temp table here.
-- create temp table to store the output
create table #tempdate([year] varchar(1000), [count] int);
--create a variable of table type values
declare #years as [values] ,
--create following variable to process the data
#year int, #minyear int,#maxyear int;
--insert years in table type variable #years
insert into #years
select YEAR(reg_date) from datte
group by YEAR(reg_date)
--store min year and max year value in variable for processing
select #minyear = min([year]) from #years;
select #maxyear = max([year]) from #years;
-- logic to create query to get desired output
while(#minyear <= #maxyear)
begin
SET #YEAR = #minyear;
IF EXISTS(SELECT 1 FROM DATTE WHERE REG_DATE LIKE '%'+CONVERT(VARCHAR,#YEAR)+'%')
BEGIN
--insert the yearly in temp table
insert into #tempdate
select cast(convert(varchar,#year) as varchar) + '-' +cast(m.number as varchar) , isnull(total_qty,0) as total_qty
from (
select number
from master.dbo.spt_values
where type = 'P' and number between 01 and 12
) m
left join (
select MONTH(reg_date) as mth, SUM(value) as total_qty
from datte
where YEAR(reg_date) = #year
group by MONTH(reg_date)
) s on m.number = s.mth ;
END
SET #MINYEAR = #MINYEAR + 1;
end
--print the data
select [count],[year] from #tempdate
hope this helps,
Jai
You can format how date is displayed and group by this results
SELECT count(ID), DATE_FORMAT(reg_date, "%Y-%m") AS rd FROM tablename GROUP BY rd
Also works something like:
SELECT start_date,
IFNULL(TIME_FORMAT(SEC_TO_TIME (sum(value)),'%l.%i' ),0)
as time01 FROM table WHERE YEAR(start_date) = YEAR(CURDATE())
and MONTH(start_date) = 01
SELECT start_date,
IFNULL(TIME_FORMAT(SEC_TO_TIME (sum(value)),'%l.%i' ),0)
as time02 FROM table WHERE YEAR(start_date) = YEAR(CURDATE())
and MONTH(start_date) = 02
....
Thanks guys a lot.