SQL How To Group by Date Ranges - sql

Can someone help me figure out to group by a range of dates??
Right now I have query similar to this
Select date, count(x)
from data
group by date
This returns results that look like this
2011/1/1 10
2011/1/2 5
2011/1/3 8
2011/1/4 3
etc...
But I would like to count every 2 days so that the data would look like this
2011/1/1 15
2011/1/3 11
Any ideas??
Thanks

You could normalize the dates into groups of 2 by converting to a numerical integer value, and reducing to the even numbers. A simple way to do that is val / 2 * 2, because the first / 2 will be truncated of any decimal places (as long as the type of val is an integer!), and * 2 will return it to the original value except normalized to an even number. Here is an example that normalizes and groups the results using a CTE data source:
;with Data as (
select '1/1/2011' as [date], 1 as x union
select '1/1/2011' as [date], 2 as x union
select '1/1/2011' as [date], 3 as x union
select '1/1/2011' as [date], 4 as x union
select '1/1/2011' as [date], 5 as x union
select '1/1/2011' as [date], 6 as x union
select '1/1/2011' as [date], 7 as x union
select '1/1/2011' as [date], 8 as x union
select '1/1/2011' as [date], 9 as x union
select '1/1/2011' as [date], 10 as x union
select '1/2/2011' as [date], 11 as x union
select '1/2/2011' as [date], 12 as x union
select '1/2/2011' as [date], 13 as x union
select '1/2/2011' as [date], 14 as x union
select '1/2/2011' as [date], 15 as x union
select '1/3/2011' as [date], 16 as x union
select '1/3/2011' as [date], 17 as x union
select '1/3/2011' as [date], 18 as x union
select '1/3/2011' as [date], 19 as x union
select '1/3/2011' as [date], 20 as x union
select '1/3/2011' as [date], 21 as x union
select '1/3/2011' as [date], 22 as x union
select '1/3/2011' as [date], 23 as x union
select '1/4/2011' as [date], 24 as x union
select '1/4/2011' as [date], 25 as x union
select '1/4/2011' as [date], 26 as x
)
Select
cast(cast(cast(Date as datetime) as integer) / 2 * 2 as datetime) as date,
count(x)
from data
group by cast(cast(Date as datetime) as integer) / 2 * 2
Output:
date (No column name)
2011-01-01 00:00:00.000 15
2011-01-03 00:00:00.000 11

Select floor((date - trunc(date,'MM')) / 2), count(x)
from data
group by floor((date - trunc(date,'MM')) / 2)

Related

SQL: Create multiple rows for a record based on months between two dates

