MySQL select field from one table with different conditions - mysql

I need your help with mysql query.
I have one table 'metrics':
create table metrics
(
guid binary(16) not null
primary key,
entry_guid binary(16) not null,
customer_guid binary(16) null,
metrics varchar(30) not null,
value int not null,
`_created` timestamp null,
`_updated` timestamp null
);
So, I'm trying to do something like this:
SELECT t1.entry_guid as entry_guid, SUM(t1.`value`) as last_week, SUM(t2.`value`) as last_month
FROM metrics as t1, metrics as t2
WHERE t1.`_created` > NOW() - INTERVAL 7 DAY OR t2.`_created` > NOW() - INTERVAL 1 MONTH
GROUP BY t1.entry_guid
But in result i get identical strange result of using SUM() function
entry_guid last_week last_month
1 4613 4613
2 207 207
3 6003 6003
4 9108 9108
Moreover result of SUM() func is strange, because I have only 300 rows and 'value' field in each row is equal 1, so max sum must be very little.
So, the query
SELECT t1.entry_guid as entry_guid, SUM(t1.`value`) as sum
FROM metrics as t1
GROUP BY t1.entry_guid
gives me
entry_guid sum
0x34303535636637643538396665633265 21
0x34313830656231666665393131326635 21
0x34336537663033653963303437356165 1
0x34363061653730313738313263386264 44
I need to get SUM('value') from one table, but with different conditions.
Can you show me how? Thank you in advance.

Use conditional aggregation:
SELECT m.entry_guid as entry_guid,
SUM(CASE WHEN m.`_created` > NOW() - INTERVAL 7 DAY THEN t1.`value` ELSE 0 END) as last_week,
SUM(CASE WHEN m.`_created` > NOW() - INTERVAL 1 MONTH THEN t2.`value` ELSE 0 END) as last_month
FROM metrics m
GROUP BY m.entry_guid;

Related

In mysql how to get users that have no event X in past 14 days and neither in the future

Consider I have the following table and current date is 2022-09-01:
Requirement: I want to get all users that have no event_name like cbt care in the past 14 days and onwards into the future.
I have this query:
SELECT * FROM test_table
WHERE event_name LIKE "%cbt care%"
AND start_date <= DATE_SUB(NOW(), INTERVAL 14 DAY)
;
Which returns:
The issue is that user_id = x does have a cbt care event in 2022-09-10 which is 9 days ahead of current date (2022-09-01).
How to return only users satisfy requirement posted above?
SELECT user_id,
COUNT(CASE WHEN event_name LIKE '%cbt care%' AND start_date
> CURDATE() - INTERVAL 14 day THEN 1 END) AS count_recent
FROM test_table
GROUP BY user_id
HAVING count_recent = 0;
https://www.db-fiddle.com/f/64j7L1VZsVdLYqmcQ2NrvV/0
The CASE expression returns 1 for each row with the conditions you described (a specific event name and a start date after the date 14 days ago, which includes all of the future dates too). For rows that don't match that condition, the CASE returns NULL. There's an implicit ELSE NULL in any CASE expression.
COUNT(<expr>), like many set functions, ignores NULLs. It will only count the occurrences of non-NULL values. So if the count returns 0, then the CASE returned only NULLs, which means there are no recent or future 'cbt care' events for that user.
select id
,user_id
,event_name
,start_date
from (
select *
,count(case when abs(datediff(curdate(),start_date)) <= 14 and event_name like "%cbt care%" then 1 end) over (partition by user_id) as cw
from t
) t
where cw = 0
id
user_id
event_name
start_date
0
a
cbt care
2022-06-01 20:00:00
Fiddle

mysql group by week , one month and three recent month

