SQL Server 2012, Generate random date in specific way - sql

I am trying to create a random appointment in my db.
How can I refactor this code so StartDate can only be given Whole, Half or Quartz minutes and the EndDate adds 1 hour to StartDate?
I am using SQL Server 2012
SELECT
(SELECT TOP 1 Id from [dbo].[am_Customer] order by newid()) AS CustomerId
-- TODO: StartDate can only be given Whole, Half or Quartz hours
,(SELECT DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), getdate())) AS StartDate
-- TODO: Need to add 1 hour to StartDate
,(SELECT DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), getdate())) AS EndDate
,(SELECT TOP 1 ServiceName from [dbo].[am_Appointments]
WHERE DATALENGTH(ServiceName) > 0 order by newid()) AS ServiceName
,(SELECT TOP 1 Id from [dbo].[Employees] order by newid()) AS EmployeeId
EDIT:
Here is the solution i ended up with:
;WITH s AS (
SELECT
DATEADD(minute, ABS(CHECKSUM(NEWID()) % 350400)*15,
DATEADD(day,DATEDIFF(day,0,getdate()),0)) AS StartDate
)
SELECT
(SELECT TOP 1 Id FROM [dbo].[am_Customer] ORDER BY newid()) AS CustomerId
,(SELECT s.StartDate) AS StartDate
,(SELECT DATEADD(hour,1,s.StartDate)) AS EndDate
,(SELECT TOP 1 ServiceName from [dbo].[am_Appointments] WHERE DATALENGTH(ServiceName) > 0 ORDER BY newid()) AS ServiceName
,(SELECT TOP 1 Id FROM [dbo].[Employees] ORDER BY newid()) AS EmployeeId
FROM s

This will generate a StartDate value some time in the next 10 years that falls on a 15 minute interval, and also an EndDate an hour later:
;With s as (
SELECT
DATEADD(minute, ABS(CHECKSUM(NEWID()) % 350400)*15,
DATEADD(day,DATEDIFF(day,0,getdate()),0)) as StartDate
)
select s.StartDate,DATEADD(hour,1,s.StartDate) as EndDate
from s
This has a (small) probability of generating a StartDate that falls today and before now. If you want to avoid that, the simple fix is to change the second 0 in DATEADD(day,DATEDIFF(day,0,getdate()),0)) to a 1, and then it won't generate any values on today's date.

Use CROSS APPLY to calculate the start date, then that calculation can be referenced by its alias to calculate the end date, like so:
| STARTDATE | COLUMN_1 |
|---------------------------------|---------------------------------|
| February, 25 2020 10:30:00+0000 | February, 25 2020 11:30:00+0000 |
| July, 08 2018 18:15:00+0000 | July, 08 2018 19:15:00+0000 |
produced by
SELECT
ca1.StartDate
, dateadd(hour,1,ca1.StartDate)
from supportContacts --<< any table
CROSS APPLY (select
dateadd(minute,ABS(CHECKSUM(NEWID()) % 3650) * 15 ,DATEADD(DAY, ABS(CHECKSUM(NEWID()) % 3650), dateadd(dd, datediff(dd,0, getDate()), 0)))
) as ca1 (StartDate)
see: http://sqlfiddle.com/#!3/1fa93/14607

Related

For each quarter between two dates, add rows quarter by quarter in SQL SERVER

