SQL - Create a table with unique date & currency records for every currency code in a table (between two defined dates) - sql

So I have a currency code table with ~40 records and a query that produces a list of dates between two days. (I also have a table, but figured this would work)
The goal is to create a table of every combination of currency and day. So if there are 4 currencies and 10 days there would be 400 records of every combination.
Thanks for any insight!
PK
Currency
1
USD
2
EUR
3
CAD
4
KRW
5
CNY
6
JPY
7
GBP
8
PLN
9
NZD
...
...
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = Getdate()-15
SET #EndDateTime = Getdate()-1
;WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT CAST(DateData as Date)
FROM DateRange

Cross join is what you want.
WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT DateData, Currency
FROM DateRange
CROSS JOIN CurrencyTableName
If a column is already a date type you don't have to cast it to Date

Related

Dynamic Grouping based on date field SSRS

In my SSRS report which has a dataset with date field as below
The dataset returns the following type of data (just an example)
Voucher Amount Date
R3221 € 3,223.00 1-Dec-17
R3222 € 123.00 28-Nov-17
R3223 € 1,233.00 19-Oct-17
R3224 € 442.00 27-Sep-17
R3225 € 123.00 17-Nov-17
R3226 € 423.00 29-Oct-17
R3227 € 1,234.00 8-Oct-17
What I would like to know is how to show this data grouped by Voucher and Due Date
User should be able to select the Start Date and the period type (Day, Week, month,) and the interval between the two columns (e.g 3 , 10 or 30 or any other number)
so the user should be able to select the period type, e.g if he select Day and interval as 3 then the report should show
**voucher start date <Dynamic grouping columns based on the selection criteria>**
R3221
R3222
R3223
R3224
R3225
R3226
R3227
Any kind of hint will be much appreciated!
This is only part of the solution. First generate date periods you want in the report, then do a LEFT JOIN against your table, filter and group.
declare #dateFrom date
declare #periodInDays int
declare #periods int --how many periods of 10 days
set #periodInDays = 10;
set #periods = 15; --how many periods of 10 days
set #dateFrom = getdate();
with [dates] as (
select #dateFrom as date --start
union all
select dateadd(day, #periodInDays, [date]) as date
from [dates]
where [date] < DATEADD(day, #periodInDays * #periods, #dateFrom) --end
)
select [date]
from [dates]
option (maxrecursion 0)
In this example returns 16 dates (1 per row), from start date (2017-12-01), every 10 days
2017-12-01, 2017-12-11, 2017-12-21, 2017-12-31, 2018-01-10, 2018-01-20
2018-01-30, 2018-02-09, 2018-02-19, 2018-03-01, 2018-03-11, 2018-03-21
2018-03-31, 2018-04-10, 2018-04-20, 2018-04-30
To filter by period get start date from the above date table, so the end date for that period can be calculated as DATEADD(day, #periodInDays, [date]) and we don't need to look into next row at this point.
Just let me know if someone find more straight forward solution.

How can I contstruct this T-SQL query involving missing date ranges?

I'll try to keep the specific details of my problem out of this question and focus only on the pertinent issues.
Lets say I have an Assets table with a primary key of AssetID.
I have another table called ProcessedDates with primary key PID and with additional columns AssetID, StartDate, EndDate.
I want to run a process for a list of assets between a start date and end date. Before I can run this process, I need to know which assets and which date ranges have already been processed.
For example, there are 2 entries in ProcessedDates:
AssetID StartDate EndDate
--------------------------
Asset1 Day4 day7
Asset1 Day10 Day12
I want to process Asset1 between day2 and day11. I don't need to waste time by processing on days that have already been done so in this example, I will only process asset1 from day2 to day3 and from day8 to day 9.
So what I need is a query that returns the gaps in the date ranges. In this case, the result set will be 2 lines:
AssetID StartDate EndDate
--------------------------
Asset1 day2 day3
Asset1 day8 day9
In my actual requirement I have many assetIDs. The ProcessedDates table may have multiple entries for each asset or none at all and each asset does not necessarily have the same processed dates as any other asset.
declare #StartDate date, #EndDate date (assume these are given)
--get distinct assets
select distinct AssetIDs from (some query) into #Assets
--get the already processed date ranges
select p.AssetID, p.StartDate, p.EndDate
from ProcessedDates p inner join #Assets a on p.AssetID = a.AssetID
where p.StartDate between #StartDate and #EndDate
or p.EndDate between #StartDate and #EndDate
From here I have no clue how to proceed. How do I get it to return AssetID, StartDate, EndDate for all the gaps in between?
Something like this:
declare #StartDate date = '2015-01-01', #EndDate date = '2015-05-05'
declare #Assets table (AssetID varchar(50), StartDate date, EndDate date)
declare #AssetTypes table (AssetID varchar(50))
insert into #AssetTypes values
('Asset1'),
('Asset2')
insert into #Assets values
('Asset1', '2014-12-10', '2014-12-31'), -- Ignored
('Asset1', '2015-02-02', '2015-03-02'),
('Asset1', '2015-03-05', '2015-05-01'),
('Asset1', '2015-06-01', '2015-06-06') -- Ignored
;WITH Base AS (
SELECT AT.AssetID
, CASE WHEN A.AssetID IS NULL THEN 1 ELSE 0 END EmptyAsset
, A.StartDate
, A.EndDate
, ROW_NUMBER() OVER (PARTITION BY AT.AssetID ORDER BY StartDate) RN
FROM #AssetTypes AT
LEFT JOIN #Assets A ON A.AssetID = AT.AssetID
WHERE A.AssetID IS NULL -- case of totally missing asset
OR (StartDate <= #EndDate AND EndDate >= #StartDate)
)
-- first missing range, before the first row
SELECT AssetID, #StartDate StartDate, DATEADD(dd, -1, StartDate) EndDate
FROM Base
WHERE RN = 1 AND StartDate > #StartDate
UNION ALL
-- each row joined with the next one
SELECT B1.AssetID, DATEADD(dd, 1, B1.EndDate), ISNULL(DATEADD(dd, -1, B2.StartDate), #EndDate)
FROM Base B1
LEFT JOIN Base B2 ON B2.AssetID = B1.AssetID AND B2.RN = B1.RN + 1
WHERE B1.EmptyAsset = 0
AND (B2.AssetID IS NULL -- Last row case
OR DATEADD(dd, 1, B1.EndDate) < B2.StartDate) -- Other rows case
AND B1.EndDate < #EndDate -- If the range ends after #EndDate, nothing to do
UNION ALL
-- case of totally missing asset
SELECT AssetID, #StartDate, #EndDate
FROM Base
WHERE EmptyAsset = 1
The main idea is that each row is joined with the next one. A new range is generated (if necessary) between the EndDate + 1 and the StartDate - 1. There is a special handling for the last row (B2.AssetID IS NULL and ISNULL(... #EndDate)). The first SELECT generated a row before the first range, and the last select is for the special case of no ranges present for an asset.
As I've written in the comments, it gets ugly quite quickly.
Here's an simple version to get the result you want. I use integer as date, and assume the min date is 0 and the max date is 999.
--DDL
create table Assets (AssetID integer, StartDate integer, EndDate integer);
insert into Assets values
(1,4,7),
(1,10,12),
(1,15,17),
(2,5,7),
(2,9,10);
with temp as(
select a1.AssetId,
a1.enddate+1 as StartDate,
coalesce(min(a2.startdate) - 1,999) as EndDate
from Assets a1
left join Assets a2
on a1.assetid = a2.assetid
and a1.enddate < a2.startdate
group by a1.assetid,a1.enddate
union all
select a.assetid,0,min(startdate) -1
from Assets a
group by a.assetid
)
select AssetId,
case when StartDate<2 then 2 else StartDate end as StartDate,
case when EndDate>11 then 11 else EndDate end as EndDate
from temp
where StartDate<=11 and EndDate>=2
order by AssetId,StartDate
The temp table can get the missing ranges. Then filter the match ranges between Day2 and Day11, will get the result that you want.
AssetId StartDate EndDate
1 2 3
1 8 9
2 2 4
2 8 8
2 11 11
Here's the SqlFiddle Demo

SQL Server 2008 - Enumerate multiple date ranges

How can I enumerate multiple date ranges in SQL Server 2008? I know how to do this if my table contains a single record
StartDate EndDate
2014-01-01 2014-01-03
;WITH DateRange
AS (
SELECT #StartDate AS [Date]
UNION ALL
SELECT DATEADD(d, 1, [Date])
FROM DateRange
WHERE [Date] < #EndDate
)
SELECT * FROM DateRange
OUTPUT
2014-01-01, 2014-01-02, 2014-01-03
I am however lost as how to do it if my table contains multiple records. I could possibly use the above logic in a cursor but want to know if there is a set based solution instead.
StartDate EndDate
2014-01-01 2014-01-03
2014-01-05 2014-01-06
DESIRED OUTPUT:
2014-01-01, 2014-01-02, 2014-01-03, 2014-01-05, 2014-01-06
Well, let's see. Define the ranges as a table. Then generate the full range of dates from the first to the last date. Finally, select the dates that are in the range:
with dateranges as (
select cast('2014-01-01' as date) as StartDate, cast('2014-01-03' as date) as EndDate union all
select '2014-01-05', '2014-01-06'
),
_dates as (
SELECT min(StartDate) AS [Date], max(EndDate) as enddate
FROM dateranges
UNION ALL
SELECT DATEADD(d, 1, [Date]), enddate
FROM _dates
WHERE [Date] < enddate
),
dates as (
select [date]
from _dates d
where exists (select 1 from dateranges dr where d.[date] >= dr.startdate and d.[date] <= dr.enddate)
)
select *
from dates
. . .
You can see this work here.
You could grab the min and max dates first, like so:
SELECT #startDate = MIN(StartDate), #endDate = MAX(EndDate)
FROM YourTable
WHERE ...
And then pass those variables into your date range enumerator.
Edit... Whoops, I missed an important requirement. See the accepted answer.
As GordonLinoff mentioned, you should:
Store your ranges in a table
Generate a range of dates that encompasses your ranges
Filter down to only those dates that fall within the range
The following query builds up a collection of numbers, and then uses that to quickly generate all of the dates that fall within each range.
-- Create a table of digits (0-9)
DECLARE #Digits TABLE (digit INT NOT NULL PRIMARY KEY);
INSERT INTO #Digits(digit)
VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
WITH
-- Store our ranges in a common table expression
CTE_DateRanges(StartDate, EndDate) AS (
SELECT '2014-01-01', '2014-01-03'
UNION ALL
SELECT '2014-01-05', '2014-01-06'
)
SELECT DATEADD(DAY, NUMBERS.num, RANGES.StartDate) AS Date
FROM
(
-- Create the list of all 3-digit numbers (0-999)
SELECT D3.digit * 100 + D2.digit * 10 + D1.digit AS num
FROM #Digits AS D1
CROSS JOIN #Digits AS D2
CROSS JOIN #Digits AS D3
-- Add more CROSS JOINs to #Digits if your ranges span more than 999 days
) NUMBERS
-- Join to our ranges table to generate the dates and filter them
-- down to those that fall within a range
INNER JOIN CTE_DateRanges RANGES
ON DATEADD(DAY, NUMBERS.num, RANGES.StartDate) <= RANGES.EndDate
ORDER BY
Date
The date creation is done by joining our number list with our date ranges, using the number as a number of days to add to the StartDate of the range. We then filter out any results where the generated date for a given range falls beyond that range's EndDate. Since we're adding a non-negative number of days to the StartDate to generate the date, we know that our date will always be greater-than-or-equal-to the StartDate of the range, so we don't need to include StartDate in the WHERE clause.
This query will return DATETIME values. If you need a DATE value, rather than a DATETIME value, you can simply cast the value in the SELECT clause.
Credit goes to Itzik Ben-Gan for the digits table.

display list of dates by giving start and end date and get data against those dates

I have a sql table having three columns
id, balance, datetime
i want to get data from the table by giving time duration. suppose i want to get data between 1/1/2013 to 1/15/2013. data is given table is shown as:
#id Datetime Balance #
1 1/1/2013 1500
2 1/2/2013 2000
3 1/4/2013 1500
4 1/5/2013 2500
now I want the output as
#id Datetime Balance #
1 1/1/2013 1500
2 1/2/2013 2000
3 1/3/2013 0
4 1/4/2013 1500
5 1/5/2013 2500
i want to display all the dates and if there is no balance against the date. it shows O or null value
I would get rid of ID column as it is useless when you are adding additional rows and do something like this:
set dateformat mdy
declare #tmpTable table (dates date)
declare #startDate date = '1/1/2013'
declare #endDate date = '1/15/2013'
while #startDate <= #endDate
begin
insert into #tmpTable (dates) values (#startDate)
set #startDate = DATEADD(DAY, 1, #startDate)
end
select tmp.dates, yourtable.balance
from #tmpTable as tmp
left outer join yourTable on yourTable.[Datetime] = tmp.dates
where yourtable.[Datetime] between #startDate and #endDate
I'm not sure what flavor of SQL you're using but you can a table of all dates and to do an outer join to it. For example, if you have a table of all dates called 'Dates', then you could do:
select id, dateTime, balance from table1 t RIGHT JOIN Dates d on t.dateTime = d.dateTime
where t.dateTime BETWEEN '1/1/2013' AND '1/5/2013'
You can do it by creating a cte with the dates between your daterange and outer joining it with your table.
with cte as
(
select start_date dt
union all
select dateadd(dd,1,dt) from cte where dt < end_date
)
select id, cte.dt, balance from cte left outer join yourtable b on cte.dt = b.date;

Generate missing dates + Sql Server (SET BASED)

I have the following
id eventid startdate enddate
1 1 2009-01-03 2009-01-05
1 2 2009-01-05 2009-01-09
1 3 2009-01-12 2009-01-15
How to generate the missing dates pertaining to every eventid?
Edit:
The missing gaps are to be find out based on the eventid's. e.g. for eventid 1 the output should be 1/3/2009,1/4/2009,1/5/2009.. for eventtype id 2 it will be 1/5/2009, 1/6/2009... to 1/9/2009 etc
My task is to find out the missing dates between two given dates.
Here is the whole thing which i have done so far
declare #tblRegistration table(id int primary key,startdate date,enddate date)
insert into #tblRegistration
select 1,'1/1/2009','1/15/2009'
declare #tblEvent table(id int,eventid int primary key,startdate date,enddate date)
insert into #tblEvent
select 1,1,'1/3/2009','1/5/2009' union all
select 1,2,'1/5/2009','1/9/2009' union all
select 1,3,'1/12/2009','1/15/2009'
;with generateCalender_cte as
(
select cast((select startdate from #tblRegistration where id = 1 )as datetime) DateValue
union all
select DateValue + 1
from generateCalender_cte
where DateValue + 1 <= (select enddate from #tblRegistration where id = 1)
)
select DateValue as missingdates from generateCalender_cte
where DateValue not between '1/3/2009' and '1/5/2009'
and DateValue not between '1/5/2009' and '1/9/2009'
and DateValue not between '1/12/2009'and'1/15/2009'
Actually what I am trying to do is that, I have generated a calender table and from there I am trying to find out the missing dates based on the id's
The ideal output will be
eventid missingdates
1 2009-01-01 00:00:00.000
1 2009-01-02 00:00:00.000
3 2009-01-10 00:00:00.000
3 2009-01-11 00:00:00.000
and also it has to be in SET BASED and the start and end dates should not be hardcoded
Thanks in adavnce
The following uses a recursive CTE (SQL Server 2005+):
WITH dates AS (
SELECT CAST('2009-01-01' AS DATETIME) 'date'
UNION ALL
SELECT DATEADD(dd, 1, t.date)
FROM dates t
WHERE DATEADD(dd, 1, t.date) <= '2009-02-01')
SELECT t.eventid, d.date
FROM dates d
JOIN TABLE t ON d.date BETWEEN t.startdate AND t.enddate
It generates dates using the DATEADD function. It can be altered to take a start & end date as parameters. According to KM's comments, it's faster than using the numbers table trick.
Like rexem - I made a function that contains a similar CTE to generate any series of datetime intervals you need. Very handy for summarizing data by datetime intervals like you are doing.
A more detailed post and the function source code are here:
Insert Dates in the return from a query where there is none
Once you have the "counts of events by date" ... your missing dates would be the ones with a count of 0.