how to create a month name as a column for a date range for previous month to next 5 months dynamically in sql server - sql-server-2008

How to create a month name as a column for a date range for previous month to next 5 months dynamically in sql server.
I want retrive 6 months data (previous month to next 5 months i.e August 2018 to Jan 2019) based on current month.
suppose if I run same query next octomber month that time 6 months data should be sept 2018 to feb 2019 and months names should be consider as column
names dynamicaly.
table :
CREATE TABLE [dbo].[empproj](
[projectname] [varchar](50) NULL,
[empname] [varchar](50) NULL,
[startdate] [date] NULL,
[enddate] [date] NULL,
[projectstatus] [numeric](18, 2) NULL
)
GO
INSERT [dbo].[empproj] ([projectname], [empname], [startdate], [enddate], [projectstatus]) VALUES (N'p1', N'e1', CAST(N'2018-04-01' AS Date), CAST(N'2018-12-31' AS Date), CAST(1.00 AS Numeric(18, 2)))
go
INSERT [dbo].[empproj] ([projectname], [empname], [startdate], [enddate], [projectstatus]) VALUES (N'p1', N'e5', CAST(N'2014-02-01' AS Date), CAST(N'2019-01-31' AS Date), CAST(0.25 AS Numeric(18, 2)))
GO
INSERT [dbo].[empproj] ([projectname], [empname], [startdate], [enddate], [projectstatus]) VALUES (N'p2', N'e1', CAST(N'2017-01-01' AS Date), CAST(N'2019-03-30' AS Date), CAST(0.75 AS Numeric(18, 2)))
GO
based on the above data I want output like below as per current month run the query.
projectname |empname | August2018| September2018|October2018|November2018| December2018|January2019
p1 |e1 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |0.0
p1 |e5 | 0.25 | 0.25 | 0.25 | 0.25 |0.25 |0.25
p2 |e1 | 0.75 | 0.75 | 0.75 |0.75 | 0.75 |0.75
if run same query in the next month (october) then result should come like below.
projectname |empname | September2018|October2018|November2018| December2018|January2019 |February2019
p1 |e1 | 1.0 | 1.0 | 1.0 | 1.0 |0.0 |0.0
p1 |e5 | 0.25 | 0.25 | 0.25 |0.25 |0.25 |0.0
p2 |e1 | 0.75 | 0.75 |0.75 | 0.75 |0.75 |0.75
I tried like below :
declare #start DATE = (select DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0))
declare #end DATE = (select DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+4, 0))
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(month,1,date)
from months
where DATEADD(month,1,date)<=#end
)
select Datename(month,date)months from months
I have got stuck with logic.
How can I write a query to achieve this task month names dynamic column for 6 months data in sql server.

