number of visitors gained and lost comparing with previous day - sql - mysql

CREATE TABLE visitors (
Date DATE,
visitor VARCHAR(20));
INSERT INTO visitors VALUES
("2019-10-01", "v1"),
("2019-10-01", "v2"),
("2019-10-01", "v3"),
("2019-10-02", "v2"),
("2019-10-03", "v2"),
("2019-10-03", "v4"),
("2019-10-03", "v5");
please find Find number_of_visitors_per_date, number_of_visitors gained comparing with previous day, and number_of_visitors lost comparing with previous day. The expected results for the above table should be:
Date | number_of_visitors | number_of_visitors_gained | number_of_visitors_lost
2019-10-01 | 3 | 3 | 0
2019-10-02 | 1 | 0 | 2
2019-10-03 | 3 | 2 | 0
Obviously, the most challenging part is how to get the last two columns. Note, since there is no previous day for the first day, the number_of_visitors_gained is the total number of visitors in the first day, and the number_of_visitors_lost is 0.

If you RDBMS supports window functions, you can aggregate, and then use lag():
select
date,
number_of_visitors,
case
when lag(number_of_visitors) over(order by date) is null
then number_of_visitors
when lag(number_of_visitors) over(order by date) < number_of_visitors
then number_of_visitors - lag(number_of_visitors) over(order by date)
else 0
end number_of_visitors_gained,
case when lag(number_of_visitors) over(order by date) > number_of_visitors
then lag(number_of_visitors) over(order by date) - number_of_visitors
else 0
end number_of_visitors_lost
from (
select date, count(*) number_of_visitors
from visitors
group by date
) t
order by date
Demo on DB Fiddle:
date | number_of_visitors | number_of_visitors_gained | number_of_visitors_lost
:--------- | -----------------: | ------------------------: | ----------------------:
2019-10-01 | 3 | 3 | 0
2019-10-02 | 1 | 0 | 2
2019-10-03 | 3 | 2 | 0

You are looking for aggregation using lead() and lag() window functions:
select date, count(*) as visitors_on_day,
sum(case when prev_vdate is null or prev_vdate <> date - interval 1 day
then 1 else 0
end) as gained_visitors,
sum(case when next_vdate <> date + interval 1 day
then 1 else 0
end) as lost_visitors
from (select v.*,
lag(date) over (partition by visitor) as prev_vdate,
lead(date) over (partition by visitor) as next_vdate
from visitors v
) t
group by date
order by date;

Related

count by person by month between days in mysql

I have a table of absences with 3 columns id, begin_dt, end_dt. I need to give a count of how many id's has at least one day of absence in that month. So for example there is a row as follow:
id begin_dt end_dt
1 01/01/2020 02/02/2020
2 02/02/2020 02/02/2020
my result has to be
month count
01-2020 1
02-2020 2
I thought with a group by on DATE_FORMAT(SYSDATE(), '%Y-%m'), but I don't know how to manage the fact that we had to look for the whole period begin_dt till end_dt
you can find a working creation of table of this example here: https://www.db-fiddle.com/f/rYBsxQzTjjQ9nGBEmeAX6W/0
Schema (MySQL v5.7)
CREATE TABLE absence (
`id` VARCHAR(6),
`begin_dt` DATETIME,
`end_dt` DATETIME
);
INSERT INTO absence
(`id`, `begin_dt`, `end_dt`)
VALUES
('1', DATE('2019-01-01'), DATE('2019-02-02')),
('2', DATE('2019-02-02'), DATE('2019-02-02'));
Query #1
select * from absence;
| id | begin_dt | end_dt |
| --- | ------------------- | ------------------- |
| 1 | 2019-01-01 00:00:00 | 2019-02-02 00:00:00 |
| 2 | 2019-02-02 00:00:00 | 2019-02-02 00:00:00 |
View on DB Fiddle
SELECT DATE_FORMAT(startofmonth, '%Y-%m-01') year_and_month,
COUNT(*) absent_person_count
FROM absence
JOIN ( SELECT DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m-01') startofmonth,
DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m-01') + INTERVAL 1 MONTH - INTERVAL 1 DAY endofmonth
FROM ( SELECT MIN(begin_dt) dt
FROM absence ) startdate,
( SELECT 0 n UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 ) numbers,
( SELECT DATE_FORMAT(MIN(begin_dt), '%Y-%m') mindate,
DATE_FORMAT(MAX(end_dt), '%Y-%m') maxdate
FROM absence ) datesrange
WHERE DATE_FORMAT(dt + INTERVAL n MONTH, '%Y-%m') BETWEEN mindate AND maxdate ) dateslist
ON begin_dt <= endofmonth
AND end_dt >= startofmonth
GROUP BY year_and_month;
fiddle

Generate a weekly calendar based upon Week Start and Week End dates