My table has records as below for different Id's and different start and end dates
ID, Startdate, Enddate
1, 2017-02-14, 2018-11-05
I want to write an SQL without using date dimension table that gives below output: Basically one record for each month between start and end date.
1, 2017, 02
1, 2017, 03
1, 2017, 04
1, 2017, 05
1, 2017, 06
1, 2017, 07
1, 2017, 08
1, 2017, 09
1, 2017, 10
1, 2017, 11
1, 2017, 12
1, 2018, 01
1, 2018, 02
1, 2018, 03
1, 2018, 04
1, 2018, 05
1, 2018, 06
1, 2018, 07
1, 2018, 09
1, 2018, 10
1, 2018, 11
Please use below query example:
set #start_date = '2017-02-14';
set #end_date = LAST_DAY('2018-11-05');
WITH RECURSIVE date_range AS
(
select MONTH(#start_date) as month_, YEAR(#start_date) as year_, DATE_ADD(#start_date, INTERVAL 1 MONTH) as next_month_date
UNION
SELECT MONTH(dr.next_month_date) as month_, YEAR(dr.next_month_date) as year_, DATE_ADD(dr.next_month_date, INTERVAL 1 MONTH) as next_month_date
FROM date_range dr
where next_month_date <= #end_date
)
select month_, year_ from date_range
order by next_month_date desc
This is what I did and it worked like a charm:
-- sample data
WITH table_data
AS (
SELECT 1 AS id
,cast('2017-08-14' AS DATE) AS start_dt
,cast('2018-12-16' AS DATE) AS end_dt
UNION ALL
SELECT 2 AS id
,cast('2017-09-14' AS DATE) AS start_dt
,cast('2019-01-16' AS DATE) AS end_dt
)
-- find minimum date from the data
,starting_date (start_date)
AS (
SELECT min(start_dt)
FROM TABLE_DATA
)
--get all months between min and max dates
,all_dates
AS (
SELECT last_day(add_months(date_trunc('month', start_date), idx * 1)) month_date
FROM starting_date
CROSS JOIN _v_vector_idx
WHERE month_date <= add_months(start_date, abs(months_between((
SELECT min(start_dt) FROM TABLE_DATA), (SELECT max(end_dt) FROM TABLE_DATA))) + 1)
ORDER BY month_date
)
SELECT id
,extract(year FROM month_date)
,extract(month FROM month_date)
,td.start_dt
,td.end_dt
FROM table_data td
INNER JOIN all_dates ad
ON ad.month_date > td.start_dt
AND ad.month_date <= last_day(td.end_dt)
ORDER BY 1
,2
You have to generate date and from that have to pick year and month
select distinct year(date),month( date) from
(select * from (
select
date_add('2017-02-14 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date
from
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n1,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n2,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n3,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n4,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n5
) a
where date >'2017-02-14 00:00:00.000' and date < '2018-11-05'
) as t

SQL Convert Week Number to Date (dd/MM)

I am trying to convert the week number (for example: 21) in SQL-Server to the date (from the Monday of that week) in dd/MM format.
I have searched online but cannot seem to find anything that I could use.
Is this something I can do?
Any help or advice is appreciated.
Thank you in advance.
Try this,
declare #wk int set #wk = 21
declare #yr int set #yr = 2016
select dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4 -
datepart(dw, dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4) + 1
or try this way
declare #wk int = 21
select dateadd(week,#wk-1, DATEADD(wk, DATEDIFF(wk,-1,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), 0))
You can do it something like:
declare #Week_Number int, #Year int, #Year_Start_Day date, #Week_Day date
select
#Week_Number = 1,
#Year = 2016
select #Year_Start_Day = cast(#Year as nvarchar(4)) + '0101'
select #Week_Day = dateadd(wk, #Week_Number, #Year_Start_Day)
select dateadd(dd, 1 - datepart(weekday, #Week_Day), #Week_Day)
This will do:
DECLARE #y int = 2016,
#w int = 21
SELECT CONVERT(nvarchar(5),DATEADD(day,#w*7-(DATEPART(WEEKDAY,CAST(#y as nvarchar(4))+'-01-01')-2),CAST(#y as nvarchar(4))+'-01-01'),3)
Output:
23/05
How about this?
DECLARE #YearNum SMALLINT = 2016;
DECLARE #WeekNum TINYINT=25;
select
SUBSTRING(CONVERT(VARCHAR(10),selected_date,105),0,6) AS WeeKDate
from
(select DATEADD(dd,t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i,'1970-01-01') selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where YEAR(selected_date)=#YearNum
AND DATEPART(WK,selected_date)=#WeekNum
AND DATEPART(WEEKDAY,selected_date)=2 -- Monday

Next 5 Available Dates

I wonder if anyone could tell me how I can get the next 5 available dates using a table which only stores the Weekend dates and Bank Holiday dates.. So it has to select the next 5 days which do not collide with any dates in the table.
I would like to see the following results from this list of dates:
07/11/2015 (Saturday)
08/11/2015 (Sunday)
09/11/2015 (Holiday)
14/11/2015 (Saturday)
15/11/2015 (Sunday)
Results:
05/11/2015 (Thursday)
06/11/2015 (Friday)
10/11/2015 (Tuesday)
11/11/2015 (Wednesday)
12/11/2015 (Thursday)`
Based on limited information, here's a quick hack:
with offsets(n) as (
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9 union all
select 10 union all
select 11
)
select top 5 dateadd(dd, n, cast(getdate() as date)) as dt from offsets
where dateadd(dd, n, cast(getdate() as date) not in (
select dt from <exclude_dates>
)
order by dt
A possible solution is to create a table of all possible dates in a year.
select top 5 date
from possible_dates
where date not in
(select date from unavailable_dates)
and date > [insert startdate here]
order by date

How do I get running total for distinct values in a column

I'd like to group by date range as the below example
Date ItemNo Qty
==================================
1/1/2014 101 20
2/1/2014 102 10
3/1/2014 103 5
4/1/2014 104 10
1/1/2014 101 5
2/1/2014 101 10
3/1/2014 102 15
4/1/2014 104 20
I want to get the balance daily by sum the qty till that day grouped by ItemNo to be as below
Date ItemNo Qty
==================================
1/1/2014 101 25
2/1/2014 101 35
2/1/2014 102 10
3/1/2014 102 25
3/1/2014 103 5
4/1/2014 104 30
I know I can solve the problem by using cursors but I need another solution
thanks
so just use SUM
SELECT Date, ItemNo, SUM(Qty)
FROM table
GROUP BY Date, ItemNo
please read on agregate function and sum
Edit
i took your comment and did this:
SELECT a.Date, a.ItemNo, tmp.qty + a.ItemNo
FROM table a
JOIN (SELECT TOP 1 * FROM table t WHERE t.date < a.Date ORDER BY t.date DESC) tmp ON a.ItemNo = tmp.ItemNo
i'm checking it now, so it might need some tweaks, but i wanted to release it straight away so you'll have the general idea
Here is your sample table
SELECT * INTO #TEMP
FROM
(
SELECT '1/1/2014' [DATE], 101 [ItemNo], 20 QTY
UNION ALL
SELECT '2/1/2014', 102, 10
UNION ALL
SELECT '3/1/2014', 103, 5
UNION ALL
SELECT '4/1/2014', 104, 10
UNION ALL
SELECT '1/1/2014', 101, 5
UNION ALL
SELECT '2/1/2014', 101, 10
UNION ALL
SELECT '3/1/2014', 102, 15
UNION ALL
SELECT '4/1/2014', 104, 20
)TAB
Use Row_Number to get number for each Item's date do the sum inside CTE
;WITH CTE1 AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY [ItemNo] ORDER BY CAST([DATE] AS DATE))RNO,
[DATE],[ItemNo],SUM(Qty)Qty
FROM #TEMP
GROUP BY [DATE],[ItemNo]
)
SELECT A.RNO,[DATE],[ItemNo],
CASE WHEN RNO=1 THEN Qty
ELSE (SELECT SUM(b.Qty)
FROM CTE1 b
WHERE A.ItemNo=B.ItemNo AND B.RNO<=A.RNO)
END QTY
FROM CTE1 A
ORDER BY A.itemno,CAST(A.[DATE] AS DATE);
RESULT
Here's a solution using a recursive common table expression.
Not sure if it will be faster or not than the answer by Sarath Avanavu, but you can try!
Sample data:
DECLARE #t TABLE([Date] DATETIME, ItemNo INT, QTY INT)
INSERT #t
( Date, ItemNo, QTY )
SELECT '1/1/2014', 101, 20
UNION ALL SELECT '2/1/2014', 102, 10
UNION ALL SELECT '3/1/2014', 103, 5
UNION ALL SELECT '4/1/2014', 104, 10
UNION ALL SELECT '1/1/2014', 101, 5
UNION ALL SELECT '2/1/2014', 101, 10
UNION ALL SELECT '3/1/2014', 102, 15
UNION ALL SELECT '4/1/2014', 104, 20
Query:
;WITH dSum AS (
SELECT [Date], ItemNo, SUM(QTY) AS QTY
FROM #t AS t
GROUP BY [Date], [ItemNo]
), dSumRN AS (
SELECT [Date], ItemNo, QTY, ROW_NUMBER() OVER(PARTITION BY ItemNo ORDER BY [Date]) AS rn
FROM dSum
), cte AS (
SELECT [Date], ItemNo, QTY, rn
FROM dSumRN
WHERE rn = 1
UNION ALL SELECT
dSumRN.[Date], dSumRN.ItemNo, cte.QTY + dSumRN.QTY AS QTY, cte.rn + 1 AS rn
FROM cte
JOIN dSumRN ON cte.ItemNo = dSumRN.ItemNo AND cte.rn + 1 = dSumRN.rn
)
SELECT [Date], [ItemNo], QTY FROM cte
ORDER BY [Date], [ItemNo]
OPTION (MAXRECURSION 1000) -- maximum this can be set to is 32767
Easiest code below for your query:
select Date,itemno,
(select sum(Qty) from #temp where date<=T.date and itemno=T.itemno)
from #temp T
group by Date,itemno order by date

Insert 24 hour period into database

This is more of a best practices question although I am struggling with the sql but the 2 are linked. I want to insert into a sql database the date and time for the forthcoming 24 hour period starting at 6am.
I think it would be best to run a schedualed SQL job at say 1am for the forthcoming day. This would create one column and 24 rows spanning for example 20/03/2013 06:00 to 21/03/2013 05:00.
thanks
The problem is the date time functions. Here is an example of how it could be done (in SQL Server):
insert into t(thedatetime)
select dateadd(hour, hrs.hr, cast(CAST(getdate() as DATE) as datetime))
from (select 0 as hr union all select 1 union all select 2 union all select 3 union all
select 4 union all select 5 union all select 6 union all select 7 union all
select 8 union all select 9 union all select 10 union all select 11 union all
select 12 union all select 13 union all select 14 union all select 15 union all
select 16 union all select 17 union all select 18 union all select 19 union all
select 20 union all select 21 union all select 22 union all select 23
) as hrs;
In Oracle, the select would might be:
select trunc(sysdate) + hrs.hr/24.0
And there are similar constructs for other databases.
This is assuming that it is running after midnight on the date in question. For the next day, you would have to add one day to the current date.
This works in Postgres:
select timestamp '2013-03-30 06:00' + interval '1' hour * i
from generate_series(0,23) i;
This will work for ORACLE
SELECT To_date('22-03-2013 '||(CASE WHEN (6+(LEVEL-1))>12 THEN (CASE WHEN LEVEL>19 THEN (LEVEL-12-7) ELSE (6+(LEVEL-1))-12 END) ELSE (6+(LEVEL-1)) END)||':00:00','DD-MM-YYYY HH:MI:SS')
FROM dual CONNECT BY LEVEL<=24;