I have a table with column named started_at
I want to get statistics of new inserted row by last day , week , one month and three month .
the started_at column format is default MySQL timestamp which is string .
before posting this question , I try this querys
SELECT WEEK(`started_at`) , COUNT(*) AS nbr FROM users_in_bots WHERE `bot_id` = 5529 GROUP BY WEEK (`started_at`);
SELECT MONTH(`started_at`), COUNT(*) AS nbr FROM users_in_bots WHERE `bot_id` = 5529 GROUP BY MONTH(`started_at`);
and the result is not what I want .
I want get all statistics with just one query .
the table structure :
CREATE TABLE `users_in_bots` (
`user_id` bigint(20) NOT NULL,
`bot_id` bigint(20) NOT NULL,
`started_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
sample row :
INSERT INTO `users_in_bots` (`user_id`, `bot_id`, `started_at`) VALUES
(2314, 509492849, '2022-02-27 03:59:21'),
(28779, 210686266, '2022-03-03 21:51:38'),
(28779, 503513058, '2022-04-01 12:28:37'),
(28779, 515774720, '2022-03-25 08:25:16'),
(28779, 518099352, '2022-03-22 17:22:38'),
(28779, 519646468, '2022-03-04 22:02:02'),
(84588, 517141146, '2022-03-28 12:36:45'),
(87075, 509498849, '2022-02-27 03:59:21'),
(116264, 210509102, '2022-02-27 00:02:54'),
(116264, 212268136, '2022-02-27 00:29:06');
expected output ( what i wish to use in my application ):
new users in last 24 hour : 42
new users in last week : 532
new users in last month : 4568
and same with 3 and six month and all the time .
You can use conditional aggregation to get the results you want. For example:
SELECT SUM(started_at BETWEEN NOW() - INTERVAL 1 HOUR AND NOW()) last_hour,
SUM(started_at BETWEEN CURDATE() - INTERVAL 1 WEEK AND CURDATE()) last_week,
SUM(started_at BETWEEN CURDATE() - INTERVAL 1 MONTH AND CURDATE()) last_month,
SUM(started_at BETWEEN CURDATE() - INTERVAL 3 MONTH AND CURDATE()) last_3month,
SUM(started_at BETWEEN CURDATE() - INTERVAL 6 MONTH AND CURDATE()) last_6month
FROM users_in_bots
Output (for your sample data as of 2022-04-13):
last_hour last_week last_month last_3month last_6month
0 0 4 10 10
Demo on dbfiddle

Combining data from multiple rows into one

First question on here, so i will try my best to be clear.
I have 2 tables:
"TABLE1" which contains a record for each stock code and a list of attributes.
In TABLE 1 there is just one record for each stock_code
"TABLE2" which contains a log of changes to attributes of products, over time.
"TABLE2" contains the following fields:.
stock_code
stock_attribute
old_value
new_value
change_date
change_time
TABLE 2 has multiple entries ofr each stock_code.
Every time a stock item is change, another entry is made in Table2, with the attribute that has changed, the change date, time, old value and new value.
I want to create a query which will result in a table that has one record for each stock_code (from TABLE 1), and a column for each week over past year, with the value in each field being the last recorded "new_val" for that week (From TABLE 2)
I have tried
SELECT a.`stcode`, b.`week1`, b.`week2`, b.`week3`, b.`week4` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) THEN newval END)week1,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 356 DAY) AND DATE_SUB(CURDATE(),INTERVAL 350 DAY) THEN newval END)week2,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 349 DAY) AND DATE_SUB(CURDATE(),INTERVAL 343 DAY) THEN newval END)week3,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 342 DAY) AND DATE_SUB(CURDATE(),INTERVAL 336 DAY) THEN newval END)week4,
(etc
etc
etc
FROM (SELECT * from TABLE 2 ORDER BY "chngdate" DESC, "chngtime" DESC )as sub) as b ON b.stcode = s.stcode
ORDER BY stcode ASC
The trouble is with this, i get multiple lines for a stock_code which has mutliple entries....
for example, for stock_code abc123 the result i get is
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 null null 4 null null null
abc123 2 null null null null null
abc123 null null null null 3 null
what i WANT is this:
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 2 null 4 null 3 null
I have also tried the following, but teh query took so long, it never finished (there were 52 derived tables!)
SELECT a.`stcode`, w1.`new_value`, w2.`new_value`, w3.`new_value`, w4.`new_value` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
LEFT JOIN (SELECT stcode, depot, fieldname, chngdate, chngtime, newval from STDepotAmendmentsLog WHERE chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) ORDER BY "chngdate" DESC, "chngtime" DESC)as w1 on s.stcode = w1.stcode
etc for week 2, 3, 4 etc etc
You could do the following:
Find the greatest date for each "week"
Find the rows corresponding to those dates
Use conditional aggregation to convert rows into columns
Here is a rough outline of the code. It assumes that e.g. if today is 2020-03-03 then week 52 is from 2020-02-26 to 2020-03-03. Adjust if necessary:
SELECT t.stock_code
, MAX(CASE WHEN weeknum = 51 THEN new_value END) AS week01
, MAX(CASE WHEN weeknum = 50 THEN new_value END) AS week02
, MAX(CASE WHEN weeknum = 1 THEN new_value END) AS week51
, MAX(CASE WHEN weeknum = 0 THEN new_value END) AS week52
FROM table2 AS t
JOIN (
SELECT stock_code
, DATEDIFF(CURRENT_DATE, change_date) div 7 AS weeknum -- count multiples of 7
, MAX(change_date) AS greatest_date
GROUP BY stock_code, weeknum
FROM table2
) AS a ON t.stock_code = a.stock_code AND t.change_date = a.greatest_date
GROUP BY t.stock_code

MySQL multiple conditions - concatenate or subquery?

We had a MySQL query that selected records and was required to also show Sold records for 60days before being removed from the shown results.
Logic was such that after changing a record from some invStatus to 1 which equals sold, we would filter it from our records after 60 days.
The problem is that if the filter is applied in a single query even active records are getting dropped if they are not updated within that 60 day window.
So how to select all records and then only filter a subset of those records based on date interval?
Should I select ALL the Ids and then filter those that are status sold and then apply the date interval in a subquery or run two queries and concatenate the two?
UPDATE:
SQLFiddle created that shows (10) records.
The goal is to not lose any invStatus = 0 but filter invStatus records that = 1 by NOT returning them if Update_date is older than 60 days from today
There are (7) records that have a invStatus = 0 (not Sold)
and (3) records that have invStatus = 1 (Sold)
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY)
ORDER BYtblinventory.invId
results in (6) records
5 which are invStatus = 0
1 that is invStatus = 1
Should be
(7) invStatus 0’s as they ALL should be present
(1) invStatus = 1 that is within 60 days
SQLFiddle schema:
CREATE TABLE IF NOT EXISTS `tblinventory` (
`invId` int(4) NOT NULL,
`Update_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`invStatus` int(3) NOT NULL DEFAULT '0'
) DEFAULT CHARSET=utf8;
INSERT INTO `tblinventory` (`invId`,`Update_date`,`invStatus`)
VALUES
("3777","2019-08-06 00:00:00","1"),
("3782","2019-08-30 00:00:00","0"),
("3820","2019-04-04 00:00:00","0"),
("3821","2019-03-21 00:00:00","1"),
("3835","2019-02-20 00:00:00","0"),
("3836","2019-06-30 00:00:00","1"),
("4035","2019-08-25 00:00:00","0"),
("4036","2019-09-01 00:00:00","0"),
("4037","2019-09-01 00:00:00","0"),
("4038","2019-09-01 00:00:00","0");
Query:
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY) AND invStatus = 1
ORDER BY
tblinventory.invId
Comparative Query:
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY)
https://www.db-fiddle.com/f/3KuYDHgYaNtaB8mMuatrz2/0
I guess you need a simple UNION ALL clause. Presumably invStatus <> 1 means non sold entities, You can try below query -
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE `invStatus` = 0 OR (Update_date >= NOW() - INTERVAL 60 DAY)
ORDER BY
tblinventory.invId