CREATE TABLE #temp(
[months] varchar NULL,
)
declare #i int
set #i=-1
while(#i<5)
begin
insert into #temp values (Datename(month,DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+#i, 0)))
set #i=#i+1
end
select * from #temp

Related

Calculating a 7 day average

I have this query.
CREATE TABLE ip_logs (
`ip_address` VARCHAR(11),
`start_date` VARCHAR(11),
`end_date` VARCHAR(11),
`time_stamp`, VARCHAR(20),
`mappings`, INTEGER,
`loc_id` INTEGER
);
INSERT INTO ip_logs
(`ip_address`,`start_date`,`end_date`,`time_stamp`,`mappings`, `loc_id`)
VALUES
('120.2.53.21','2020-01-03','2020-01-09','2020-01-03 14:33','2', '5'),
('198.3.222.2','2020-01-03','2020-01-14','2020-01-03 7:53', '7','4'),
('108.4.213.3','2020-01-04','2020-01-07','2020-01-04 12:13','3', '4'),
('128.5.173.4','2020-01-07','2020-02-15','2020-01-07 8:29', '12','3'),
('110.6.432.5','2020-01-07','2020-03-01','2020-01-07 11:45','4', '2'),
('198.7.222.6','2020-01-10','2020-01-14', '2020-01-10 17:32','8', '1'),
('118.8.113.7','2020-01-10','2020-01-19','2020-01-10 20:52','10', '4'),
('106.1.212.9','2020-02-24','2020-03-30','2020-02-24 10:08','5', '1');
I want to generate the 7 day average of the mappings column. For example, I want to calculate the average mappings for each day (from 2020-01-03 - 2020-01-10).
Results:
time_stamp | avg_mapping
2020-01-03 | 4.5 (2 + 7) /2
2020-01-04 | 3
2020-01-07 | 8 (12 +4)/2
2020-01-10 | 9
Then return avg(avg_mapping)
I don't see how your question relates to a rolling average. From your data and results, it seems like you just want aggregation and avg():
select date(time_stamp) as time_stamp_day, avg(mapping) as avg_mapping
from ip_logs
group by date(time_stamp)
If you want the average of column avg_mapping in the resultset (which is not the same thing as an overall average of mapping), then you can add another level of aggregation:
select avg(avg_mapping) as avg_avg_mapping
from (
select avg(mapping) as avg_mapping
from ip_logs
group by date(time_stamp)
) l

Get the last row that was left outside of a timeframed SQL query

Is there a way to write a SQL query with a time-frame condition to include the latest row that is outside of the time-frame (besides solutions like counting the size of the result set and querying for the size+1, etc.) ?
Lets say I have a table A, which holds timestamped value changes.
I want to query for all the changes in the last 24 hours (assume that the time when the query was ran on 2019-08-08 00:00:00) - how do I include the last row that isn't included in 24-hours interval, i.e., row #2 (assuming I don't know when it occurred):
CREATE TABLE A(`timeframe` datetime, `value` int);
INSERT INTO A
(`timeframe`, `value`)
VALUES
('2019-06-08 18:00:00', 10),
('2019-06-09 02:00:00', 20),
('2019-07-08 17:00:00', 50),
('2019-07-08 19:00:00', 10),
('2019-07-09 01:35:00', 30),
('2019-07-09 02:00:00', 40);
| timestamp | value |
|------------------|-------|
| 2019-08-06 15:00 | 10 |
| 2019-08-06 23:00 | 20 |
| 2019-08-07 14:00 | 50 |
| 2019-08-07 16:00 | 10 |
| 2019-08-07 22:35 | 30 |
| 2019-08-07 23:00 | 40 |
SELECT value
, time
FROM A
WHERE time >= DATE_SUB(NOW(), INTERVAL 1 DAY)
The result set should include the value changes in last day (rows #3-#6) and the latest row outside the timeframe - row #2.
I'm looking for a generic solution, as the time-frame can changed.
Please try this below script where first part will return records from last 24 hours and the second script will return the latest row before 24 hours. Union all of result should give your expected output-
SELECT value,timeframe
FROM A
WHERE timeframe >= DATE_SUB(NOW(), INTERVAL 1 DAY)
UNION ALL
(
SELECT value,timeframe
FROM A
WHERE timeframe < DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY timeframe DESC
LIMIT 1
);
Assuming you have older rows you can do:
SELECT value, time
FROM A
WHERE a.time >= (SELECT MAX(a2.time)
FROM A a2
WHERE a2.time < DATE_SUB(NOW(), INTERVAL 1 DAY)
);
If there are no older rows, you have a bit of a challenge dealing with NULL. This is one place where ALL is handy:
SELECT value, time
FROM A
WHERE a.time >= ALL (SELECT a2.time
FROM A a2
WHERE a2.time < DATE_SUB(NOW(), INTERVAL 1 DAY)
);

How to calculate time outside of work hours

This seemed pretty straight forward initially, but has proved to be a real headache. Below is my table, data, expected output and SQL Fiddle of where I have got to in solving my problem.
Schema & Data:
CREATE TABLE IF NOT EXISTS `meetings` (
`id` int(6) unsigned NOT NULL,
`user_id` int(6) NOT NULL,
`start_time` DATETIME,
`end_time` DATETIME,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `meetings` (`id`, `user_id`, `start_time`, `end_time`) VALUES
('0', '1', '2018-05-09 04:30:00', '2018-05-09 17:30:00'),
('1', '1', '2018-05-10 06:30:00', '2018-05-10 17:30:00'),
('2', '1', '2018-05-10 12:30:00', '2018-05-10 16:00:00'),
('3', '1', '2018-05-11 17:00:00', '2018-05-12 11:00:00'),
('4', '2', '2018-05-11 07:00:00', '2018-05-12 11:00:00'),
('5', '2', '2018-05-11 04:30:00', '2018-05-11 15:00:00');
What I would like to get from the above is total time worked outside of 09:00 to 17:00, grouped by day and user_id. So the result from the above data would look like:
date | user_id | overtime_hours
---------------------------------------
2018-05-09 | 1 | 05:00:00
2018-05-10 | 1 | 03:00:00
2018-05-11 | 1 | 07:00:00
2018-05-12 | 1 | 09:00:00
2018-05-11 | 2 | 13:30:00
2018-05-12 | 2 | 09:00:00
As you can see the expected results are only summing overtime for each day and user for those hours outside of 9 to 5.
Below is the query and SQL Fiddle of where I am. The main issue comes when the start and ends straddle midnight (or multiple midnight's)
SELECT
SEC_TO_TIME(SUM(TIME_TO_SEC(TIME(end_time)) - TIME_TO_SEC(TIME(start_time)))), user_id, DATE(start_time)
FROM
(SELECT
start_time, CASE WHEN TIME(end_time) > '09:00:00' THEN DATE_ADD(DATE(end_time), INTERVAL 9 HOUR) ELSE end_time END AS end_time, user_id
FROM
meetings
WHERE
TIME(start_time) < '09:00:00'
UNION
SELECT
CASE WHEN TIME(start_time) < '17:00:00' THEN DATE_ADD(DATE(start_time), INTERVAL 17 HOUR) ELSE start_time END AS start_time, end_time, user_id
FROM
meetings
WHERE
TIME(end_time) > '17:00:00') AS clamped_times
GROUP BY user_id, DATE(start_time)
http://sqlfiddle.com/#!9/77bc85/1
Pastebin for when the fiddle decides to flake: https://pastebin.com/1YvLaKbT
As you can see the query grabs the easy overtime with start and ends on the same day, but does not work with the multiple day ones.
If the meeting is going to span across n days, and you are looking to compute "work hours" daywise within a particular meeting; it rings a bell, that we can use a number generator table.
(SELECT 0 AS gap UNION ALL SELECT 1 UNION ALL SELECT 2) AS ngen
We will use the number generator table to consider separate rows for the individual dates ranging from the start_time to end_time. For this case, I have assumed that it is unlikely that meeting will span across more than 2 days. If it happens to span more number of days, you can easily extend the range by adding more UNION ALL SELECT 3 .. to the ngen Derived Table.
Based on this, we will determine "start time" and "end time" to consider for a specific "work date" in an ongoing meeting. This calculation is being done in a Derived Table, for a grouping of user_id and "work date".
Afterwards, we can SUM() up "working hours" per day of a user using some maths. Please find the query below. I have added extensive comments to it; do let me know if anything is still unclear.
Demo on DB Fiddle
Query #1
SELECT
dt.user_id,
dt.wd AS date,
SEC_TO_TIME(SUM(
CASE
/*When both start & end times are less than 9am OR more than 5pm*/
WHEN (st < TIME_TO_SEC('09:00:00') AND et < TIME_TO_SEC('09:00:00')) OR
(st > TIME_TO_SEC('17:00:00') AND et > TIME_TO_SEC('17:00:00'))
THEN et - st /* straightforward difference between the two times */
/* atleast one of the times is in 9am-5pm block, OR,
start < 9 am and end > 5pm.
Math of this can be worked out based on signum function */
ELSE GREATEST(0, TIME_TO_SEC('09:00:00') - st) +
GREATEST(0, et - TIME_TO_SEC('17:00:00'))
END
)) AS working_hours
FROM
(
SELECT
m.user_id,
/* Specific work date */
DATE(m.start_time) + INTERVAL ngen.gap DAY AS wd,
/* Start time to consider for this work date */
/* If the work date is on the same date as the actual start time
we consider this time */
CASE WHEN DATE(m.start_time) + INTERVAL ngen.gap DAY = DATE(m.start_time)
THEN TIME_TO_SEC(TIME(m.start_time))
/* We are on the days after the start day */
ELSE 0 /* 0 seconds (start of the day) */
END AS st,
/* End time to consider for this work date */
/* If the work date is on the same date as the actual end time
we consider this time */
CASE WHEN DATE(m.start_time) + INTERVAL ngen.gap DAY = DATE(m.end_time)
THEN TIME_TO_SEC(TIME(m.end_time))
/* More days to come still for this meeting,
we consider the end of this day as end time */
ELSE 86400 /* 24 hours * 3600 seconds (end of the day) */
END AS et
FROM meetings AS m
JOIN (SELECT 0 AS gap UNION ALL SELECT 1 UNION ALL SELECT 2) AS ngen
ON DATE(start_time) + INTERVAL ngen.gap DAY <= DATE(end_time)
) AS dt
GROUP BY dt.user_id, dt.wd;
Result
| user_id | date | working_hours |
| ------- | ---------- | ------------- |
| 1 | 2018-05-09 | 05:00:00 |
| 1 | 2018-05-10 | 03:00:00 |
| 1 | 2018-05-11 | 07:00:00 |
| 1 | 2018-05-12 | 09:00:00 |
| 2 | 2018-05-11 | 13:30:00 |
| 2 | 2018-05-12 | 09:00:00 |
Further Optimization Possibilities:
This query can do away with the usage of subquery (Derived Table) very easily. I just wrote it in this way, to convey the mathematics and process in a followable manner. However, you can easily merge the two SELECT blocks to a single query.
Maybe, more optimization possible in usage of Date/Time functions, as well as further simplification of mathematics in it. Function details available at: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html
Some date calculations are done multiple times, e.g., DATE(m.start_time) + INTERVAL ngen.gap DAY. To avoid recalculation, we can utilize User-defined variables, which will also make the query less verbose.
Make this JOIN condition sargable: JOIN .. ON DATE(start_time) + INTERVAL ngen.gap DAY <= DATE(end_time)

SELECT one from multiple rows based on variable

Guys would you have any idea how to solve that problem?
I've hit a brick wall. I'm writing a code behind a report. Report has to show Colour based on the FinancialYear variable.
Here is a table example:
CompanyID | StartDate | EndDate | ReviewDate | FinancialYear | Colour
46 | 2012-01-18 | 2013-12-17 | 2013-12-15 | 2012 | Red
46 | 2013-12-17 | 1900-01-01 | 2017-03-10 | 2014 | Red
46 | 2011-05-11 | 2012-01-17 | 2011-06-30 | 2014 | Orange
When variable FinancialYear would be 2016, CompanyID should show Colour Red as EndDate is ''
result should show row nnumber 2.
However, if FinancialYear variable would be 2012 I have two rows which would fit the criteria and would need to select the one that greater EndDate or StartDate, Reviewdate.
The result should show row number 1.
Would anyone has an idea how to do it? Thanks for any feedback!
What I've tried so far:
SELECT
CompanyID
, StartDate AS [Traffic Lights Start Date]
, EndDate AS [Traffic Lights End Date]
, ReviewDate AS [Traffic Lights Review Date]
, Category AS [Traffic Lights Colour]
, CASE
WHEN (YEAR(EndDate) <> 1900
AND #FinancialYear BETWEEN (YEAR(DATEADD(DD,0, DATEDIFF(DD,0, DATEADD(MM, -(((12 + DATEPART(M, CAST(StartDate AS DATETIME))) - 7)%12), CAST(StartDate AS DATETIME))))) +1)
AND (YEAR(DATEADD(DD,0, DATEDIFF(DD,0, DATEADD(MM, -(((12 + DATEPART(M, CAST(EndDate AS DATETIME))) - 7)%12), CAST(EndDate AS DATETIME))))) +1)
AND (YEAR(DATEADD(DD,0, DATEDIFF(DD,0, DATEADD(MM, -(((12 + DATEPART(M, CAST(ReviewDate AS DATETIME))) - 7)%12), CAST(ReviewDate AS DATETIME)))))+1) >= #FinancialYear)
THEN '1'
WHEN
(YEAR(EndDate) = 1900
AND YEAR(ReviewDate) > = #FinancialYear)
THEN '1'
ELSE '0'
END AS FinancialYear_TrafficLights
, (YEAR(DATEADD(MONTH,-((DATEPART(MONTH,CAST(StartDate AS DATETIME)) +7) %12),CAST(StartDate AS DATETIME)))+1) AS Actual_Financial_Year
, ROW_NUMBER() OVER(PARTITION BY Company ORDER BY StartDate ASC) AS LatestRow
FROM
CompanyTrafficLightHistory
If you are only after the one row then you are right in that you need to order your dataset and then select top 1. You can change the way the dataset is ordered in the order by clause by adding or removing fields and using desc to specify a descending order (the default is ascending):
declare #t table (CompanyID int
,StartDate date
,EndDate date
,ReviewDate date
,FinancialYear int
,Colour nvarchar(50)
);
insert into #t values
(46,'20120118','20131217','20131215',2012,'Red')
,(46,'20131217','19000101','20170310',2014,'Red')
,(46,'20110511','20120117','20110630',2014,'Orange');
declare #FinancialYear int = 2012;
select top 1 *
from #t
where FinancialYear = #FinancialYear
order by case when EndDate = '19000101' -- Because you want 1900-01-01 to be seen as 'most recent',
then '29990101' -- you need to replace it with a value way into the future.
else EndDate
end desc;

Max Number of Overlapping datetimes, with many start and end times

After some experimenting and some search I found out that this query give me exactly the result I need:
SET #start := '2015-12-12 00:00:00', #end := '2015-12-12 23:59:59';
SELECT Max(simultaneous_people),
Max(simultaneous_event),
boundary
FROM (SELECT Count(id) AS simultaneous_people,
Count(DISTINCT uniqueId) AS simultaneous_event,
boundary
FROM mytable
RIGHT JOIN (SELECT row_begin AS boundary
FROM mytable
WHERE row_begin BETWEEN #start AND #end
UNION
SELECT row_end
FROM mytable
WHERE row_end BETWEEN #start AND #end
UNION
SELECT #start
UNION
SELECT #end
UNION
SELECT Max(boundary)
FROM (SELECT Max(row_begin) AS boundary
FROM mytable
WHERE row_begin <= #start
UNION ALL
SELECT Max(row_end)
FROM mytable
WHERE row_end <= #end) t) t
ON row_begin <= boundary
AND boundary < row_end
WHERE row_status = 1
GROUP BY boundary) t;
Which are the maximum number of overlapping time periods in the same time.
But I need this information to be extracted between many time intervals, for example 10.
I can't find out how to extract this information inside a calendar built in run time with a query like:
SELECT DATE_SUB(#date, INTERVAL #num MINUTE) AS endSample,
DATE_SUB(#date, INTERVAL #num:=#num+#lenght MINUTE) AS startSample
FROM
mytable,
(SELECT #num:=0) num
LIMIT 10;
I'm using MySQL and unfortunately I cannot store any data/table/procedure/view on this Database.
If someone have an idea of how can I merge those two queries in an efficient way will be great.
Thanks!
Update:
My schema:
CREATE TABLE mytable (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
uniqueId INT,
row_status INT,
row_begin DATETIME,
row_end DATETIME
);
And some data just for one day, to test only the hour granularity:
INSERT INTO mytable (uniqueId, row_status, row_begin, row_end)
VALUES
(1, 1, '2015-12-12 08:00:00', '2015-12-12 12:00:00'),
(1, 1, '2015-12-12 08:00:00', '2015-12-12 14:00:00'),
(1, 1, '2015-12-12 08:00:00', '2015-12-12 14:00:00'),
(2, 1, '2015-12-12 13:00:00', '2015-12-12 14:00:00'),
(2, 1, '2015-12-12 13:00:00', '2015-12-12 16:00:00'),
(3, 1, '2015-12-12 09:00:00', '2015-12-12 12:00:00'),
(3, 0, '2015-12-12 08:00:00', '2015-12-12 16:00:00');
I just added the SQL Fiddle.
Here I have to set manually each date range with variables, but I need to be able to set a 'calendar', and be able to specify if the calendar is day by day, hour by hour or minute by minute, by tuning the #length variable.
The first part of my solution is taken from this answer.
My desired output, with hour as time granularity, will be something like:
start_sample | end_sample | MAX(simultaneous_people) | MAX(simultaneout_event)
2015-12-12 08:00:00 | 2015-12-12 08:59:59 | 3 | 1
2015-12-12 09:00:00 | 2015-12-12 09:59:59 | 4 | 2
2015-12-12 10:00:00 | 2015-12-12 10:59:59 | 4 | 2
2015-12-12 11:00:00 | 2015-12-12 11:59:59 | 4 | 2
2015-12-12 12:00:00 | 2015-12-12 12:59:59 | 2 | 1
2015-12-12 13:00:00 | 2015-12-12 13:59:59 | 4 | 2
2015-12-12 14:00:00 | 2015-12-12 14:59:59 | 1 | 1
...
But if I need to change granularity to days, with this data, I will obtain
start_sample | end_sample | MAX(simultaneous_people) | MAX(simultaneout_event)
2015-12-12 00:00:00 | 2015-12-12 23:59:59 | 4 | 2
2015-12-13 00:00:00 | 2015-12-12 23:59:59 | 0 | 0
...
The solution was easier than I thought (hopefully is right as it seems until now).
I'll add some extra boundaries using my calendar generator, in order to avoid empty holes for my final data (a day with no entries will not be shown otherwise):
...
UNION
SELECT #start
UNION
SELECT #end
UNION
SELECT DATE_SUB(#date, INTERVAL #num:=#num+#lenght MINUTE)
FROM
mytable,
(SELECT #num:=0) num
LIMIT 10
...
Then I have to group by the date_part (in postgres) ora DATE_FORMAT (in MySQL), more interesting to me (I must add a variable to be paired with #length), not the whole boundaries as I was doing before, and the external query will become:
SELECT Max(simultaneous_people),
Max(simultaneous_event),
DATE_FORMAT(boundary,'%Y%m%d')
FROM (...) as t
GROUP BY DATE_FORMAT(boundary,'%Y%m%d');
I hope this may help someone else, it took a while to reach this point.
This query is really heavy with a lot of data, so the more you cut/segment (vertically and horizontally) the data, the better it will perform, the 'WHERE row_status = 1' should be added everywhere a bound is obtained throud the Unions.