I'm trying to create a script that would give me the fiscal weeks based upon reference dates of Start Week and End Week
For example, assume that I have table columns with StartWeek and EndWeek such that:
StartWeek | EndWeek
----------|---------
20190603 | 20190609
20190610 | 20190616
20190617 | 20190623
20190624 | 20190630
20190701 | 20190707
...
I would like a script to generate the following:
FiscalDateStart | FiscalDateEnd | NumOfDays | WeekDayStart | WeekDayEnd
----------------|----------------|------------|--------------|-----------
2019-06-05 | 2019-06-09 | 5 | Wednesday | Sunday
2019-06-10 | 2019-06-16 | 7 | Monday | Sunday
2019-06-17 | 2019-06-23 | 7 | Monday | Sunday
2019-06-24 | 2019-06-30 | 7 | Monday | Sunday
2019-07-01 | 2019-07-02 | 2 | Monday | Tuesday
I have found the following logic on Stack:
DECLARE #StartDate DATE = '2019-06-05'
, #EndDate DATE = '2019-07-02'
SET DATEFIRST 1
SET LANGUAGE French;
SET NOCOUNT ON;
select *
from
(Select
WeekNum = Dense_Rank() over (Partition By FY,FM Order By FW)
,StartDate = Min(D) over (Partition By FY,FM,FW )
,EndDate = Max(D) over (Partition By FY,FM,FW )
,NumOfDays = DATEDIFF(DAY, Min(D) over (Partition By FY,FM,FW ), Max(D) over (Partition By FY,FM,FW )) + 1
From
(
/* Establish calendar rules/functions for Fiscal Year (FY), Fiscal Month (FM), Fiscal Week (FW), & Fiscal Day (D) */
Select FY = DatePart(Year,#StartDate)-1+sum(case when convert(varchar(5),#StartDate,101)=convert(varchar(5),D,101) then 1 else 0 end) over (Order By D)
,FM = sum(case when DatePart(Day,D)=DatePart(Day,#StartDate) then 1 else 0 end) over (Order By D)
,FW = sum(case when DatePart(WeekDay,D)=1 then 1 else 0 end) over (Order By D)
,D
From (
/* Generate Fiscal Day (D) rule based on #StartDate & #EndDate */
Select Top (DateDiff(DAY,#StartDate,#EndDate)+1)
D = DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#StartDate)
From master..spt_values n1,master..spt_values n2
) A1
) A
--Order By D
) a
group by
WeekNum, StartDate, EndDate, NumOfDays
The issue is that we're forcing the #StartDate in the above query to be as of 2019-06-05. However, I need the above script to generate the same results, but based upon the #StartDate as of 2019-06-03.
Any help would be appreciated.

Finding total active hours by calculating difference between TimeDate records

I have a table to register users logs every one minute and other activities using DateTime for each user_id
This is a sample data of my table
id | user_id | log_datetime
------------------------------------------
1 | 1 | 2016-09-25 13:01:08
2 | 1 | 2016-09-25 13:04:08
3 | 1 | 2016-09-25 13:07:08
4 | 1 | 2016-09-25 13:10:08
5 | 2 | 2016-09-25 13:11:08
6 | 1 | 2016-09-25 13:13:08
7 | 2 | 2016-09-25 13:13:09
8 | 2 | 2016-09-25 13:14:10
I would like to calculate the total active time on the system
UPDATE: Expected Output
For Example user_id 1 his total available time should be 00:12:00
Since his hours and seconds are same so I'll just subtract last log from previous then previous from next previous and so on then I'll sum all subtracted values
this a simple for
Simply I want to loop through the data from last record to first record with in my range
this is a simple formula I hope that make my question clear
SUM((T< n > - T< n-1 >) + (T< n-1 > - T< n-2 >) ... + (T< n-x > - T< n-first >))
Since user_id 1 his hours and seconds are the same then I'll calculate the minutes only.
(13-10)+(10-7)+(7-4)+(4-1) = 12
user_id | total_hours
---------------------------------
1 | 00:12:00
2 | 00:03:02
I did this code
SET #start_date = '2016-09-25';
SET #start_time = '13:00:00';
SET #end_date = '2016-09-25';
SET #end_time = '13:15:00';
SELECT
`ul1`.`user_id`, SEC_TO_TIME(SUM(TIME_TO_SEC(`dl1`.`log_datetime`))) AS total_hours
FROM
`users_logs` AS `ul1`
JOIN `users_logs` AS `ul2`
ON `ul1`.`id` = `ul2`.`id`
WHERE
`ul1`.`log_datetime` >= CONCAT(#start_date, ' ', #start_time)
AND
`ul2`.`log_datetime` <= CONCAT(#end_date, ' ', #end_time)
GROUP BY `ul1`.`user_id`
But this code Sum all Time not getting the difference. This is the output of the code
user_id | total_hours
---------------------------------
1 | 65:35:40
2 | 39:38:25
How can I calculate the Sum of all difference datetime, then I want to display his active hours every 12 hours (00:00:00 - 11:59:59) and (12:00:00 - 23:59:59) with in selected DateTime Period at the beginning of the code
So the output would look like this (just an dummy example not from given data)
user_id | total_hours | 00_12_am | 12_00_pm |
-------------------------------------------------------
1 | 10:10:40 | 02:05:20 | 08:05:20 |
2 | 04:10:20 | 01:05:10 | 03:05:30 |
Thank you
So you log every minute and if a user is available there is a log entry.
Then count the logs per user, so you have the number of total minutes.
select user_id, count(*) as total_minutes
from user_logs
group by user_id;
If you want them displayed as time use sec_to_time:
select user_id, sec_to_time(count(*) * 60) as total_hours
from user_logs
group by user_id;
As to conditional aggregation:
select
user_id,
count(*) as total_minutes,
count(case when hour(log_datetime) < 12 then 1 end) as total_minutes_am,
count(case when hour(log_datetime) >= 12 then 1 end) as total_minutes_pm
from user_logs
group by user_id;
UPDATE: In order to count each minute just once count distinct minutes, i.e. DATE_FORMAT(log_datetime, '%Y-%m-%d %H:%i'). This can be done with COUNT(DISTINCT ...) or with a subquery getting distinct values.
The complete query:
select
user_id,
count(*) as total_minutes,
count(case when log_hour < 12 then 1 end) as total_minutes_am,
count(case when log_hour >= 12 then 1 end) as total_minutes_pm
from
(
select distinct
user_id,
date_format(log_datetime, '%y-%m-%d %h:%i') as log_moment,
hour(log_datetime) as log_hour
from.user_logs
) log
group by user_id;

MySQL - 2 sums and 2 WHERE statements from one table into 2 columns

maybe this is an easy answer for someone but I searched and searched and failed.
Here's what I have: 1 Table that contains the following:
id, int
year, int
month, int
day, int
revenue, float
sub_category, text
What I'm trying to get is 1 SQL statement that returns 1 table with 3 columns to hold the sum of the revenue per month and sub_category
-------------------------------
| Month | revcomp1 | revcomp2 |
-------------------------------
| 01 | sumcat1 | sumcat2 |
-------------------------------
| 02 | sumcat1 | sumcat2 |
-------------------------------
| .. | .. | .. |
Here's the SQL Statement I was running:
SELECT
month,
cast(sum(rev *-1) as decimal(10,2)) as REVCOMP1,
0 as REVCOMP2
FROM
tbl_data
WHERE
year='2014' and sub_category='comp1'
GROUP BY month, sub_category DESC
UNION
SELECT
month,
0 as REVCOMP1
cast(sum(rev *-1) as decimal(10,2)) as REVCOMP2,
FROM
tbl_data
WHERE
year='2014' and sub_category='comp2'
GROUP BY month, sub_category DESC
But this doesn't really return the sums of a given month in one line...
Any help is really appreciated,
Thanks
You can do this using conditional aggregation:
select month,
cast(sum(case when sub_cateogry = 'comp1' then rev *-1 end) as decimal(10,2)) as REVCOMP1,
cast(sum(case when sub_category = 'comp2' then rev *-1 end) as decimal(10,2)) as REVCOMP2
from tbl_data t
where year = '2014'
group by month;

Weekly report behalf of specific date field

I am using this query for weekly reporting but can not found a way like this
week_number | week_startdate | organization_1 | organization_2
---------------------------------------------------------------
1 | 2013-01--05 |count(date) like 4,24,etc_ | count(date) like 4,24,etc_
SQL:
SELECT WEEK(signed_date) AS week_name, signed_date AS Week_Starting,
YEAR(signed_date), WEEK(signed_date), COUNT(*)
FROM business
WHERE YEAR(signed_date) = YEAR(CURDATE())
GROUP BY CONCAT(YEAR(signed_date), '/', WEEK(signed_date))
ORDER BY YEAR(signed_date), WEEK(signed_date
SAMPLE DATA:
signed_date | organization_id
01-01-2013 | 1
02-01-2013 | 1
03-01-2013 | 2
In 1 week organization_1 have 2 signed & organization_2 has 1 signed.
You should use case within count or sum:
SELECT WEEK(signed_date) AS week_name, signed_date AS Week_Starting,
YEAR(signed_date), WEEK(signed_date),
SUM(CASE WHEN organization_id=1 THEN 1 ELSE 0 END) as organization_1,
SUM(CASE WHEN organization_id=2 THEN 1 ELSE 0 END) as organization_2
FROM business
WHERE YEAR(signed_date) = YEAR(CURDATE())
GROUP BY CONCAT(YEAR(signed_date), '/', WEEK(signed_date))
ORDER BY YEAR(signed_date), WEEK(signed_date);
http://sqlfiddle.com/#!2/587ad/3