t-sql function to determine someone category - sql

If I am given a date like 1999-07-08 15:49:00 what would be a good function to determine whether is an AM shift, PM shift or a NOC shift?
--AM: 06:45:00 - 14:44:59
--PM: 14:45:00 - 22:59:59
--NOC: 23:00:00 - 06:44:59
Here is my attempt but then I noticed a bug
ALTER FUNCTION [dbo].[DateToNocShift]
(
-- Add the parameters for the function here
#DummyDate DATETIME
)
RETURNS VARCHAR(10)
AS
BEGIN
-- Declare the return variable here
DECLARE #Shift VARCHAR(10)
DECLARE #DateValues TABLE
(
RawDate DATETIME,
HourNow int,
MinuteNow int,
TimeHourMinute FLOAT,
Shift VARCHAR(4)
)
INSERT INTO #DateValues
VALUES
(
#DummyDate,
DATEPART(hour,#DummyDate),
cast(DATEPART(minute,#DummyDate)as decimal),
ROUND(DATEPART(hour,#DummyDate) + cast(DATEPART(minute,#DummyDate)as decimal)/60,2),
null
)
UPDATE #DateValues
SET Shift = 'AM'
WHERE TimeHourMinute BETWEEN 6.75 AND 14.74 -- good estimate
UPDATE #DateValues
SET Shift = 'PM'
WHERE TimeHourMinute BETWEEN 14.75 AND 22.99
UPDATE #DateValues
SET Shift = 'NOC'
WHERE TimeHourMinute BETWEEN 23.00 AND 6.74
SELECT #Shift = Shift FROM #DateValues
RETURN #Shift

You don't need so much code, a single CASE statement will do
CREATE FUNCTION [dbo].[DateToNocShift]
(
-- Add the parameters for the function here
#DummyDate DATETIME
)
RETURNS VARCHAR(10)
AS
BEGIN
DECLARE #dateChar varchar(8)
set #dateChar = convert(varchar, #DummyDate, 108)
RETURN CASE
WHEN #dateChar >= '06:45:00' and #dateChar < '14:45:00' then 'AM'
WHEN #dateChar >= '14:45:00' and #dateChar < '23:00:00' then 'PM'
ELSE 'NOC'
END -- CASE
END

Another approach would be to create a table with a row in it for every minute of the day, 1440 rows.
CREATE TABLE ShiftCheck
(
id int,
MyTime datetime not null,
ShiftNumber int not null,
ShiftName char(3) not null
CONSTRAINT [PK_ShiftCheck] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
populate that table with a statement similar to this
; WITH DateIntervalsCTE AS
(
SELECT 0 i, cast('1/1/1900' as datetime) AS Date
UNION ALL
SELECT i + 1, DATEADD(MINUTE, i,cast('1/1/1900' as datetime) )
FROM DateIntervalsCTE
WHERE DATEADD(MINUTE, i, cast('1/1/1900' as datetime) ) < cast('1/2/1900' as datetime)
)
SELECT
ROW_NUMBER() over (order by Date),
Date,
CASE
WHEN convert(varchar, Date, 108) >= '06:45:00' and convert(varchar, Date, 108) < '14:45:00' then 1
WHEN convert(varchar, Date, 108) >= '14:45:00' and convert(varchar, Date, 108) < '23:00:00' then 2
ELSE 3
END,
CASE
WHEN convert(varchar, Date, 108) >= '06:45:00' and convert(varchar, Date, 108) < '14:45:00' then 'AM'
WHEN convert(varchar, Date, 108) >= '14:45:00' and convert(varchar, Date, 108) < '23:00:00' then 'PM'
ELSE 'NOC'
END
FROM DateIntervalsCTE
OPTION (MAXRECURSION 32767);
Then, you just need to join to the ShiftCheck table on a time. There is only a need to calculate what time a shift is in one time, to populate the table.
Scalar Valued Functions, like the one listed in your question are executed for every row in a given query. For example
Select *,[dbo].[DateToNocShift](ShiftDate)
from myTable
the function will be executed for every row in myTable which at a certain point (Saturday morning while you are sleeping) will get very slow. In conclusion, this will eventually become a performance problem and eventually someone will want to see the word 1st, 2nd, 3rd for the shift name. This solution will solve both of those as well as force a look-up instead of a calculation.
*If that is not accurate enough, put a row for every second of the day (24*60*60 = 86400 rows, still not that big for sql server)
*I took the generate sql from here

Related

Average per quarter hour

I have a table with these columns:
Id, Method, DateTime, time taken
Ex
1, Done, 2014-06-22 08:18:00.000, 2000
2, Not Done, 2014-06-23 04:15:00.000, 5000
3, Done, 2014-06-23 14:15:00.000, 6000
I want to have a result set as, "average time taken by DONE methods in each 15 min interval between 8AM to 15PM"
Please guide me on how to proceed on this, I am not sure if cursor fits in this req.
You can use a CTE to generate a list of quarters. Then left join to look up the run times per quarter. A group by will allow you to calculate the average.
In SQL Server 2012, the time type is available, and you can:
; with quarters as
(
select cast('08:00' as time) as time
union all
select dateadd(minute, 15, time)
from quarters
where time <= '14:30'
)
select q.time
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
RunTime rt
on q.time <= cast(rt.dt as time)
and cast(rt.dt as time) < dateadd(minute, 15, q.time)
and method = 'Done'
group by
q.time
Live example at SQL Fiddle.
For SQL Server 2008R2 and earler, you can use integer math instead:
; with quarters as
(
select 8*60 as min
union all
select min + 15
from quarters
where min < 15*60
)
select q.min / 60 as hour
, q.min % 60 as minute
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
(
select datepart(minute, dt) +
60 * datepart(hour, dt) as min
, time_taken
from RunTime
where method = 'Done'
) rt
on q.min <= rt.min and rt.min < q.min + 15
group by
q.min;
Live example at SQL Fiddle.
I'm not entirely sure if this is what you want, but here ist the code:
CREATE TABLE #Test(
id int IDENTITY(1,1) PRIMARY KEY,
Method nvarchar(50),
[Datetime] datetime,
timeTaken Bigint
)
CREATE TABLE #Result(
[Between] datetime,
[And] datetime,
[Avg] bigint)
INSERT INTO #Test (Method,Datetime,timeTaken)
VALUES(
'Done', '2014-06-22 08:18:00.000', 2000),
('Not Done', '2014-06-23 04:15:00.000', 5000),
('Done', '2014-06-23 14:15:00.000', 6000)
DECLARE #MaxTime datetime,#StartTime datetime,#Next datetime
SELECT #MaxTime = MAX([datetime]),
#StartTime = MIN([datetime])
FROM #TEST
WHILE #StartTime <= #MaxTime
BEGIN
SET #Next = (SELECT Dateadd(MINUTE,15,#StartTime))
INSERT INTO #Result
SELECT #StartTime AS [Between], #Next AS [And],AVG(timeTaken) AS [AVG]
FROM #Test
WHERE [Datetime] Between #StartTime AND #Next
AND Method = 'Done'
SET #StartTime = #Next
END
SELECT * FROM #Result
DROP TABLE #Test
DROP TABLE #Result
You can now set a where to the Select * from #result in which you can say between 8 AM and 3 PM
Please let me know if this is what you want
Etienne

TSQL Averaging over datepart

I have a table with a datetime column in it, consider it an event log for simple, analogous purposes.
I want to produce a report detailing the average number of events that occur at each time of day, to 30 min accuracy.
so the logic is,
get just the time component of each date
round the time to the nearest 30 min window (it can be floored, i.e. 00:29 -> 00:00)
count these (grouped by date)
average all these counts over all days
I also don't want to have any time holes in my data, for example, if nothing occurred in the 00:00 - 00:30 range, i want to report a 0, rather than having a missing row.
How can I achieve this?
WITH TestDates (date) AS (
SELECT CONVERT(DATETIME, '2011-11-15 10:00') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-15 11:31') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-16 10:00')
-- CTE to generate 4 million rows with a sequential integer starting at 0
), GeneratedRows (seq) AS (
SELECT ROW_NUMBER() OVER (ORDER BY N1.number) - 1
FROM master..spt_values AS N1
CROSS JOIN master..spt_values AS N2
WHERE N1.name IS NULL
AND N2.name IS NULL
), RoundedTestDates (date) AS (
SELECT CASE
-- Subtract the minute part
WHEN DATEPART(MINUTE, date) < 25 THEN DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date)
-- Subtract the minute part, then add an hour
WHEN DATEPART(MINUTE, date) >= 45 THEN DATEADD(HOUR, 1, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
-- Subtract the minute part, then add an half-hour
ELSE DATEADD(MINUTE, 30, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
END
FROM TestDates
)
SELECT rounded_date = GeneratedPeriod.date
, ocurrences = COUNT(RoundedTestDates.date)
FROM (SELECT DATEADD(MINUTE, 30 * seq, (SELECT MIN(date) FROM RoundedTestDates))
FROM GeneratedRows
) AS GeneratedPeriod (date)
LEFT JOIN RoundedTestDates
ON GeneratedPeriod.date = RoundedTestDates.date
WHERE GeneratedPeriod.date <= (SELECT MAX(date) FROM RoundedTestDates)
GROUP BY GeneratedPeriod.date
ORDER BY 1
Here is the code you need: (tested in sql2008 and works fine!)
-- Table with the 48 30mins periods of the day
CREATE TABLE #Periods
(
Num INT
)
DECLARE #idt INT
SET #idt = 1
WHILE (#idt <= 48)
BEGIN
INSERT INTO #Periods VALUES (#idt)
SET #idt = #idt + 1
END
--Average of the count for each period on all days.
SELECT DayTable.Num, AVG(CAST(DayTable.DayCount AS DECIMAL))
FROM
( --Total incidents for each interval on each day.
SELECT CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME) AS DayWithOutTime,
#Periods.Num AS Num,
COUNT(#MyLog.ID) AS DayCount
FROM #Periods LEFT JOIN #MyLog
ON #Periods.Num = (DATEPART(hh, #MyLog.LogDate)*60 + DATEPART(mi,#MyLog.LogDate))/30
GROUP BY CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME),
#Periods.Num
) AS DayTable
GROUP BY DayTable.Num
DROP TABLE #Periods
Where #NyLog is the table where your datetime is. It shows the count of incidences for each 30min period. The Period 1 is 00:00 -> 00:30 and Period 48 is 23:30 -> 24:00.
In sybase sql is something like this, in sql-server you might need to do some changes but not much :)
create procedure Test #startDay varchar(8), #endDay varchar(8)
as
declare #ocurrence int
declare #numberOfDays int
select #numberOfDays = 0
create table #intervals (
interval_hour int,
interval_min_minute int,
interval_max_minute int,
ocurrences int
)
create table #insertions (
hour int,
minute int
)
declare #hour int, #minute int
select #hour = 0
-- create the intervals
while (#hour <> 24)
begin
insert into #intervals values(#hour,0,29,0)
insert into #intervals values(#hour,30,59,0)
select #hour = #hour + 1
end
while(#startDay <> #endDay)
begin
insert into #insertions
select datepart(hh, *yourcolumn*), datepart(mm, *yourcolumn*) from *yourdb..yourtable* where convert(varchar(8), *yourcolumn*, 112) = #startDay
select #startDay = convert(varchar(8), dateadd(dd, 1, convert(datetime, #startDay, 112)), 112)
select #numberOfDays = #numberOfDays + 1
end
declare cursor1 cursor for
select hour, minute from #insertions
open cursor1
fetch cursor1 into #hour, #minute
while (##sqlstatus=0)
begin
update #intervals
set i.ocurrences = i.ocurrences + 1
from #intervals i
where interval_hour = #hour and #minute between interval_min_minute and interval_max_minute
fetch cursor1 into #hour, #minute
end
close cursor1
select interval_hour 'hour', interval_min_minute 'min minute', interval_max_minute 'max minute', ocurrences,
case when ocurrences > 0 then convert(float, ocurrences) / convert(float, #numberOfDays) else 0 end 'ocurrences average' from #intervals
drop table #intervals
drop table #insertions
go
What I've done is use an auxiliary table of numbers (a 1 column table with number 1 to 1 million) and join to it, adding the value of the number with the dateadd function to the midnight of the date.
since you want 30 minute intervals, then you want to use the dateadd(minute, number*30, yourdate) where number <= 48 (since there are 1440 minutes in a day)/30 = 48 intervals. This will create your time intervals.
Then simply count your occurrences that happen in between the time intervals.

How can I generate a temporary table filled with dates in SQL Server 2000?

I need to make a temporary table that holds of range of dates, as well as a couple of columns that hold placeholder values (0) for future use. The dates I need are the first day of each month between $startDate and $endDate where these variables can be several years apart.
My original sql statement looked like this:
select dbo.FirstOfMonth(InsertDate) as Month, 0 as Trials, 0 as Sales
into #dates
from customer
group by dbo.FirstOfMonth(InsertDate)
"FirstOfMonth" is a user-defined function I made that pretty much does what it says, returning the first day of the month for the provided date with the time at exactly midnight.
This produced almost exactly what I needed until I discovered there were occasionally gaps in my dates where I had a few months were there were no records insert dates. Since my result must still have the missing months I need a different approach.
I have added the following declarations to the stored procedure anticipating their need for the range of the dates I need ...
declare $startDate set $startDate = select min(InsertDate) from customer
declare $endDate set $endDate = select max(InsertDate) from customer
... but I have no idea what to do from here.
I know this question is similar to this question but, quite frankly, that answer is over my head (I don't often work with SQL and when I do it tends to be on older versions of SQL Server) and there are a few minor differences that are throwing me off.
I needed something similar, but all DAYS instead of all MONTHS.
Using the code from MatBailie as a starting point, here's the SQL for creating a permanent table with all dates from 2000-01-01 to 2099-12-31:
CREATE TABLE _Dates (
d DATE,
PRIMARY KEY (d)
)
DECLARE #dIncr DATE = '2000-01-01'
DECLARE #dEnd DATE = '2100-01-01'
WHILE ( #dIncr < #dEnd )
BEGIN
INSERT INTO _Dates (d) VALUES( #dIncr )
SELECT #dIncr = DATEADD(DAY, 1, #dIncr )
END
This will quickly populate a table with 170 years worth of dates.
CREATE TABLE CalendarMonths (
date DATETIME,
PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2000',
#offset = 1
WHILE (#offset < 2048)
BEGIN
INSERT INTO CalendarMonths SELECT DATEADD(MONTH, #offset, date) FROM CalendarMonths
SELECT #offset = #offset + #offset
END
You can then use it by LEFT joining on to that table, for the range of dates you require.
I would probably use a Calendar table. Create a permanent table in your database and fill it with all of the dates. Even if you covered a 100 year range, the table would still only have ~36,525 rows in it.
CREATE TABLE dbo.Calendar (
calendar_date DATETIME NOT NULL,
is_weekend BIT NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (calendar_date)
)
Once the table is created, just populate it once in a loop, so that it's always out there and available to you.
Your query then could be something like this:
SELECT
C.calendar_date,
0 AS trials,
0 AS sales
FROM
dbo.Calendar C
WHERE
C.calendar_date BETWEEN #start_date AND #end_date AND
DAY(C.calendar_date) = 1
You can join in the Customers table however you need to, outer joining on FirstOfMonth(InsertDate) = C.calendar_date if that's what you want.
You can also include a column for day_of_month if you want which would avoid the overhead of calling the DAY() function, but that's fairly trivial, so it probably doesn't matter one way or another.
This of course will not work in SQL-Server 2000 but in a modern database where you don't want to create a permanent table. You can use a table variable instead creating a table so you can left join the data try this. Change the DAY to HOUR etc to change the increment type.
declare #CalendarMonths table (date DATETIME, PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2014',
#offset = 1
INSERT INTO #CalendarMonths SELECT #basedate
WHILE ( DATEADD(DAY, #offset, #basedate) < CURRENT_TIMESTAMP)
BEGIN
INSERT INTO #CalendarMonths SELECT DATEADD(HOUR, #offset, date) FROM #CalendarMonths where DATEADD(DAY, #offset, date) < CURRENT_TIMESTAMP
SELECT #offset = #offset + #offset
END
A starting point of a useful kludge to specify a range or specific list of dates:
SELECT *
FROM
(SELECT CONVERT(DateTime,'2017-1-1')+number AS [Date]
FROM master..spt_values WHERE type='P' AND number<370) AS DatesList
WHERE DatesList.Date IN ('2017-1-1','2017-4-14','2017-4-17','2017-12-25','2017-12-26')
You can get 0 to 2047 out of master..spt_values WHERE type='P', so that's five and a half year's worth of dates if you need it!
Tested below and it works, though it's a bit convoluted.
I assigned arbitrary values to the dates for the test.
DECLARE #SD smalldatetime,
#ED smalldatetime,
#FD smalldatetime,
#LD smalldatetime,
#Mct int,
#currct int = 0
SET #SD = '1/15/2011'
SET #ED = '2/02/2012'
SET #FD = (DATEADD(dd, -1*(Datepart(dd, #SD)-1), #sd))
SET #LD = (DATEADD(dd, -1*(Datepart(dd, #ED)-1), #ED))
SET #Mct = DATEDIFF(mm, #FD, #LD)
CREATE TABLE #MyTempTable (FoM smalldatetime, Trials int, Sales money)
WHILE #currct <= #Mct
BEGIN
INSERT INTO #MyTempTable (FoM, Trials, Sales)
VALUES
(DATEADD(MM, #currct, #FD), 0, 0)
SET #currct = #currct + 1
END
SELECT * FROM #MyTempTable
DROP TABLE #MyTempTable
For SQL Server 2000, this stackoverflow post looks promising for a way to temporarily generate dates calculated off of a start and end date. It's not exactly the same but quite similar. This post has a very in-depth answer on truncating dates, if needed.
In case anyone stumbles on this question and is working in PostgreSQL instead of SQL Server 2000, here is how you might do it there...
PostgreSQL has a nifty series generating function. For your example, you could use this series of all days instead of generating an entire calendar table, and then do groupings and matchups from there.
SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a);
dates
------------
2004-02-05
2004-02-12
2004-02-19
(3 rows)
SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00', '10 hours');
generate_series
---------------------
2008-03-01 00:00:00
2008-03-01 10:00:00
2008-03-01 20:00:00
2008-03-02 06:00:00
2008-03-02 16:00:00
2008-03-03 02:00:00
2008-03-03 12:00:00
2008-03-03 22:00:00
2008-03-04 08:00:00
(9 rows)
I would also look into date_trunc from PostgreSQL using 'month' for the truncator field to maybe refactor your original query to easily match with a date_trunc version of the calendar series.
select top (datediff(D,#start,#end)) dateadd(D,id-1,#start)
from BIG_TABLE_WITH_NO_JUMPS_IN_ID
declare #start datetime
set #start = '2016-09-01'
declare #end datetime
set #end = '2016-09-30'
create table #Date
(
table_id int identity(1,1) NOT NULL,
counterDate datetime NULL
);
insert into #Date select top (datediff(D,#start,#end)) NULL from SOME_TABLE
update #Date set counterDate = dateadd(D,table_id - 1, #start)
The code above should populate the table with all the dates between the start and end. You would then just join on this table to get all of the dates needed. If you only needed a certain day of each month, you could dateadd a month instead.
SELECT P.Id
, DATEADD ( DD, -P.Id, P.Date ) AS Date
FROM (SELECT TOP 1000 ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) AS Id, CAST(GETDATE () AS DATE) AS Date FROM master.dbo.spt_values) AS P
This query returns a table calendar for the last 1000 days or so. It can be put in a temporary or other table.
Create a table variable containing a date for each month in a year:
declare #months table (reportMonth date, PRIMARY KEY (reportMonth));
declare #start date = '2018', #month int = 0; -- base 0 month
while (#month < 12)
begin
insert into #months select dateAdd(month, #month, #start);
select #month = #month + 1;
end
--verify
select * from #months;
This is by far the quickest method I have found (much quicker than inserting rows 1 by 1 in a WHILE loop):
DECLARE #startDate DATE = '1900-01-01'
DECLARE #endDate DATE = '2050-01-01'
SELECT DATEADD(DAY, sequenceNumber, #startDate) AS TheDate
INTO #TheDates
FROM (
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n AS sequenceNumber
FROM
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tenthousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n <= DATEDIFF(day, #startDate, #endDate)
) theNumbers
SELECT *
FROM #TheDates
ORDER BY TheDate
The recursive answer:
DECLARE #startDate AS date = '20220315';
DECLARE #endDate AS date = '20230316'; -- inclusive
WITH cte_minutes(dt)
AS (
SELECT
DATEFROMPARTS(YEAR(#startDate), MONTH(#startDate), 1)
UNION ALL
SELECT
DATEADD(month, 1, dt)
FROM
cte_minutes
WHERE DATEADD(month, 1, dt) < #endDate
)
SELECT
dt
into #dates
FROM
cte_minutes
WHERE
dt >= #startDate
AND
dt <= #endDate
OPTION (MAXRECURSION 2000);
DROP TABLE dbo.#dates

MySQL group by intervals in a date range

I am going to be graphing netflow data stored in a MySQL database, and I need an efficient way to get the relevant data points. They records are stored with the date as an int for seconds since epoch. I Would like to be able to something like:
Select SUM(bytes) from table where stime > x and stime < Y
group by (10 second intervals)
Is there anyway to do this? or, would it be faster to handle it locally in python? even for a 500K row table?
EDIT
My Mistake, the time is stored as an unsigned double instead of an INT.
I'm currently using GROUP BY (FLOOR(stime / I)) where I is the desired interval.
You may be able to do this using integer division. Not sure of the performance.
Let I be your desired interval in seconds.
SELECT SUM(bytes), ((stime - X) DIV I) as interval
FROM table
WHERE (stime > X) and (stime < Y)
GROUP BY interval
Example, let X = 1500 and I = 10
stime = 1503 -> (1503 - 1500) DIV 10 = 0
stime = 1507 -> (1507 - 1500) DIV 10 = 0
stime = 1514 -> (1514 - 1500) DIV 10 = 1
stime = 1523 -> (1523 - 1500) DIV 10 = 2
Have you tried the following? Just devide the tyiem column by 10 and round the result down.
SELECT SUM(bytes)
FROM table
WHERE stime > x
AND stime < Y
GROUP BY ROUND(stime/10, -1)
I don't know wether the ROUND() function and grouping with function calls works in MySQL though, the above is T-SQL.
FLOOR in group by sometimes fails. it sometimes groups different times as one value for example when you divide the value with 3 but it doesn't do the same when you divide with 4, although the difference between these two values is far bigger than 3 or 4 which it should group as two different groups. Better cast it to unsigned after floor which works like:
CAST(FLOOR(UNIX_TIMESTAMP(time_field)/I) AS UNSIGNED INT)
The problem:
Sometimes GROUP BY FLOOR(UNIX_TIMESTAMP(time_field)/3) gives less groups compared to GROUP BY FLOOR(UNIX_TIMESTAMP(time_field)/4) which is mathematically shouldn't be possible.
SELECT sec_to_time(time_to_sec(datefield)- time_to_sec(datefield)%(10)) as intervals,SUM(bytes)
FROM table
WHERE where stime > x and stime < Y
group by intervals
I used suggestions from both answers and a coworker. End result is as follows:
Select FROM_UNIXTIME(stime), bytes
from argusTable_2009_10_22
where stime > (UNIX_TIMESTAMP()-600)
group by floor(stime /10)
I tried the rounding solution as well, but the results were inconsistent.
Chance
I did this a few time ago, so i created some function (with sql server, but i assume it's nearly the same) :
First I created a scalar function that return me the ID of a date depending on an interval and a date part (minute,hour,day,moth,year):
CREATE FUNCTION [dbo].[GetIDDate]
(
#date datetime,
#part nvarchar(10),
#intervalle int
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
DECLARE #res int
DECLARE #date_base datetime
SET #date_base = convert(datetime,'01/01/1970',103)
set #res = case #part
WHEN 'minute' THEN datediff(minute,#date_base,#date)/#intervalle
WHEN 'hour' THEN datediff(hour,#date_base,#date)/#intervalle
WHEN 'day' THEN datediff(day,#date_base,#date)/#intervalle
WHEN 'month' THEN datediff(month,#date_base,#date)/#intervalle
WHEN 'year' THEN datediff(year,#date_base,#date)/#intervalle
ELSE datediff(minute,#date_base,#date)/#intervalle END
-- Return the result of the function
RETURN #res
END
Then I created a table function that returns me all the id betweend a date range :
CREATE FUNCTION [dbo].[GetTableDate]
(
-- Add the parameters for the function here
#start_date datetime,
#end_date datetime,
#interval int,
#unite varchar(10)
)
RETURNS #res TABLE (StartDate datetime,TxtStartDate nvarchar(50),EndDate datetime,TxtEndDate nvarchar(50),IdDate int)
AS
begin
declare #current_date datetime
declare #end_date_courante datetime
declare #txt_start_date nvarchar(50)
declare #txt_end_date nvarchar(50)
set #current_date = case #unite
WHEN 'minute' THEN dateadd(minute, datediff(minute,0,#start_date),0)
WHEN 'hour' THEN dateadd(hour, datediff(hour,0,#start_date),0)
WHEN 'day' THEN dateadd(day, datediff(day,0,#start_date),0)
WHEN 'month' THEN dateadd(month, datediff(month,0,#start_date),0)
WHEN 'year' THEN dateadd(year, datediff(year,0,dateadd(year,#interval,#start_date)),0)
ELSE dateadd(minute, datediff(minute,0,#start_date),0) END
while #current_date < #end_date
begin
set #end_date_courante =
case #unite
WHEN 'minute' THEN dateadd(minute, datediff(minute,0,dateadd(minute,#interval,#current_date)),0)
WHEN 'hour' THEN dateadd(hour, datediff(hour,0,dateadd(hour,#interval,#current_date)),0)
WHEN 'day' THEN dateadd(day, datediff(day,0,dateadd(day,#interval,#current_date)),0)
WHEN 'month' THEN dateadd(month, datediff(month,0,dateadd(month,#interval,#current_date)),0)
WHEN 'year' THEN dateadd(year, datediff(year,0,dateadd(year,#interval,#current_date)),0)
ELSE dateadd(minute, datediff(minute,0,dateadd(minute,#interval,#current_date)),0) END
SET #txt_start_date = case #unite
WHEN 'minute' THEN CONVERT(VARCHAR(20), #current_date, 100)
WHEN 'hour' THEN CONVERT(VARCHAR(20), #current_date, 100)
WHEN 'day' THEN REPLACE(CONVERT(VARCHAR(11), #current_date, 106), ' ', '-')
WHEN 'month' THEN REPLACE(RIGHT(CONVERT(VARCHAR(11), #current_date, 106), 8), ' ', '-')
WHEN 'year' THEN CONVERT(VARCHAR(20), datepart(year,#current_date))
ELSE CONVERT(VARCHAR(20), #current_date, 100) END
SET #txt_end_date = case #unite
WHEN 'minute' THEN CONVERT(VARCHAR(20), #end_date_courante, 100)
WHEN 'hour' THEN CONVERT(VARCHAR(20), #end_date_courante, 100)
WHEN 'day' THEN REPLACE(CONVERT(VARCHAR(11), #end_date_courante, 106), ' ', '-')
WHEN 'month' THEN REPLACE(RIGHT(CONVERT(VARCHAR(11), #end_date_courante, 106), 8), ' ', '-')
WHEN 'year' THEN CONVERT(VARCHAR(20), datepart(year,#end_date_courante))
ELSE CONVERT(VARCHAR(20), #end_date_courante, 100) END
INSERT INTO #res (
StartDate,
EndDate,
TxtStartDate,
TxtEndDate,
IdDate) values(
#current_date,
#end_date_courante,
#txt_start_date,
#txt_end_date,
dbo.GetIDDate(#current_date,#unite,#interval)
)
set #current_date = #end_date_courante
end
return
end
So if I want to count all the user added for each interval of 33 minutes :
SELECT count(id_user) , timeTable.StartDate
FROM user
INNER JOIn dbo.[GetTableDate]('1970-01-01',datedate(),33,'minute') as timeTable
ON dbo.getIDDate(user.creation_date,'minute',33) = timeTable.IDDate
GROUP BY dbo.getIDDate(user.creation_date,'minute',33)
ORDER BY timeTable.StartDate
:)

How I can select / sort dates by period intervals?

For ex:
If we have in table records like:
25/06/2009
28/12/2009
19/02/2010
16/04/2011
20/05/2012
I want to split/select this dates according to 6 month intervals starting from current date.
result should be like:
0-6 month from now: first record
7-12 month from now: second record
...
It will be much apreciated if you make this simple as I made it very stupid and complicated like:
declare variable like t1=curdate()+6
t2=curdate()+12
...
then selected records to fit between curdate() and t1, then t1 and t2 etc.
Thanks,
r.
CORRECTION: Had it backwards, Need to use Modulus, not integer division - sorry...
If MonthCount is a calculated value which counts the number of months since a specific Dec 31, and mod is modulus division (output the remainder after dividing)
Select [Column list here]
From Table
Group By Case When MonthCount Mod 12 < 6
Then 0 Else 1 End
In SQL Server, for example, you could use the DateDiff Function
Select [Column list here]
From Table
Group By Case When DateDiff(month, myDateColumn, curdate) % 12 < 6
Then 0 Else 1 End
( in SQL Server the percent sign is the modulus operator )
This will group all the record into buckets which each contain six months of data
SELECT (DATEDIFF(MONTH, thedate, GETDATE()) / 6) AS semester,
SUM(receipt)
FROM thetable
GROUP BY semester
ORDER BY semester
the key idea is grouping and ordering by the expression that gives you the "semester".
This question really baffled me, cos I couldn't actually come up with a simple solution for it. Damn.
Best I could manage was an absolute bastardization of the following where you create a Temp Table, insert the "Periods" into it, join back to your original table, and group off that.
Assume your content table has the following
ID int
Date DateTime
Counter int
And you're trying to sum all the counter's in six month periods
DECLARE #min_date datetime
select #min_date = min(date) from test
DECLARE #max_date datetime
select #max_date = max(date) from test
DECLARE #today_a datetime
DECLARE #today_b datetime
set #today_a = getdate()
set #today_b = getdate()
CREATE TABLE #temp (startdate DateTime, enddate DateTime)
WHILE #today_a > #min_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (dateadd(month, -6, #today_a), #today_a)
SET #today_a = dateadd(month, -6, #today_a)
END
WHILE #today_b < #max_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (#today_b, dateadd(month, 6, #today_b))
SET #today_b = dateadd(month, 6, #today_b)
END
SELECT * FROM #temp
SELECT
sum(counter),
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121) as Period
FROM test t
JOIN #Temp ht
ON t.Date between ht.startDate AND ht.EndDate
GROUP BY
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121)
DROP TABLE #temp
I really hope someone can come up with a better solution my brain has obviously melted.
Not quite what you're attempting to accomplish, but you could use the DATEDIFF function to distinguish the ranging of each record:
SELECT t.MonthGroup, SUM(t.Counter) AS TotalCount
FROM (
SELECT Counter, (DATEDIFF(m, GETDATE(), Date) / 6) AS MonthGroup
FROM Table
) t
GROUP BY t.MonthGroup
This would create a sub query with an expression that expresses the date ranging group you want. It would then group the sub-query by this date ranging group and you can then do whatever you want with the results.
Edit: I modified the example based on your example.
If you're using SQL Server:
SELECT *,
(
FLOOR
(
(
DATEDIFF(month, GETDATE(), date_column)
- CASE WHEN DAY(GETDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table
If you're using MySQL:
SELECT *,
(
FLOOR
(
(
((YEAR(date_column) - YEAR(CURDATE())) * 12)
+ MONTH(date_column) - MONTH(CURDATE())
- CASE WHEN DAY(CURDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table