I have a table, with types int, datetime, datetime:
id start date end date
-- ---------- ----------
1 2019-04-02 2020-09-17
2 2019-08-10 2020-08-10
Here is create/insert:
CREATE TABLE dbo.something
(
id int,
[start date] datetime,
[end date] datetime
);
INSERT dbo.something(id,[start date],[end date])
VALUES(1,'20190402','20200917'),(2,'20190810','20200810');
What is a SQL query that can produce these results:
id Year Quarter
-- ---- ----------
1 2019 2
1 2019 3
1 2019 4
1 2020 1
1 2020 2
1 2020 3
2 2019 3
2 2019 4
2 2020 1
2 2020 2
2 2020 3
Just use a recursive CTE. This version switches to counting quarters from year 0:
with cte as (
select id,
year(start_date) * 4 + datepart(quarter, start_date) - 1 as yyyyq,
year(end_date) * 4 + datepart(quarter, end_date) - 1 as end_yyyyq
from t
union all
select id, yyyyq + 1, end_yyyyq
from cte
where yyyyq < end_yyyyq
)
select id, yyyyq / 4 as year, (yyyyq % 4) + 1 as quarter
from cte;
Here is a db<>fiddle.
If you cannot make another reference table/etc, you can use DATEDIFF (and DATEPART) using quarters, and then some simple date arithmetic.
The logic below is simply to find, for each startdate, the first quarter and then the number of additional quarters to get to the maximum. Then do a SELECT where the additional quarters are added to the startdate, to get each quarter.
The hardest part of the query to understand imo is the WITH numberlist section - all this does is generate a series of integers between 0 and the maximum number of quarters difference. If you already have a numbers table, you can use that instead.
Key code part is below, and here's a full DB_Fiddle with some additional test data.
CREATE TABLE #yourtable (id int, startdate date, enddate date)
INSERT INTO #yourtable (id, startdate, enddate) VALUES
(1, '2019-04-02', '2020-09-17'),
(2, '2019-08-10', '2020-08-20')
; WITH number_list AS
-- list of ints from 0 to maximum number of quarters
(SELECT n
FROM (SELECT ones.n + 10*tens.n AS n
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)
) AS a
WHERE n <= (SELECT MAX(DATEDIFF(quarter,startdate,enddate)) FROM #yourtable)
)
SELECT id,
YEAR(DATEADD(quarter, number_list.n, startdate)) AS [Year],
DATEPART(quarter, DATEADD(quarter, number_list.n, startdate)) AS [Quarter]
FROM (SELECT id, startdate, DATEDIFF(quarter,startdate,enddate) AS num_additional_quarters FROM #yourtable) yt
CROSS JOIN number_list
WHERE number_list.n <= yt.num_additional_quarters
DROP TABLE #yourtable
First create a date dimension table which contains date, corresponding quarter and year. Then use below query to get the result. Tweak column and table name according to your schema.
with q_date as
(
select 1 as id, '2019-04-02' :: date as start_date, '2020-09-17' :: date as end_date
UNION ALL
select 2 as id, '2019-08-10' :: date as start_date, '2020-08-10' :: date as end_date
)
select qd.id, dd.calendar_year, dd.calendar_quarter_number
from dim_date dd, q_date qd
where dd.date_dmk between qd.start_date and qd.end_date
group by qd.id, dd.calendar_year, dd.calendar_quarter_number
order by qd.id, dd.calendar_year, dd.calendar_quarter_number;

How to Calculate Employment Experience with Special Case

I am trying to calculate the years of experience
Let's say someone has multiple employments as follow:
startdate enddate
2007-08-27 2008-05-09
2007-08-27 2008-05-09
2012-01-01 2018-07-31
2013-01-06 2019-03-25
What would be the correct sql syntax to just select startdate, enddate which will be later pluged in a function to calculate the number of days for each employment?
Here's my expected result:
1st row: startdate 8/27/2007, enddate 5/9/2008
2nd row: startdate 1/1/2012, enddate 3/25/2019
The second employment took place during the same time for the 1st employment so, it will not be counted. The fourth employment started before the third employment ended so we should use the startdate for the third employment and enddate for the fourth employment
Use Distinct to remove dplicated records. Then you can use this query:
Select totaldays / 365 from
(Select Sum(
DATEDIFF(day, srartdate, enddate )
) As totaldays
)
This query returns the dates without overlapping:
select
v.startdate startdate,
min(vv.enddate) enddate
from view_appemployment v
inner join view_appemployment vv
on v.startdate <= vv.enddate
and not exists (
select * from view_appemployment vvv
where vv.enddate >= vvv.startdate and vv.enddate < vvv.enddate
)
where not exists (
select * from view_appemployment vvvv
where v.startdate > vvvv.startdate and v.startdate <= vvvv.enddate
)
group by v.startdate
See the demo
Results:
startdate | enddate
------------------ | ------------------
27/08/2007 00:00:00 | 09/05/2008 00:00:00
01/01/2012 00:00:00 | 25/03/2019 00:00:00
This is a Gaps & Islands in Sequences problem, the following query should do what you want:
CREATE TABLE #emp (empid int, startdate date,enddate date)
INSERT INTO #emp VALUES
(1,'2007-08-27','2008-05-09'),
(1,'2007-08-27','2008-05-09'),
(1,'2012-01-01','2018-07-31'),
(1,'2013-01-06','2019-03-25')
;WITH starts AS (
SELECT em.*,
(CASE WHEN EXISTS (SELECT 1
FROM #emp em2
WHERE em2.EmpID = em.EmpID AND
em2.StartDate < em.StartDate AND
em2.EndDate >= em.StartDate
) THEN 0 ELSE 1 END) AS [IsStart]
FROM #emp em )
SELECT EmpID
,MIN(StartDate) AS [StartDate]
,MAX(EndDate) AS [EndDate]
FROM (SELECT s.*, SUM(isstart) OVER (PARTITION BY EmpID ORDER BY StartDate) AS [grp]
FROM starts s
) s
GROUP BY EmpID, grp
ORDER BY EmpID
Please try this:
SELECT concat(id," row: start date ",date_format(start_date,'%d/%m/%y')," end date ",date_format(end_date,'%d/%m/%y'))as dateinfo FROM `dates`

How to get previous 7 days' data from today in SQL Server

I have a DataEntry Table called GuestAddressData(UserId INT, EDate DateTime) with users data. I need to fetch the count of users for today to previous 7 Days. My Query:
SELECT
row_number() over (order by (SELECT 1)) ID,
count(*) Total,
LEFT(Datename(weekday, Cast(EDate as date)), 3) Day
FROM
CRM0001GuestAddressData
WHERE
EDate >= dateadd(week, datediff(d, -1, getdate()-2)/7, -1)
GROUP BY
Cast(EDate as date)
ORDER BY
Cast(EDate as date)
For example if today is Friday then my expected output is:
ID | TOTAL | DAY
------------------------
1 | 78 | Sat
2 | 23 | Sun
3 | 54 | Mon
4 | 17 | Tues
5 | 56 | Wed
6 | 45 | Thus
7 | 78 | Fri - Today
but this is not correct. How to solve it?
You can "generate" a list of seven numbers and use it to build the desired dates. Then left join with your data to get the counts, including zeros:
WITH datelist(num, a, b) AS (
SELECT num, DATEADD(DAY, -num, CAST(CURRENT_TIMESTAMP AS DATE)), DATEADD(DAY, -num + 1, CAST(CURRENT_TIMESTAMP AS DATE))
FROM (VALUES (0), (1), (2), (3), (4), (5), (6)) AS v(num)
)
SELECT 7 - num AS ID, datelist.a AS Day, COUNT(IDBooking)
FROM datelist
LEFT JOIN T_Bookings ON Opened >= datelist.a AND Opened < datelist.b
GROUP BY datelist.a, datelist.num
ORDER BY datelist.a
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select cast(EDate as Date) as dDate,
count(*) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
inner join
CRM0001GuestAddressData gd on datediff(d, gd.Edate, getdate()) = t.v
WHERE
EDate >= dateadd(d, -6, cast(getdate() as date)) and EDate < dateadd(d,1,cast(getdate() as date))
GROUP BY
Cast(EDate as date)) tmp;
Note: You meant to get 7 days from yesterday, right? Nevermind, corrected based on your sample.
DBFiddle demo
EDIT: Having all days:
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select dateadd(d,-v,cast(getdate() as date)) as dDate,
count(Edate) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
left join
CRM0001GuestAddressData gd on Datediff(d,gd.EDate, getdate()) = t.v
GROUP BY
dateadd(d,-v,cast(getdate() as date))) tmp;
DBFiddle Demo

