Conditional Where when using Group By - sql

I have a working procedure to show the report for given range of period.
The below proc finds all the bookings for the given period of time and shows for each month group
CREATE PROCEDURE [dbo].[GetSummaryOverviewReport] (
#StartDate SMALLDATETIME = NULL
,#EndDate SMALLDATETIME = NULL
,#IncludeVAT BIT = 0
,#AddedAfter SMALLDATETIME
,#HasObservation BIT = 0
,#IncludeCancelledBooking BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN
SELECT YEAR(StartTime) [Year]
,MONTH(StartTime) [Month]
,COUNT(BookingId) [BookingCount]
,SUM(CASE
WHEN IsVAT = 1
AND #IncludeVAT = 1
THEN (Price / 100) * 80
ELSE Price
END) AS TotalPrice
,PaidCount = sum(CASE
WHEN B.PaymentStatus = 'FullyPaid'
THEN 1
ELSE 0
END)
,PaidSum = sum(CASE
WHEN B.PaymentStatus = 'FullyPaid'
THEN (
CASE
WHEN IsVAT = 1
AND #IncludeVAT = 1
THEN (Price / 100) * 80
ELSE Price
END
)
ELSE 0
END)
,C.CategoryId
,CategoryName
FROM Category c
LEFT JOIN Booking B ON C.CategoryId = B.CategoryId
WHERE B.AddedDateTime > #AddedAfter
AND ISNULL(B.IsCancelled, 0) = 0
AND (
(
B.StartTime BETWEEN #StartDate
AND #EndDate
)
OR (
B.StartTime BETWEEN #StartDate
AND #EndDate
)
)
AND (
#HasObservation = 0
OR B.PatientID IN (
SELECT PatientID
FROM PatientXObservation O
WHERE ObservationId IN (
1
,2
,4
)
)
)
GROUP BY YEAR(StartTime)
,MONTH(StartTime)
,C.CategoryId
,CategoryName
ORDER BY 1
,2
,CategoryName
END
END
Now we want to add a filter #IncludeCancelledBooking parameter. So user can select whether to show cancelled bookings or not. As of now the procedure ignores the cancelled booking and shows only non cancelled booking..
I couldn't able to find out how to add this condition while grouping the records. I need to use AND ISNULL(B.IsCancelled, 0) = 0 based on #IncludeCancelledBooking

Is this the logic that you want?
(#IncludeCancelled <> 'Y' OR Booking ISNULL(B.IsCancelled, 0) = 0)

If IsCancelled is also a BIT(like #IncludeCancelled), then you could use:
AND ISNULL(B.IsCancelled, 0) IN (0, #IncludeCancelled)
If #IncludeCancelled is 0, then this is effectively the same logic you have now (i.e. = 0). If #IncludeCancelled is 1, then this includes where IsCancelled = 1.

Assuming you are passing value 1 for the variable #IncludeCancelled for including the cancelled booking. Then change the WHERE condition like below.
AND ISNULL(B.IsCancelled,0)=
CASE WHEN #IncludeCancelled=1
THEN ISNULL(B.IsCancelled,0)
ELSE 0 END

Related

Combine multiple queries in 1 return row

I have a query which should return 1 row for each DetectorID. What we currently have is the following:
DECLARE #StartDate datetime SET #StartDate = '2017-12-01 00:00'
DECLARE #EndDate datetime select #EndDate ='2018-01-01 00:00'
DECLARE #Limit1 real SET #Limit1 = 200; DECLARE #AssenLimit1 real SET #AssenLimit1 = 0;
DECLARE #Limit2 real SET #Limit2 = 300; DECLARE #AssenLimit2 real SET #AssenLimit2 = 0;
DECLARE #Limit3 real SET #Limit3 = 350; DECLARE #AssenLimit3 real SET #AssenLimit3 = 0;
DECLARE #Limit4 real SET #Limit4 = 400; DECLARE #AssenLimit4 real SET #AssenLimit4 = 0;
DECLARE #Limit5 real set #Limit5 = 500; DECLARE #AssenLimit5 real SET #AssenLimit5 = 0;
SELECT DetectorID, AVG(ValidWIM) AS 'PI_mean_WIM',
COUNT(ValidWIM) AS 'PI_mean_WIM2',
COUNT(CASE
WHEN ValidWIM > 0.5 THEN ValidWIM ELSE NULL END)
AS 'PI_mean_WIM3'
,AVG((ValidWDD_Left+ValidWDD_Right)/2) AS 'PI_mean_WDD'
,COUNT(ValidWDD_Left) AS 'PI_mean_WDD2'
,COUNT (CASE
WHEN (ValidWDD_Left>0.5 AND ValidWDD_Right>0.5) THEN ValidWDD_Left ELSE NULL END)
AS PI_mean_WDD3
,COUNT(CASE
WHEN AxleLoad IS NOT NULL Then TrainPassageInformationID ELSE NULL END)
AS 'nWheels'
, COUNT(CASE
WHEN (PeakForceLeft > #Limit1 OR PeakForceRight>#Limit1) THEN TrainPassageInformationID ELSE NULL END)
AS 'n>200'
, COUNT(CASE
WHEN (PeakForceLeft > #Limit2 OR PeakForceRight>#Limit2) THEN TrainPassageInformationID ELSE NULL END)
AS 'n>300'
, COUNT(CASE
WHEN (PeakForceLeft > #Limit3 OR PeakForceRight>#Limit3) THEN TrainPassageInformationID ELSE NULL END)
AS 'n>350'
, COUNT(CASE
WHEN (PeakForceLeft > #Limit4 OR PeakForceRight>#Limit4) THEN TrainPassageInformationID ELSE NULL END)
AS 'n>400'
, COUNT(CASE
WHEN (PeakForceLeft > #Limit5 OR PeakForceRight>#Limit5) THEN TrainPassageInformationID ELSE NULL END)
AS 'n>500'
From WheelDamage
where (TimeOfAxle > #StartDate AND TimeOfAxle < #EndDate) and DetectorID in (11,12)
GROUP BY DetectorID
order by DetectorID asc
SELECT DetectorID,
AVG(PeakForceLeft) AS 'NoiseLeft' FROM WheelDamage as t
where PeakForceLeft in (
select top 10 percent PeakForceLeft
from wheeldamage as tt
where tt.WheelDamageID = t.WheelDamageID AND tt.PeakForceLeft <> 0 AND tt.PeakForceLeft IS NOT NULL AND (t.TimeOfAxle > #StartDate AND t.TimeOfAxle < #EndDate)
order by tt.PeakForceLeft asc)
Group By DetectorID
order by DetectorID asc
SELECT DetectorID,
AVG(PeakForceRight) AS 'NoiseRight' FROM WheelDamage as t
where PeakForceLeft in (
select top 10 percent PeakForceRight
from wheeldamage as tt
where tt.WheelDamageID = t.WheelDamageID AND tt.PeakForceRight <> 0 AND tt.PeakForceLeft IS NOT NULL AND (t.TimeOfAxle > #StartDate AND t.TimeOfAxle < #EndDate)
order by tt.PeakForceLeft asc)
Group By DetectorID
order by DetectorID asc
SELECT DetectorId, COUNT (DateTime) AS 'Tags1'
FROM [TagPassage]
WHERE Valid = 1 AND (DateTime > #StartDate AND DateTime < #EndDate)
GROUP by DetectorID
Order by DetectorID asc
SELECT DetectorID, COUNT(CASE
WHEN (TotalWeight=0 OR TotalWeight IS NULL) AND (HasTrainStandStill<>1 OR HasTrainStandStill IS NULL) THEN (TrainPassageInformationID) ELSE NULL END) AS 'notAnalyzed'
, SUM (TotalWeight) AS 'TotalWeight'
FROM TrainPassageInformation
WHERE (Datetime > #StartDate AND Datetime < #EndDate)
Group By DetectorID
order by DetectorID asc
This is returing 5 new rows which I understand. This is because i have 5 select statements. However, I need to know how i can put the bottom 4 select statements in the top one so that it will only return 1 row.
I have tried with Select(Select X xxx). But that gave the error that it is returning more then 1 rows.
The image above is showing the current result. I want the bottom 4 tables to be columns in the first table.
can you just join/left join all the scripts together as table and link by DetectorID, then u can select the columns from the 4 scripts at the bottom to the right.

Query with Join of tables is taking long time to execute 5 min

SELECT
B.AccountBranchID
,B.VoucherNo
,B.BranchName AS BranchName
,B.InvoiceNo
,CONVERT(VARCHAR, B.InvoiceDate, 103) AS InvoiceDate
,CONVERT(VARCHAR, B.VoucherDate, 103) AS VoucherDate
,B.CustomerName
,B.RefID
,LN.AccountName AS LedgerName
,b.SalesPersonName AS SalesPersonName
,LN.LedgerCode
,B.AgentName
,B.ShipperName
,B.Segment
,B.TransactionType
,B.JobNo
,CONVERT(VARCHAR, B.JOBDate, 103) AS JOBDate
,B.MAWBNo
,B.HAWBNo
,B.AccountName
,B.LedgerCode AS AccountLedgerCode
,B.CurrencyCode
,ISNULL(B.Amount, 0) AS Amount
,B.ChargeExRate
,(CASE B.CRDR
WHEN 'CR' THEN (B.ChargeBaseAmount * -1)
ELSE B.ChargeBaseAmount
END) AS ChargeBaseAmount
,(CASE B.CRDR
WHEN 'CR' THEN 'Credit'
ELSE 'Debit'
END) AS CRDR
FROM VW_VoucherTR AS B
INNER JOIN VW_VoucherTR AS LN
ON B.VoucherID = LN.VoucherID
WHERE B.CompanyID = #CompanyID
AND (CASE #Type
WHEN 'I' THEN B.InvoiceDate
ELSE B.VoucherDate
END) BETWEEN ISNULL(#FromDate, (SELECT
FYearStart
FROM Secmst_FinancialYear
WHERE FyearId = #yearID)
) AND ISNULL(#ToDate, GETDATE())
AND (#Segment IS NULL
OR B.Segment = #Segment)
AND (#BranchMappingID IS NULL
OR B.BranchMappingID = #BranchMappingID)
AND B.VoucherTypeCode IN ('sv')
AND B.IsDeleted = 0
AND (B.GroupName <> 'Sundry Creditors'
AND B.GroupName <> 'Sundry Debtors')
AND LN.GroupName IN ('Sundry Debtors', 'Sundry Creditors')
The subquery in the BETWEEN is probably what is killing you. Have you looked at the execution plan?
BETWEEN ISNULL(#FromDate, (
SELECT FYearStart
FROM Secmst_FinancialYear
WHERE FyearId = #yearID
))
AND ISNULL(#ToDate, GETDATE())
What's happening is you are running that query across every row, and by my looks, that's unnecessary because you are only needing static dates there (not anything based on the joined rows.)
Try this:
DECLARE #FromDateActual datetime = ISNULL(#FromDate, (
SELECT FYearStart
FROM Secmst_FinancialYear
WHERE FyearId = #yearID
));
DECLARE #ToDateActual datetime = ISNULL(#ToDate, GETDATE());
SELECT B.AccountBranchID
,B.VoucherNo
,B.BranchName AS BranchName
,B.InvoiceNo
,convert(VARCHAR, B.InvoiceDate, 103) AS InvoiceDate
,convert(VARCHAR, B.VoucherDate, 103) AS VoucherDate
,B.CustomerName
,B.RefID
,LN.AccountName AS LedgerName
,b.SalesPersonName AS SalesPersonName
,LN.LedgerCode
,B.AgentName
,B.ShipperName
,B.Segment
,B.TransactionType
,B.JobNo
,convert(VARCHAR, B.JOBDate, 103) AS JOBDate
,B.MAWBNo
,B.HAWBNo
,B.AccountName
,B.LedgerCode AS AccountLedgerCode
,B.CurrencyCode
,ISNULL(B.Amount, 0) AS Amount
,B.ChargeExRate
,(
CASE B.CRDR
WHEN 'CR'
THEN (B.ChargeBaseAmount * - 1)
ELSE B.ChargeBaseAmount
END
) AS ChargeBaseAmount
,(
CASE B.CRDR
WHEN 'CR'
THEN 'Credit'
ELSE 'Debit'
END
) AS CRDR
FROM VW_VoucherTR AS B
INNER JOIN VW_VoucherTR AS LN ON B.VoucherID = LN.VoucherID
WHERE B.CompanyID = #CompanyID
AND (
CASE #Type
WHEN 'I'
THEN B.InvoiceDate
ELSE B.VoucherDate
END
) BETWEEN #FromDateActual
AND #ToDateActual
AND (
#Segment IS NULL
OR B.Segment = #Segment
)
AND (
#BranchMappingID IS NULL
OR B.BranchMappingID = #BranchMappingID
)
AND B.VoucherTypeCode IN ('sv')
AND B.IsDeleted = 0
AND (
B.GroupName <> 'Sundry Creditors'
AND B.GroupName <> 'Sundry Debtors'
)
AND LN.GroupName IN (
'Sundry Debtors'
,'Sundry Creditors'
)
Beyond that you could consider adding non-clustered indexes. The Query Analyzer may even suggest a couple. But you'll want to be careful there, depending on how the data is used and loaded, as too many indexes or large ones can lead to further performance issues in other places (row insertions, page fragmentation, etc).
There could be many reasons for it, but one thing is quite plain. The following part is not sargable.
(CASE #Type
WHEN 'I' THEN B.InvoiceDate
ELSE B.VoucherDate
END) BETWEEN ISNULL(#FromDate, (SELECT
FYearStart
FROM Secmst_FinancialYear
WHERE FyearId = #yearID)
) AND ISNULL(#ToDate, GETDATE())
Should be rewritten to be sargable, so that indexes can be used.
SELECT #FromDate = ISNULL(#FromDate, (SELECT
TOP 1 FYearStart
FROM Secmst_FinancialYear
WHERE FyearId = #yearID)) )
SELECT #ToDate = ISNULL(#ToDate, GETDATE())
SELECT
...
WHERE
...
AND
((#Type='I' AND B.InvoiceDate BETWEEN #FromDate AND #ToDate)
OR
(#Type<>'I' AND B.VoucherDate BETWEEN #FromDate AND #ToDate))
AND
...
Of course proper indexing is the way how to speed up your query, if no indexes are on InvoiceDate, VoucherDate, etc. then your query will use full table scan instead of index seek and it will be slow.

Update multiple variables in a single query

I have a table with Sum of Amounts for Each Weekday (Sunday to Saturday) . Table structure is as below.
I need to assign these table values into parameters. For E.g : I need to assign sum of rdate '2015-11-15' i.e 324 to a variable #sundayval , 374 to variable #mondayval etc...
How to do this In a single update query. I have tried out with Case statement,
But it only assigns value to variable #saturdayval .
Thanks for the Help.
This does the job. It doesn't depend on any particulat DATEFIRST settings - it instead uses an arbitrarily chosen sunday (I picked 17th May this year) (what I usually refer to as a "known good" date because it has the property we're looking for, in this case the right day of the week):
declare #t table ([sum] int not null,rdate datetime2 not null)
insert into #t([sum],rdate) values
(324,'20151115'),
(374,'20151116'),
(424,'20151117'),
(474,'20151118'),
(524,'20151119'),
(574,'20151120'),
(624,'20151121')
declare #sundayval int
declare #mondayval int
declare #tuesdayval int
declare #wednesdayval int
declare #thursdayval int
declare #fridayval int
declare #saturdayval int
select
#sundayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150517') THEN [sum] END),
#mondayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150518') THEN [sum] END),
#tuesdayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150519') THEN [sum] END),
#wednesdayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150520') THEN [sum] END),
#thursdayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150521') THEN [sum] END),
#fridayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150522') THEN [sum] END),
#saturdayval = SUM(CASE WHEN DATEPART(weekday,rdate) = DATEPART(weekday,'20150523') THEN [sum] END)
from #t
select #sundayval,#mondayval,#tuesdayval,#wednesdayval,#thursdayval,#fridayval,#saturdayval
Result:
----------- ----------- ----------- ----------- ----------- ----------- -----------
324 374 424 474 524 574 624
Ok - let's do it this way: the else case is set to return itself, so each variable essentially aggregates as a coalesce. Note: I don't have any way to test this right now. :)
SELECT
#sundayval = case when DATEPART(weekday, rdate) = 1 then sum else #sundayval end
, #mondayval = case when DATEPART(weekday, rdate) = 2 then sum else #mondayval end
, #tuesdayval = case when DATEPART(weekday, rdate) = 3 then sum else #tuesdayval end
, #wednesdayval = case when DATEPART(weekday, rdate) = 4 then sum else #wednesdayval end
, #thursdayval = case when DATEPART(weekday, rdate) = 5 then sum else #thursdayval end
, #fridayval = case when DATEPART(weekday, rdate) = 6 then sum else #fridayval end
, #saturdayval = case when DATEPART(weekday, rdate) = 7 then sum else #saturdayval end
FROM TABLE
I have No idea Whether a Single UPDATE Statement will do this. I have assigned value for Each Variable like as Below,
SELECT #SunTotal = [sum]
FROM [table]
where rdate = #Startdate
SELECT #MonTotal = [sum]
FROM [table]
where rdate = DATEADD(DAY,1,#Startdate)
SO ON...

Check result of transaction before updating table

I have the following stored procedure:
CREATE PROCEDURE [dbo].[vRpt_VolunteerPaymentsAll]
#startdate DATETIME = NULL ,
#enddate DATETIME = NULL ,
#user NVARCHAR(50)
AS /* This procedure generates Team Events that have been audtied */
DECLARE #sd DATETIME
DECLARE #ed DATETIME
/* Ensure that the start and end dates covert whole days */
SET #sd = CONVERT(VARCHAR(10), #startdate, 120) + ' 00:00:00'
SET #ed = CONVERT(VARCHAR(10), #enddate, 120) + ' 23:59:59'
DECLARE #id INT
INSERT INTO vVolunteerPaymentEvents
( StartDate ,
EndDate ,
Verifier ,
DatePaid ,
RecordsPaid ,
UnDone ,
Comments ,
Flags
)
VALUES ( #startdate ,
#enddate ,
/*Get the users initials*/
( SELECT dbo.vUsers.Initials
FROM dbo.vUsers
WHERE dbo.vUsers.UserID = #user
) ,
GETDATE() ,
/* Count how many records are going ot be affected*/
( SELECT COUNT(*)
FROM dbo.vVolunteerPayments
WHERE dbo.vVolunteerPayments.PaymentEventID = 0
AND ( dbo.vVolunteerPayments.DateCreated BETWEEN #sd
AND
#ed )
) ,
0 ,
'' ,
0
)
/*Set the id of the payment event id for the volunteer payments*/
SET #id = SCOPE_IDENTITY() ;
--get the totals for the payment based on the
--#id value just inserted above
UPDATE dbo.vVolunteerPayments
SET dbo.vVolunteerPayments.PaymentEventID = #id ,
dbo.vVolunteerPayments.DatePaid = GETDATE()
WHERE dbo.vVolunteerPayments.PaymentEventID = 0
AND dbo.vVolunteerPayments.DateCreated BETWEEN #sd
AND #ed
SELECT * FROM
(SELECT VOLSACCT.volunteerid ,
ISNULL(VOLS.Forename, '') + ' ' + ISNULL(VOLS.Surname, '') AS Name ,
VOLSACCT.SortCode ,
VOLSACCT.AccountName ,
VOLSACCT.AccountNumber ,
SUM(CASE [Type]
WHEN 1001
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS Reimbursements ,
SUM(CASE [Type]
WHEN 1002
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS MobilePhoneCharges ,
SUM(CASE [Type]
WHEN 1003
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS GPContributions ,
SUM(CASE [Type]
WHEN 12
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS GPExpenses ,
SUM(CASE [Type]
WHEN 137
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS Expenses ,
SUM(CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Mileage
ELSE 0
END) AS TotalMileageWeek ,
VOLSACCT.BACS,
PMTS.ChargeRate
FROM dbo.vVolunteerPayments PMTS
INNER JOIN dbo.vVolunteerAccounts VOLSACCT ON PMTS.VolunteerId = VOLSACCT.VolunteerID
INNER JOIN dbo.vVolunteers VOLS ON PMTS.VolunteerId = VOLS.VolunteerID
WHERE PMTS.PaymentEventID = #id
GROUP BY VOLSACCT.volunteerid ,
VOLSACCT.AccountName ,
VOLSACCT.AccountNumber ,
VOLSACCT.BACS ,
VOLSACCT.SortCode ,
VOLS.Forename ,
VOLS.Surname,
PMTS.ChargeRate) AS A
WHERE
GPContributions >0
OR GPExpenses > 0
OR MobilePhoneCharges > 0
OR Expenses > 0
OR Reimbursements > 0
OR Expenses > 0
OR TotalMileageWeek >0
ORDER BY Name
It basically inserts a summary record of affected transactions into one table (volunteerpaymentevent) , updates existing records of another table (volunteerpayments(datepaid and payment event id)) and then returns record sets of sumary values within a join
However I have found that if the join fails or there are no joins at all (and consequently nothing outputs) then the update part of the procedure still updates the records
Is there a way of checking the result of the inner join before updating the records in the volunteerpayments table
Cheers
You need to put your code inside a transaction:
ALTER PROCEDURE [dbo].[vRpt_VolunteerPaymentsAll]
#startdate DATETIME = NULL ,
#enddate DATETIME = NULL ,
#user NVARCHAR(50)
AS
begin transaction
your procedure code
commit transaction
See MSDN for more on transactions.

Select date + 3 days, not including weekends and holidays

I've found a number of answers to the problem of doing a date-diff, in SQL, not including weekends and holidays. My problem is that I need to do a date comparison - how many child records are there whose work date is within three days of the parent record's send date?
Most of the date-diff answers involve a calendar table, and I think if I can build a sub-select that returns the date+3, I can work out the rest. But I can't figure out how to return a date+3.
So:
CREATE TABLE calendar
(
thedate DATETIME NOT NULL,
isweekday SMALLINT NULL,
isholiday SMALLINT NULL
);
And:
SELECT thedate AS fromdate, xxx AS todate
FROM calendar
What I want is for todate to be fromdate + 72 hours, not counting weekends and holidays. Doing a COUNT(*) where isweekday and not isholiday is simple enough, but doing a DATEADD() is another matter.
I'm not sure where to start.
EDIT:
Changed to include non-workdays as valid fromDates.
WITH rankedDates AS
(
SELECT
thedate
, ROW_NUMBER()
OVER(
ORDER BY thedate
) dateRank
FROM
calendar c
WHERE
c.isweekday = 1
AND
c.isholiday = 0
)
SELECT
c1.fromdate
, rd2.thedate todate
FROM
(
SELECT
c.thedate fromDate
,
(
SELECT
TOP 1 daterank
FROM
rankedDates rd
WHERE
rd.thedate <= c.thedate
ORDER BY
thedate DESC
) dateRank
FROM
calendar c
) c1
LEFT JOIN
rankedDates rd2
ON
c1.dateRank + 3 = rd2.dateRank
You could put a date rank column on the calendar table to simplify this and avoid the CTE:
CREATE TABLE
calendar
(
TheDate DATETIME PRIMARY KEY
, isweekday BIT NOT NULL
, isHoliday BIT NOT NULL DEFAULT 0
, dateRank INT NOT NULL
);
Then you'd set the daterank column only where it's a non-holiday weekday.
This should do the trick, change the number in the "top" to the number of days you want to include.
declare #date as datetime
set #date = '5/23/13'
select
max(_businessDates.thedate)
from (
select
top 3 _Calendar.thedate
from calendar _Calendar
where _Calendar.isWeekday = 1
and _Calendar.isholiday = 0
and _Calendar.thedate >= #date
order by
_Calendar.thedate
) as _businessDates
For a dynamic version that can go forward or backward a certain number of days try this:
declare #date as datetime
declare #DayOffset as int
set #date = '5/28/13'
set #DayOffset = -3
select
(case when #DayOffset >= 0 then
max(_businessDates.thedate)
else
min(_businessDates.thedate)
end)
from (
select
top (abs(#DayOffset) + (case when #DayOffset >= 0 then 1 else 0 end)) _Calendar.thedate
from calendar _Calendar
where _Calendar.isWeekday = 1
and _Calendar.isholiday = 0
and ( (#DayOffset >= 0 and _Calendar.thedate >= #date)
or (#DayOffset < 0 and _Calendar.thedate < #date) )
order by
cast(_Calendar.thedate as int) * (case when #DayOffset >=0 then 1 else -1 end)
) as _businessDates
You can set #DayOffset to a positive or negative number.
You just need DATEADD, unless I'm not understanding your question.
DATEADD(DAY,3,fromdate)
Edit: I see, not counting weekends or Holidays, will update momentarily.
Update: Well looks like Jason nailed it, but on the off chance you're using SQL2012, here's the simple version:
SELECT todate = thedate
fromdate = LEAD(thedate,3) OVER (ORDER BY thedate)
FROM calendar
WHERE isweekday = 1
AND isHoliday = 0
Try this if you need it as a query with dateAdd:
SELECT
allDates.thedate fromDate
,min(nonWeekendHoliday.thedate) toDate
FROM (
SELECT
thedate
FROM
calendar _calendar
) allDates
LEFT JOIN (
SELECT
thedate
FROM
calendar _calendar
WHERE
_calendar.isweekday = 1
AND
_calendar.isholiday = 0
) nonWeekendHoliday
on dateadd(d,3,allDates.thedate) <= nonWeekendHoliday.thedate
where allDates.thedate between '5/20/13' and '5/31/13'
group by
allDates.thedate