Calculate average from data only stored in MySQL on change

We have a database where we only store data values and timestamp (as a row in MySQL) when the data value changes. There are therefore no fixed interval between the timestamps in the database. The table looks something like this:
MySQLTimestamp Data
2014-10-01 18:01 1
2014-10-03 16:13 2
2014-10-07 15:45 1
2014-10-09 10:08 3
THE PROBLEM: We want to calculate the average over time assuming that the data value continues to be i.e. 2 until the value changes on the next row in the database.
A simple AVG won't do the trick because it will only calculate the average between the number of rows. This would not take in to count that a value can continue to be the same value for a long period of time before the next row states a change in data value.
Would really appreciate your help!
Self join and calculate the duration of date or time as weight to Data.
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) as next_tm,
datediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
cross join
your_table t2
group by
t1.MySQLTimestamp,
t1.data
) as t
Use datediff to calculate days interval as weight. If you want hours or minutes as interval, you could use timediff and transfer the result to hours, minutes or seconds.
Here is the sql fiddle demo and the results:
AVG_OVER_DATE AVG_OVER_HOUR AVG_OVER_SEC
1.5 1.51887 1.5189
Another version by left join:
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(t2.MySQLTimestamp) as next_tm,
datediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
left join
your_table t2
on
t1.MySQLTimestamp<t2.MySQLTimestamp
group by
t1.MySQLTimestamp,
t1.data
) as t