Query to return all the days of a month

This problem is related to this, which has no solution in sight: here
I have a table that shows me all sessions of an area.
This session has a start date.
I need to get all the days of month of the start date of the session by specific area (in this case)
I have this query:
SELECT idArea, idSession, startDate FROM SessionsPerArea WHERE idArea = 1
idArea | idSession | startDate |
1 | 1 | 01-01-2013 |
1 | 2 | 04-01-2013 |
1 | 3 | 07-02-2013 |
And i want something like this:
date | Session |
01-01-2013 | 1 |
02-01-2013 | NULL |
03-01-2013 | NULL |
04-01-2013 | 1 |
........ | |
29-01-2013 | NULL |
30-01-2013 | NULL |
In this case, the table returns me all the days of January.
The second column is the number of sessions that occur on that day, because there may be several sessions on the same day.
Anyone can help me?
Please try:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATEtime)
INSERT #SessionsPerArea VALUES (1,1,'2013-01-01')
INSERT #SessionsPerArea VALUES (1,2,'2013-01-04')
INSERT #SessionsPerArea VALUES (1,3,'2013-07-02')
DECLARE #RepMonth as datetime
SET #RepMonth = '01/01/2013';
WITH DayList (DayDate) AS
(
SELECT #RepMonth
UNION ALL
SELECT DATEADD(d, 1, DayDate)
FROM DayList
WHERE (DayDate < DATEADD(d, -1, DATEADD(m, 1, #RepMonth)))
)
SELECT *
FROM DayList t1 left join #SessionsPerArea t2 on t1.DayDate=startDate and t2.idArea = 1
This will work:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATE)
INSERT #SessionsPerArea VALUES
(1,1,'2013-01-01'),
(1,2,'2013-01-04'),
(1,3,'2013-07-02')
;WITH t1 AS
(
SELECT startDate
, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate), '1900-01-01') firstInMonth
, DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate) + 1, '1900-01-01')) lastInMonth
, COUNT(*) cnt
FROM #SessionsPerArea
WHERE idArea = 1
GROUP BY
startDate
)
, calendar AS
(
SELECT DISTINCT DATEADD(DAY, c.number, t1.firstInMonth) d
FROM t1
JOIN master..spt_values c ON
type = 'P'
AND DATEADD(DAY, c.number, t1.firstInMonth) BETWEEN t1.firstInMonth AND t1.lastInMonth
)
SELECT d date
, cnt Session
FROM calendar c
LEFT JOIN t1 ON t1.startDate = c.d
It uses simple join on master..spt_values table to generate rows.
Just an example of calendar table. To return data for a month adjust the number of days between < 32, for a year to 365+1. You can calculate the number of days in a month or between start/end dates with query. I'm not sure how to do this in SQL Server. I'm using hardcoded values to display all dates in Jan-2013. You can adjust start and end dates for diff. month or to get start/end dates with queries...:
WITH data(r, start_date) AS
(
SELECT 1 r, date '2012-12-31' start_date FROM any_table --dual in Oracle
UNION ALL
SELECT r+1, date '2013-01-01'+r-1 FROM data WHERE r < 32 -- number of days between start and end date+1
)
SELECT start_date FROM data WHERE r > 1
/
START_DATE
----------
1/1/2013
1/2/2013
1/3/2013
...
...
1/31/2013

