i have a table with snow data which i get delivered per hour.
so for instance at between 0am and 1 am 1 cm snow will fall, between 1 am and 2 am 3 cm snow will fall, between 2 am and 3 am 0 cm snow will fall, between 3 am and 4 am 2 cm snow will fall etc.
so the table has a Snowdate column (datetime), a Snowdate hour column (int) and a snowfall column (int)
now i want to present the data grouped by groups of 6 hours (0-5, 6-11, 12-17 and 18-23), so when between 0 am and 6 am i have 6 records (1cm, 3c, 0cm, 2 cm, 2cm, 0cm) i want to show one row with the number 8, and so on for the rest of the day.
For every hour a day there will be a record in the db, so always 24 records a day
A pure sql solution will be ok (a view or so) or a linq to entities will be ok too.
Michel
Group by the hour / 6 (using integer arithmetic) and select for grouping the amount column. Select into a new object, the Key * 6 and Sum() the resulting grouping for the total for that "hour". The "hour" will be the first hour in each range.
var query = db.SnowRecords.GroupBy( s => s.SnowHour / 6, a => a.SnowFall )
.Select( g => new {
Hour = g.Key * 6,
Amount = g.Sum()
});
You don't say if you need to group by date as well, but if you do, then this would become the inner query on the records grouped by date.
var query = db.SnowRecords.GroupBy( s => s.SnowDate.Date )
.Select( g => new {
Date = g.Key,
HourlySnowFall = g.GroupBy( s => s.SnowHour / 6, a => a.SnowFall )
.Select( sg => new {
Hour = sg.Key * 6,
Amount = sg.Sum()
})
});
If i understand correct, try something like this
DECLARE #Table TABLE(
SnowDate DATETIME,
SnowHour INT,
SnowFall INT
)
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 0, 1
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 1, 3
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 2, 0
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 3, 2
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 4, 2
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 5, 0
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 6, 10
INSERT INTO #Table (SnowDate,SnowHour,SnowFall) SELECT '10 Sep 2009', 7, 10
SELECT SnowDate,
CAST(FLOOR((SnowHour) / 6.) * 6 AS VARCHAR(4)) + ' TO ' + CAST((FLOOR((SnowHour) / 6.) + 1) * 6 - 1 AS VARCHAR(4)),
SUM(SnowFall) AS Total
FROM #Table
GROUP BY SnowDate,
FLOOR((SnowHour) / 6.)
Related
Sorry for my English...
I Iave a table with column
project, month, year
abc 2 2017
xyz 5 2017
abc 3 2017
abc 5 2017
abc 1 2018
How can I search project abc with month = 2 year = 2017 until month = 1 year = 2018
As far as I know, SQL Server 2008 cannot use concat function
Use math comparison:
SELECT * FROM table1
WHERE (year * 12 + month) BETWEEN (2017 * 12 + 1) AND (2018 * 12 + 1)
Try this:
Select *
From YourTable
Where DATETIMEFROMPARTS(year, month, 1, 1, 1, 1, 1)
between '2017-02-01' And '2018-01-01'
I have edited the code to account for the leading zero in the month.
Declare #temp Table
(
project varchar(50),
month int,
year int
);
Insert Into #temp
(project, month, year)
Values ('abc', 2, 2017)
Insert Into #temp
(project, month, year)
Values ('xyz', 5, 2017)
Insert Into #temp
(project, month, year)
Values ('abc', 3, 2017)
Insert Into #temp
(project, month, year)
Values ('abc', 5, 2017)
Insert Into #temp
(project, month, year)
Values ('abc', 1, 2018)
Insert Into #temp
(project, month, year)
Values ('xxx', 5, 2010)
Insert Into #temp
(project, month, year)
Values ('xxx', 12, 2018)
Declare #FromYear int = 2010;
Declare #FromMonth int = 04;
Declare #ToYear int = 2018;
Declare #ToMonth int = 05;
Select *
From #temp
Where Convert(varchar, year) + right('00' + Convert(varchar, month), 2) Between '201004' and '201805'
How can I search project abc with month = 2 year = 2017 until month = 1 year = 2018
You can use
SELECT *
FROM T
WHERE (([Year] * 10) + [Month]) BETWEEN 20172 AND 20181
AND
project = 'abc';
Demo
I need to create a report that returns a range of data, based on an open date and any records opened within a month for each previous quarter.
So if I was to run the report at the start of August - it would show records which have an open date within May, Feb, November, August with no limit on the year. The open date month will be defined by the month where the report is run.
I have this very basic code
SELECT RecordMonth, RecordYear, caseRef, caseDescription,
caseOpenDate, caseClosedDate, PersonName, PersonSurname
FROM dbo.casereview
WHERE month(caseopendate) = 03
OR month(casecopendate) = 06
OR month(caseopendate) = 09
OR month(caseopendate) = 12
Issue is, I need this to run dynamically, so whichever month it is run, it can show cases where the open date falls within the previous quarters/months.
You can use Modulo to calculate the 4 month numbers you need based on the current system date then filter on those.
In you case something like this will work.
DECLARE #month int = month(getdate())
DECLARE #m1 int = ((#month -1 ) % 12) +1
DECLARE #m2 int = ((#month + 2) % 12) +1
DECLARE #m3 int = ((#month + 5) % 12) +1
DECLARE #m4 int = ((#month + 8) % 12) +1
SELECT RecordMonth, RecordYear, caseRef, caseDescription,
caseOpenDate, caseClosedDate, PersonName, PersonSurname
FROM dbo.casereview
WHERE month(caseopendate) IN (#m1, #m2, #m3, #m4)
So first this is to get the current month number, we do this by using getdate() to get the current date and the MONTH function to return just the month number. So today this will equal 8.
Next we use Modulo (the % sign) to work out what months we need.
So for example if month = 6 then #m4 is worked out as
(#month + 8) = 14
14 % 12 = 2 (remainder of 14 divided by 12)
2 + 1 = 3
Note: I've made this a bit longer than it needs to be so it's a bit easier to read. We could put the calcs for #m1, #m2 etc directly into the select statement but doing this separately makes it clearer.
Notice I also used an IN statement rather than a bunch of ORs in the WHERE clause. IN just matches on any of the values listed in the parentheses.
Assuming you want all matching rows in opendate order, we can simply use the Modulus function. We could do four MONTH % 12 queries, or a single MONTH % 3 query.
I built a test database to show the basic process:
create database [_TEST_QUARTERS];
GO
USE [_TEST_QUARTERS];
GO
create table [CASEREVIEW] (
[CASE_ID] integer primary key,
[CASEOPENDATE] date
);
insert into [CASEREVIEW] values ( 1, '2016-09-19');
insert into [CASEREVIEW] values ( 2, '2016-10-20');
insert into [CASEREVIEW] values ( 3, '2016-11-11');
insert into [CASEREVIEW] values ( 4, '2016-12-22');
insert into [CASEREVIEW] values ( 5, '2017-01-11');
insert into [CASEREVIEW] values ( 6, '2017-02-12');
insert into [CASEREVIEW] values ( 7, '2017-03-13');
insert into [CASEREVIEW] values ( 8, '2017-04-14');
insert into [CASEREVIEW] values ( 9, '2017-05-15');
insert into [CASEREVIEW] values (10, '2017-06-16');
insert into [CASEREVIEW] values (11, '2017-07-17');
insert into [CASEREVIEW] values (12, '2017-08-18');
So we can see the data from which it has to choose:
select CURRENT_TIMESTAMP as [Today];
select * from [CASEREVIEW];
And the actual query:
select [CASE_ID], [CASEOPENDATE] from [CASEREVIEW]
where MONTH(CONVERT(VARCHAR(10), [CASEOPENDATE], 120)) % 3 = MONTH(CONVERT(VARCHAR(10), CURRENT_TIMESTAMP, 120)) % 3
ORDER BY [CASEOPENDATE];
Results:
CASE_ID CASEOPENDATE
----------- ------------
3 2016-11-11
6 2017-02-12
9 2017-05-15
12 2017-08-18
(4 row(s) affected)
I have a view with two columns TOTAL and DATE, the latter one excludes Saturdays and Sundays, i.e.
TOTAL DATE
0 1-1-2014
33 2-1-2014
11 3-1-2014
55 5-1-2014
...
25 15-1-2014
35 16-1-2014
17 17-1-2014
40 20-1-2014
33 21-1-2014
...
The task that I'm trying to complete is counting 5 days TOTAL average for the whole month, i.e between 13th and 17th, 14th and 20th (we skip weekends), 15th and 21st etc. up to current date.
And YES, they ARE OVERLAPPING RANGES.
Any idea how to achieve it in SQL?
Example of the output (starting from the 6th and using fake numbers)
5daysAVG Start_day
22 1-01-2014 <-counted between 1st to 6th Jan excl 4 and 5 of Jan
25 2-01-2014 <- Counted between 2nd to 7th excluding 4 and 5
27 3-01-2014 <- 3rd to 8th excluding 4/5
24 6-01-2014 <-6th to 10th
...
33 today-5
Okay, I usually set up some test data to play with.
Here is some code to create a [work] table in tempdb. I am skipping weekends. The total is a random number from 0 to 40.
-- Just playing
use tempdb;
go
-- drop existing
if object_id ('work') > 0
drop table work
go
-- create new
create table work
(
my_date date,
my_total int
);
go
-- clear data
truncate table work;
go
-- Monday = 1
SET DATEFIRST 1;
GO
-- insert data
declare #dt date = '20131231';
declare #hr int;
while (#dt < '20140201')
begin
set #hr = floor(rand(checksum(newid())) * 40);
set #dt = dateadd(d, 1, #dt);
if (datepart(dw, #dt) < 6)
insert into work values (#dt, #hr);
end
go
This becomes real easy in SQL SERVER 2012 with the new LEAD() window function.
-- show data
with cte_summary as
(
select
row_number() over (order by my_date) as my_num,
my_date,
my_total,
LEAD(my_total, 0, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 1, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 2, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 3, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 4, 0) OVER (ORDER BY my_date) as my_sum,
(select count(*) from work) as my_cnt
from work
)
select * from cte_summary
where my_num <= my_cnt - 4
Basically, we give a row number to each row, calculate the sum for rows 0 (current) to row 4 (4 away) and a total count.
Since this is a running total for five periods, the remaining dates have missing data. Therefore, we toss them out. my_row <= my_cnt -4
I hope this solves your problem!
If you are only caring about one number for the month, change the select to the following. I left the other rows in for you to get an understanding of what is going on.
select avg(my_sum/5) as my_stat
from cte_summary
where my_num <= my_cnt - 4
FOR SQL SERVER < 2012 & >= 2005
Like anything in this world, there is always a way to do it. I used a small tally table to loop thru the data and collect sets of 5 data points for averages.
-- show data
with
cte_tally as
(
select
row_number() over (order by (select 1)) as n
from
sys.all_columns x
),
cte_data as
(
select
row_number() over (order by my_date) as my_num,
my_date,
my_total
from
work
)
select
(select my_date from cte_data where my_num = n) as the_date,
(
select sum(my_total) / 5
from cte_data
where my_num >= n and my_num < n+5
) as the_average
from cte_tally
where n <= (select count(*)-4 from work)
Here is an explanation of the common table expressions (CTE).
cte_data = order data by date and give row numbers
cte_tally = a set based counting algorithm
For groups of five calculate an average and show the date.
This solution does not depend on holidays or weekends. If data is there, it just partitions by groups of five order by date.
If you need to filter out holidays and weekends, create a holiday table. Add a where clause to cte_data that checks for NOT IN (SELECT DATE FROM HOLIDAY TABLE).
Good luck!
SQL Server offers the datepart(wk, ...) function to get the week of the year. Unfortunately, it uses the first day of the year to define the year.
Instead, you can find sequences of consecutive values and group them together:
select min(date), max(date, avg(total*1.0)
from (select v.*, row_number() over (order by date) as seqnum
from view
) v
group by dateadd(day, -seqnum, date);
The idea is that subtracting a sequence of numbers from a sequence of consecutive days yields a constant.
You can also do this by using a canonical date and dividing by 7:
select min(date), max(date, avg(total*1.0)
from view v
group by datediff(day, '2000-01-03', date) / 7;
The date '2000-01-03' is an arbitrary Monday.
EDIT:
You seem to want a 5-day moving average. Because there is missing data for the weekends, avg() should just work:
select v1.date, avg(v2.value)
from view v1 join
view v2
on v2.date >= v1.date and v2.date < dateadd(day, 7, v1.date)
group by v1.date;
Here's a solution that works in SQL 2008;
The concept here is to use a table variable to normalize the data first; the rest is simple math to count and average the days.
By normalizing the data, I mean, get rid of weekend days, and assign ID's in a temporary table variable that can be used to identify the rows;
Check it out: (SqlFiddle also here)
-- This represents your original source table
Declare #YourSourceTable Table
(
Total Int,
CreatedDate DateTime
)
-- This represents some test data in your table with 2 weekends
Insert Into #YourSourceTable Values (0, '1-1-2014')
Insert Into #YourSourceTable Values (33, '1-2-2014')
Insert Into #YourSourceTable Values (11, '1-3-2014')
Insert Into #YourSourceTable Values (55, '1-4-2014')
Insert Into #YourSourceTable Values (25, '1-5-2014')
Insert Into #YourSourceTable Values (35, '1-6-2014')
Insert Into #YourSourceTable Values (17, '1-7-2014')
Insert Into #YourSourceTable Values (40, '1-8-2014')
Insert Into #YourSourceTable Values (33, '1-9-2014')
Insert Into #YourSourceTable Values (43, '1-10-2014')
Insert Into #YourSourceTable Values (21, '1-11-2014')
Insert Into #YourSourceTable Values (5, '1-12-2014')
Insert Into #YourSourceTable Values (12, '1-13-2014')
Insert Into #YourSourceTable Values (16, '1-14-2014')
-- Just a quick test to see the source data
Select * From #YourSourceTable
/* Now we need to normalize the data;
Let's just remove the weekends and get some consistent ID's to use in a separate table variable
We will use DateName SQL Function to exclude weekend days while also giving
sequential ID's to the remaining data in our temporary table variable,
which are easier to query later
*/
Declare #WorkingTable Table
(
TempID Int Identity,
Total Int,
CreatedDate DateTime
)
-- Let's get the data normalized:
Insert Into
#WorkingTable
Select
Total,
CreatedDate
From #YourSourceTable
Where DateName(Weekday, CreatedDate) != 'Saturday'
And DateName(Weekday, CreatedDate) != 'Sunday'
-- Let's run a 2nd quick sanity check to see our normalized data
Select * From #WorkingTable
/* Now that data is normalized, we can just use the ID's to get each 5 day range and
perform simple average function on the columns; I chose to use a CTE here just to
be able to query it and drop the NULL ranges (where there wasn't 5 days of data)
without having to recalculate each average
*/
; With rangeCte (StartDate, TotalAverage)
As
(
Select
wt.createddate As StartDate,
(
wt.Total +
(Select Total From #WorkingTable Where TempID = wt.TempID + 1) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 2) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 3) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 4)
) / 5
As TotalAverage
From
#WorkingTable wt
)
Select
StartDate,
TotalAverage
From rangeCte
Where TotalAverage
Is Not Null
I have the following table in my database:
tbl1
PK
ClientID
ScheduleDay
Time1Start
Time1Stop
Time2Start
Time2Stop
Time3Start
Time3Stop
Status
Here is some sample data
ID ClientID ScheduleDay Time1Start Time1Stop Time2Start Time2Stop Time3Start Time3Stop
-- -------- ----------- ---------- --------- ---------- --------- ---------- ---------
1 3 Sunday 0000 0800 1000 1300 NULL NULL
2 3 Monday 0000 2359 NULL NULL NULL NULL
3 3 Tuesday 1000 1200 1330 1700 1900 2200
4 3 Wednesday 0000 0800 NULL NULL NULL NULL
5 3 Thursday 0800 1200 NULL NULL NULL NULL
6 3 Friday 0400 0800 0900 1600 NULL NULL
The Time fields are CHAR(4) since I am storing the time in a military format.
What I need to accomplish is this; for any given ClientID, insert one or more records into a schedule table with the time value of the record being within the time frames in tbl1. For example, scheduling ClientID 3 on Tuesday, the time scheduled could be 1120.
In the event that multiple records need to be inserted, the times scheduled should not be any closer than one hour.
Any and all help is appreciated!
Here's my best guess as to what you're trying to do. The first two parts of the CTE are really just to get things into a form similar to what FlyingStreudel suggests. Ideally, you should change the database to match that format instead of doing this through CTEs. That will make this significantly simpler and is better for data integrity as well.
Next, I just get the distinct start times in hour increments. You could do that by joining to a Numbers table as well if you can't use CTEs (you didn't mention the version of SQL Server that you're using).
Finally, I grab one of those start times at random, using the RAND function and ROW_NUMBER. You'll want to set a good seed value for RAND().
;WITH TimesAsTimes AS
(
SELECT
ScheduleDay,
CAST(SUBSTRING(T1.Time1Start, 1, 2) + ':' + SUBSTRING(T1.Time1Start, 3, 2) AS TIME) AS time_start,
CAST(SUBSTRING(T1.Time1Stop, 1, 2) + ':' + SUBSTRING(T1.Time1Stop, 3, 2) AS TIME) AS time_stop
FROM
tbl1 T1
WHERE
T1.Time1Start IS NOT NULL
UNION ALL
SELECT
ScheduleDay,
CAST(SUBSTRING(T2.Time2Start, 1, 2) + ':' + SUBSTRING(T2.Time2Start, 3, 2) AS TIME) AS time_start,
CAST(SUBSTRING(T2.Time2Stop, 1, 2) + ':' + SUBSTRING(T2.Time2Stop, 3, 2) AS TIME) AS time_stop
FROM
tbl1 T2
WHERE
T2.Time2Start IS NOT NULL
UNION ALL
SELECT
ScheduleDay,
CAST(SUBSTRING(T3.Time3Start, 1, 2) + ':' + SUBSTRING(T3.Time3Start, 3, 2) AS TIME) AS time_start,
CAST(SUBSTRING(T3.Time3Stop, 1, 2) + ':' + SUBSTRING(T3.Time3Stop, 3, 2) AS TIME) AS time_stop
FROM
tbl1 T3
WHERE
T3.Time3Start IS NOT NULL
),
PossibleTimeStarts AS
(
SELECT
ScheduleDay,
time_start,
time_stop
FROM
TimesAsTimes TAT
UNION ALL
SELECT
ScheduleDay,
DATEADD(hh, 1, time_start) AS time_start,
time_stop
FROM
PossibleTimeStarts PTS
WHERE
DATEADD(hh, 1, time_start) <= DATEADD(hh, -1, PTS.time_stop)
),
PossibleTimesWithRowNums AS
(
SELECT
ScheduleDay,
time_start,
ROW_NUMBER() OVER(PARTITION BY ScheduleDay ORDER BY ScheduleDay, time_start) AS row_num,
COUNT(*) OVER(PARTITION BY ScheduleDay) AS num_rows
FROM
PossibleTimeStarts
)
SELECT
*
FROM
PossibleTimesWithRowNums
WHERE
row_num = FLOOR(RAND() * num_rows) + 1
First of all you may want to try a schema like
tbl_sched_avail
PK id INT
FK client_id INT
day INT (1-7)
avail_start varchar(4)
avail_end varchar(4)
This way you are not limited to a finite number of time fences.
As far as checking the schedules availability -
CREATE PROCEDURE sp_ins_sched
#start_time varchar(4),
#end_time varchar(4),
#client_id INT,
#day INT
AS
BEGIN
DECLARE #can_create BIT
SET #can_create = 0
DECLARE #fence_start INT
DECLARE #fence_end INT
--IS DESIRED TIME WITHIN FENCE FOR CLIENT
DECLARE c CURSOR FOR
SELECT avail_start, avail_end FROM tbl_sched_avail
WHERE client_id = #client_id
AND day = #day
OPEN c
FETCH NEXT FROM c
INTO #fence_start, #fence_end
WHILE ##FETCH_STATUS = 0 AND #can_create = 0
BEGIN
IF #start_time >= #fence_start AND #start_time < #fence_end
AND #end_time > #fence_start AND <= #fence_end
SET #can_create = 1
FETCH NEXT FROM c
INTO #fence_start, #fence_end
END
CLOSE c
DEALLOCATE c
IF #can_create = 1
BEGIN
--insert your schedule here
END
END
As far as the code for actually inserting the record I would need to know more about the tables in the database.
I have a table containing multiple records for different transactions i.e.
ID Date REF
1 01/09/2008 A
1 11/09/2008 A
1 01/10/2008 A
2 01/09/2008 A
2 01/10/2008 A
2 01/11/2008 B
2 01/12/2008 B
and I'm looking to summarise the data so that I have the average days for each id and ref...
i.e.
ID Ref Avg_Days
1 A 15
2 A 30
2 B 30
Thanks in advance if anyone can help
Average day difference is a SUM of differences divided by COUNT(*)
SUM of differences is in fact difference between MIN and MAX:
SELECT id, ref, DATEDIFF(day, MIN(date), MAX(date)) / NULLIF(COUNT(*) - 1, 0)
FROM mytable
GROUP BY
id, ref
Something like this... not really sure how this info will help you with anything though.... need more info as to what your trying to average the days for.
SELECT ID, REF, AVG(DATEPART(day, [Date]))
FROM dbo.Table1
GROUP BY ID, REF
Reference:
AVG,
DATEPART
Using sql server 2005 try this.
DECLARE #Table TABLE(
ID INT,
Date DATETIME,
Ref VARCHAR(MAX)
)
INSERT INTO #Table (ID,Date,Ref) SELECT 1, '01 Sep 2008', 'A'
INSERT INTO #Table (ID,Date,Ref) SELECT 1, '11 Sep 2008', 'A'
INSERT INTO #Table (ID,Date,Ref) SELECT 1, '01 Oct 2008', 'A'
INSERT INTO #Table (ID,Date,Ref) SELECT 2, '01 Sep 2008', 'A'
INSERT INTO #Table (ID,Date,Ref) SELECT 2, '01 Oct 2008', 'A'
INSERT INTO #Table (ID,Date,Ref) SELECT 2, '01 Nov 2008', 'B'
INSERT INTO #Table (ID,Date,Ref) SELECT 2, '01 Dec 2008', 'B'
;WITH Ordered AS (
SELECT ID,
Ref,
Date,
ROW_NUMBER() OVER (PARTITION BY ID, Ref ORDER BY Date) SubNumber
FROM #Table t
)
SELECT Ordered.ID,
Ordered.Ref,
AVG(DATEDIFF(dd, Ordered.Date, OrderedNext.Date)) AVG_Days
FROM Ordered INNER JOIN
Ordered OrderedNext ON Ordered.ID = OrderedNext.ID
AND Ordered.Ref = OrderedNext.Ref
AND Ordered.SubNumber + 1 = OrderedNext.SubNumber
GROUP BY Ordered.ID,
Ordered.Ref
Also have a look at it mathematically:
Let say
([X(1)-X(0)] + [X(2)-X(1)] + [X(3)-X(2)] + ... + [X(n-1)-X(n-2)] + [X(n)-X(n-1)]) / (n-1).
expand the top part as
-X(0) + X(1) - X(1) + X(2) - X(2) + X(3) - ... - X(n-2) + X(n-1) - X(n-1) + X(n)
whcih end up as -X(0) + X(n)
so we have [X(n) - X(0)] / (n - 1)
so take (MAX - MIN) / (Count - 1) for count > 1