Weighting a length of time to get a different Date each time

I have an arrival Date 01/01/2010, this has occurred 50 times and I want to randomise 50 departure dates using the length of stay weighting guide below, as you can the majority of these will leave 2 days later, but I cannot figure out how to write the code, Can you help.
LengthofStay LengthofStayWeighting
------------ ---------------------
1 1
2 5
3 4
4 3
5 3
6 3
7 3
8 1
9 1
10 1
I have started but have got stuck already
SELECT ArrivalDate,RAND(checksum(NEWID())) * LengthOfStay.LengthofStayWeighting AS Expr1,
ArrivalDate + Expr1 as DepartureDate
FROM Bookings, LengthOfStay
ORDER BY ArrivalDate
You may need to use DATEADD
SELECT ArrivalDate, DATEADD(day, RAND(checksum(NEWID())) * LengthOfStay.LengthofStayWeighting, ArrivalDate) AS DepartureDate
FROM Bookings, LengthOfStay
ORDER BY ArrivalDate
update: Based on your comment, I think I misunderstood the question. Is this what you need?:
SELECT ArrivalDate,
DATEADD(day, (select TOP 1 LengthofStayWeighting FROM LengthOfStay group by LengthofStayWeighting ORDER BY LengthofStayWeighting DESC), ArrivalDate) AS DepartureDate
FROM Bookings
ORDER BY ArrivalDate
Basically you need to obtain the length that is repeated the most, in your case "1". If so, I think you need to include a FOREIGN Key..
SELECT ArrivalDate,
DATEADD(day, (select TOP 1 LengthofStayWeighting FROM LengthOfStay l WHERE b.Id = l.BookingId GROUP BY LengthofStayWeighting ORDER BY LengthofStayWeighting DESC), ArrivalDate) AS DepartureDate
FROM Bookings b
ORDER BY ArrivalDate
You are trying to pull numbers from a cumulative distribution. This requires generating a random number and then pulling from the distribution.
The following code gives an example:
with LengthOfStay as (select 1 as LengthOfStay, 1 as LengthOfStayWeighting union all
select 2 as LengthOfStay, 5 union all
select 3, 4 union all
select 4, 4
),
Bookings as (select cast('2013-01-01' as DATETIME) as ArrivalDate),
CumeLengthOfStay as
(select los.*,
(select SUM(LengthOfStayWeighting) from LengthOfStay los2 where los2.LengthOfStay <= los.LengthOfStay
) as cumeweighting
from LengthOfStay los
) -- select * from CumeLengthOfStay
SELECT ArrivalDate, clos.LengthOfStay, randnum % sumweighting, sumweighting,
ArrivalDate + clos.LengthOfStay as DepartureDate
FROM (select b.*, ABS(CAST(NEWID() AS binary(6))+0) as randnum
from Bookings b
) b cross join
(select SUM(LengthOfStayWeighting) as sumweighting from LengthOfStay) const left outer join
CumeLengthOfStay clos
on (b.randnum % const.sumweighting) between clos.cumeweighting - clos.LengthOfStayWeighting and clos.cumeweighting - 1
ORDER BY ArrivalDate
Basically, you add up the weights, generate a random number less than the highest weight (using the % operator), and then look up this value in the cumulative sum of the weights.