Check value for every month in a year in T-sql using cursor - tsql

I need a example of a cursor for my meter system, where the system reads the meter every month.
The cursor needs to check, that every meter has a reading registered in the current year. For meters with missing readings, an estimated value is added, such that the daily consumption is like the daily comsumption in the previous period plus 15%. In no previous period exiss, the above Kwh value is used.

How about something like this. (The MonthSeed table could become a real table in your database)
declare #MonthSeed table (MonthNumber int)
insert into #MonthSeed values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
-- assumes declared table "Reading" with fields ( Id int, [Date] datetime, MeterNo varchar(50), Consumption int )
select
m.MeterNo,
r.Date,
calculatedConsumption = isnull(r.Consumption, -- read consumption
isnull((select max(r2.Consumption) Consumption from Reading r2 where datepart(month, r2.Date) = (m.MonthNumber - 1) and r2.MeterNo = m.MeterNo) * 1.15, -- previous consumption + 15%
9999)) -- default consumption
from
(select distinct
MeterNo,
MonthNumber
from
Reading, #MonthSeed) m
left join
Reading r on r.MeterNo = m.MeterNo and datepart(month, r.Date) = m.monthNumber
EDIT FOLLOWING COMMENTS - EXAMPLE OF ADDING MISSING READINGS
As commented need to include an insert before the select insert into Reading (MeterNo, Date, Consumption) and making use of the left join to the reading table include a check for the reading id to be null ie missing where r.Id is null.
I noticed that this would result in null date entries when inserting into the reading table. So I included a date aggregate in the main sub-select Date = dateadd(month, monthnumber, #seeddate); the main select was amended to show a date for missing entries isnull(r.Date, m.Date),
I've calculated the #SeedDate to be the 1st of the current month one year ago but you may want to pass in an earlier date.
declare #MonthSeed table (MonthNumber int)
insert into #MonthSeed values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
-- assumes declared table "Reading" with fields ( Id int, [Date] datetime, MeterNo varchar(50), Consumption int )
declare #SeedDate datetime = (select dateadd(month, datediff(month, 0, getdate())-12, 0)) -- this month, last year
insert into Reading (MeterNo, Date, Consumption)
select
m.MeterNo,
isnull(r.Date, m.Date),
calculatedConsumption =
isnull(r.Consumption, -- read consumption
isnull(1.15 * (select max(r2.Consumption) Consumption
from Reading r2
where datepart(month, r2.Date) = (m.MonthNumber - 1)
and r2.MeterNo = m.MeterNo), -- previous consumption + 15%
9999)) -- default consumption
from
(select distinct
MeterNo,
MonthNumber,
Date = dateadd(month, monthnumber, #seeddate)
from
Reading
cross join
#MonthSeed) m
left join
Reading r on r.MeterNo = m.MeterNo and datepart(month, r.Date) = m.monthNumber
where
r.Id is null
select * from Reading

(The following assumes SQL Server 2005 or later.)
Scrounge around in here and see if there's anything of value:
declare #StartDate as Date = '2012-01-01'
declare #Now as Date = GetDate()
declare #DefaultConsumption as Int = 2000 -- KWh.
declare #MeterReadings as Table
( MeterReadingId Int Identity, ReadingDate Date, MeterNumber VarChar(10), Consumption Int )
insert into #MeterReadings ( ReadingDate, MeterNumber, Consumption ) values
( '2012-01-13', 'E154', 2710 ),
( '2012-01-19', 'BR549', 650 ),
( '2012-02-15', 'E154', 2970 ),
( '2012-02-19', 'BR549', 618 ),
( '2012-03-16', 'BR549', 758 ),
( '2012-04-11', 'E154', 2633 ),
( '2012-04-20', 'BR549', 691 )
; with Months ( Month ) as (
select #StartDate as [Month]
union all
select DateAdd( mm, 1, Month )
from Months
where Month < #Now
),
MeterNumbers ( MeterNumber ) as (
select distinct MeterNumber
from #MeterReadings )
select M.Month, MN.MeterNumber,
MR.MeterReadingId, MR.ReadingDate, MR.Consumption,
Coalesce( MR.Consumption, #DefaultConsumption ) as [BillableConsumption],
( select Max( ReadingDate ) from #MeterReadings where MeterNumber = MN.MeterNumber and ReadingDate < M.Month ) as [PriorReadingDate],
( select Consumption from #MeterReadings where MeterNumber = MN.MeterNumber and ReadingDate =
( select Max( ReadingDate ) from #MeterReadings where MeterNumber = MN.MeterNumber and ReadingDate < M.Month ) ) as [PriorConsumption],
( select Consumption from #MeterReadings where MeterNumber = MN.MeterNumber and ReadingDate =
( select Max( ReadingDate ) from #MeterReadings where MeterNumber = MN.MeterNumber and ReadingDate < M.Month ) ) * 1.15 as [PriorConsumptionPlus15Percent]
from Months as M cross join
MeterNumbers as MN left outer join
#MeterReadings as MR on MR.MeterNumber = MN.MeterNumber and DateAdd( dd, 1 - DatePart( dd, MR.ReadingDate ), MR.ReadingDate ) = M.Month
order by M.Month, MN.MeterNumber

Related

SQL Server - Select with Group By together Raw_Number

I'm using SQL Server 2000 (80). So, it's not possible to use the LAG function.
I have a code a data set with four columns:
Purchase_Date
Facility_no
Seller_id
Sale_id
I need to identify missing Sale_ids. So every sale_id is a 100% sequential, so the should not be any gaps in order.
This code works for a specific date and store if specified. But i need to work on entire data set looping looping through every facility_id and every seller_id for ever purchase_date
declare #MAXCOUNT int
set #MAXCOUNT =
(
select MAX(Sale_Id)
from #table
where
Facility_no in (124) and
Purchase_date = '2/7/2020'
and Seller_id = 1
)
;WITH TRX_COUNT AS
(
SELECT 1 AS Number
union all
select Number + 1 from TRX_COUNT
where Number < #MAXCOUNT
)
select * from TRX_COUNT
where
Number NOT IN
(
select Sale_Id
from #table
where
Facility_no in (124)
and Purchase_Date = '2/7/2020'
and seller_id = 1
)
order by Number
OPTION (maxrecursion 0)
My Dataset
This column:
case when
Sale_Id=0 or 1=Sale_Id-LAG(Sale_Id) over (partition by Facility_no, Purchase_Date, Seller_id)
then 'OK' else 'Previous Missing' end
will tell you which Seller_Ids have some sale missing. If you want to go a step further and have exactly your desired output, then filter out and distinct the 'Previous Missing' ones, and join with a tally table on not exists.
Edit: OP mentions in comments they can't use LAG(). My suggestion, then, would be:
Make a temp table that that has the max(sale_id) group by facility/seller_id
Then you can get your missing results by this pseudocode query:
Select ...
from temptable t
inner join tally N on t.maxsale <=N.num
where not exists( select ... from sourcetable s where s.facility=t.facility and s.seller=t.seller and s.sale=N.num)
> because the only way to "construct" nonexisting combinations is to construct them all and just remove the existing ones.
This one worked out
; WITH cte_Rn AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY Facility_no, Purchase_Date, Seller_id ORDER BY Purchase_Date) AS [Rn_Num]
FROM (
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id
FROM MyTable WITH (NOLOCK)
) a
)
, cte_Rn_0 as (
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id,
-- [Rn_Num] AS 'Skipped Sale'
-- , case when Sale_id = 0 Then [Rn_Num] - 1 Else [Rn_Num] End AS 'Skipped Sale for 0'
, [Rn_Num] - 1 AS 'Skipped Sale for 0'
FROM cte_Rn a
)
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id,
-- [Skipped Sale],
[Skipped Sale for 0]
FROM cte_Rn_0 a
WHERE NOT EXISTS
(
select * from cte_Rn_0 b
where b.Sale_id = a.[Skipped Sale for 0]
and a.Facility_no = b.Facility_no
and a.Purchase_Date = b.Purchase_Date
and a.Seller_id = b.Seller_id
)
--ORDER BY Purchase_Date ASC

Why using same field when filtering cause different execution time? (different index usage)

When I run query and filter by agreement_id it is slow,
but when I filter by an alias id it is fast. (Look at the end of the query)
Why using same field when filtering cause different execution time?
Links to explain analyze:
slow1, slow2
fast1, fast2
Difference start at #20: Where different indexes are used:
Index Cond: (o.sys_period #> sys_time()) VS Index Cond: (o.agreement_id = 38)
PS. It would be nice if I can contact to developer of this feature (I have one more similar problem)
UPD I did some experiments. when I remove window functions from my query it works fast in any case. So why window function stop index usage in some cases? How to escape/workaround that?
dbfiddle with minimal test case
Server version is v13.1
Full query:
WITH gconf AS
-- https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-SELECT
NOT MATERIALIZED -- force it to be merged into the parent query
-- it gives a net savings because each usage of the WITH query needs only a small part of the WITH query's full output.
( SELECT
ocd.*,
tstzrange( '2021-05-01', '2021-05-01', '[]') AS acc_period,
(o).agreement_id AS id, -- Required to passthrough WINDOW FUNCTION
(o).id AS order_id,
(ic).consumed_period AS consumed_period,
dense_rank() OVER ( PARTITION BY (o).agreement_id, (o).id ORDER BY (ic).consumed_period ) AS nconf,
row_number() OVER ( wconf ORDER BY (c).sort_order NULLS LAST ) AS nitem,
(sum( ocd.item_cost ) OVER wconf)::numeric( 10, 2) AS conf_cost,
max((ocd.ic).consumed) OVER wconf AS consumed,
CASE WHEN true
THEN (sum( ocd.item_suma ) OVER wconf)::numeric( 10, 2 )
ELSE (sum( ocd.item_cost ) OVER wconf)::numeric( 10, 2 )
END AS conf_suma
FROM order_cost_details( tstzrange( '2021-05-01', '2021-05-01', '[]') ) ocd
WHERE true OR (ocd.ic).consumed_period #> lower( tstzrange( '2021-05-01', '2021-05-01', '[]') )
WINDOW wconf AS ( PARTITION BY (o).agreement_id, (o).id, (ic).consumed_period )
),
gorder AS (
SELECT *,
(conf_suma/6)::numeric( 10, 2 ) as conf_nds,
sum( conf_suma ) FILTER (WHERE nitem = 1) OVER worder AS order_suma
FROM gconf
WINDOW worder AS ( PARTITION BY gconf.id, (o).id )
-- TODO: Ask PG developers: Why changing to (o).agreement_id slows down query?
-- WINDOW worder AS ( PARTITION BY (o).agreement_id, (o).id )
)
SELECT
u.id, consumed_period, nconf, nitem,
(c).id as item_id,
COALESCE( (c).sort_order, pd.sort_order ) as item_order,
COALESCE( st.display, st.name, rt.display, rt.name ) as item_name,
COALESCE( item_qty, (c).amount/rt.unit ) as item_qty,
COALESCE( (p).label, rt.label ) as measure,
item_price, item_cost, item_suma,
conf_cost, consumed, conf_suma, conf_nds, order_suma,
(order_suma/6)::numeric( 10, 2 ) as order_nds,
sum( conf_suma ) FILTER (WHERE nitem = 1 ) OVER wagreement AS total_suma,
sum( (order_suma/6)::numeric( 10, 2 ) ) FILTER (WHERE nitem = 1 AND nconf = 1) OVER wagreement AS total_nds,
pkg.id as package_id,
pkg.link_1c_id as package_1c_id,
COALESCE( pkg.display, pkg.name ) as package,
acc_period
FROM gorder u
LEFT JOIN resource_type rt ON rt.id = (c).resource_type_id
LEFT JOIN service_type st ON st.id = (c).service_type_id
LEFT JOIN package pkg ON pkg.id = (o).package_id
LEFT JOIN package_detail pd ON pd.package_id = (o).package_id
AND pd.resource_type_id IS NOT DISTINCT FROM (c).resource_type_id
AND pd.service_type_id IS NOT DISTINCT FROM (c).service_type_id
-- WHERE (o).agreement_id = 38 -- slow
WHERE u.id = 38 -- fast
WINDOW wagreement AS ( PARTITION BY (o).agreement_id )
As problem workaround we can additionally SELECT an alias for column used at PARTITION BY expression. Then PG apply optimization and use index.
The answer to the question could be: PG does not apply optimization if composite type is used. Notice as it works:
PARTITION | FILTER | IS USED?
------------------------------
ALIAS | ORIG | NO
ALIAS | ALIAS | YES
ORIG | ALIAS | NO
ORIG | ORIG | NO
See this dbfiddle
create table agreement ( ag_id int, name text, cost numeric(10,2) );
create index ag_idx on agreement (ag_id);
insert into agreement (ag_id, name, cost) values ( 1, '333', 22 ),
(1,'333', 33), (1, '333', 7), (2, '555', 18 ), (2, '555', 2), (3, '777', 4);
select * from agreement;
create function initial ()
returns table( agreement_id int, ag agreement ) language sql stable AS $$
select ag_id, t from agreement t;
$$;
select * from initial() t;
explain( analyze, costs, buffers, verbose ) with totals_by_ag as (
select
*,
sum( (t.ag).cost ) over ( partition by agreement_id ) as total
from initial() t
)
select * from totals_by_ag t
where (t.ag).ag_id = 1; -- index is NOT USED
explain( analyze, costs, buffers, verbose ) with totals_by_ag as (
select
*,
sum( (t.ag).cost ) over ( partition by agreement_id ) as total
from initial() t
)
select * from totals_by_ag t
where agreement_id = 1; -- index is used when alias for column is used
explain( analyze, costs, buffers, verbose ) with totals_by_ag as (
select
*,
sum( (t.ag).cost ) over ( partition by (t.ag).ag_id ) as total --renamed
from initial() t
)
select * from totals_by_ag t
where agreement_id = 1; -- index is NOT USED because grouping by original column
explain( analyze, costs, buffers, verbose ) with totals_by_ag as (
select
*,
sum( (t.ag).cost ) over ( partition by (t.ag).ag_id ) as total --renamed
from initial() t
)
select * from totals_by_ag t
where (t.ag).ag_id = 1; -- index is NOT USED even if at both cases original column

Can't get missing days to show zero

Here is what I've written - I'm trying to get the dates without transactions to just show zero for the field. I've gone to google and talked to everyone I know, this should work - but isn't :?
IF OBJECT_ID('tempdb..#D') IS NOT NULL
DROP TABLE #D;
CREATE TABLE #D
(
[Date] [DATE] NOT NULL,
PRIMARY KEY CLUSTERED ([Date] ASC)
);
DECLARE #CurrentDate DATE = GETDATE() - 95;
DECLARE #EndDate DATE = GETDATE();
WHILE #CurrentDate < #EndDate
BEGIN
INSERT INTO #D
(
[Date]
)
SELECT DATE = #CurrentDate;
SET #CurrentDate = DATEADD(DD, 1, #CurrentDate);
END;
--SELECT * FROM #D ORDER BY Date DESC
SELECT ITEM2_NUM AS 'Part Number',
COALESCE(SUM(TRANS_QTY), 0) 'Daily Usage',
D.Date AS 'Usage Date'
FROM #D AS D
LEFT OUTER JOIN dbo.W_INVENTORY_TRANS_F IT
ON IT.GL_DT = D.Date
LEFT JOIN dbo.W_BU_ITEM_D BI
ON BI.BUSINESS_UNIT_WID = IT.BUSINESS_UNIT_WID
AND BI.ITEM_WID = IT.ITEM_WID
WHERE DOCUMENT_TYPE_WID = 22
AND D.Date >= DATEADD(yy, -1, GETDATE())
AND IT.BUSINESS_UNIT_WID = '837'
AND IT.ITEM2_NUM = '10111'
AND BI.STOCKING_TYPE = 'P'
GROUP BY ITEM2_NUM,
D.Date
ORDER BY D.Date DESC;
Below is the results of what I get:
Part Number Daily Usage Usage Date
10111 -331.0000 2019-08-19
10111 -2617.0000 2019-08-16
10111 -418.0000 2019-08-15
10111 -471.0000 2019-08-14
10111 -1158.0000 2019-08-13
10111 -766.0000 2019-08-12
10111 -1385.0000 2019-08-09
This is what I want:
Part Number Daily Usage Usage Date
10111 -331 8/19/2019
10111 0 8/18/2019
10111 0 8/17/2019
10111 -2617 8/16/2019
10111 -418 8/15/2019
10111 -471 8/14/2019
10111 -1158 8/13/2019
10111 -766 8/12/2019
10111 0 8/11/2019
10111 0 8/10/2019
10111 -1385 8/9/2019
Your where clause is effectively changing your left join to an inner join.
That is because a left join returns nulls for rows that can't be found on the right table, and null compared to anything will always return unknown which is interpreted by the SQL Server as false.
What you need to do is move all the conditions that's referencing the right side tables into the on clause.
Also, you are grouping by ITEM2_NUM which doesn't make sense because it's already filtered out in the where clause.
Here's what should be a working version of your code (except the DOCUMENT_TYPE_WID column which I don't know what table it belongs to):
SELECT '10111' AS 'Part Number',
COALESCE(SUM(TRANS_QTY), 0) 'Daily Usage',
D.Date AS 'Usage Date'
FROM #D AS D
LEFT OUTER JOIN dbo.W_INVENTORY_TRANS_F IT
ON IT.GL_DT = D.Date
AND IT.BUSINESS_UNIT_WID = '837'
AND IT.ITEM2_NUM = '10111'
-- I had to guess where this column belongs, it might be on the BI table
AND IT.DOCUMENT_TYPE_WID = 22
LEFT JOIN dbo.W_BU_ITEM_D BI
ON BI.BUSINESS_UNIT_WID = IT.BUSINESS_UNIT_WID
AND BI.ITEM_WID = IT.ITEM_WID
AND BI.STOCKING_TYPE = 'P'
WHERE D.Date >= DATEADD(yy, -1, GETDATE())
GROUP BY D.Date
ORDER BY D.Date DESC;
Zohar was 100% correct - for those that end up here at some point below if the final code I ended up using.
IF OBJECT_ID('tempdb..#D') IS NOT NULL
DROP TABLE #D;
CREATE TABLE #D
(
[Date] [DATE] NOT NULL,
PRIMARY KEY CLUSTERED ([Date] ASC)
);
DECLARE #CurrentDate DATE = GETDATE() - 95;
DECLARE #EndDate DATE = GETDATE();
WHILE #CurrentDate < #EndDate
BEGIN
INSERT INTO #D
(
[Date]
)
SELECT DATE = #CurrentDate;
SET #CurrentDate = DATEADD(DD, 1, #CurrentDate);
END;
IF OBJECT_ID('tempdb..#DailyUsage') IS NOT NULL
DROP TABLE #DailyUsage;
CREATE TABLE #DailyUsage
(
BU_ITEM_WID INT,
TRANS_QTY DECIMAL(15, 4),
GL_DT DATETIME
);
INSERT INTO #DailyUsage
(
BU_ITEM_WID,
TRANS_QTY,
GL_DT
)
SELECT BU_ITEM_WID,
TRANS_QTY AS 'Daily Usage',
GL_DT
FROM dbo.W_INVENTORY_TRANS_F
WHERE 1 = 1
AND DOCUMENT_TYPE_WID = 22
AND GL_DT >= GETDATE() - 95;
CREATE NONCLUSTERED INDEX [IX_DailyUsage]
ON #DailyUsage (BU_ITEM_WID)
INCLUDE
(
TRANS_QTY,
GL_DT
);
WITH cte_ItemNums
AS (SELECT DISTINCT
IT.ITEM2_NUM,
IT.BU_ITEM_WID
FROM W_INVENTORY_TRANS_F IT
LEFT JOIN dbo.W_BU_ITEM_D BI
ON BI.BUSINESS_UNIT_WID = IT.BUSINESS_UNIT_WID
AND BI.ITEM_WID = IT.ITEM_WID
AND BI.STOCKING_TYPE = 'P'
WHERE 1 = 1
AND IT.DOCUMENT_TYPE_WID = 22
AND IT.BUSINESS_UNIT_WID = '837'
)
SELECT IT1.ITEM2_NUM,
COALESCE(SUM(IT2.TRANS_QTY), 0) AS 'Daily Usage',
D.Date AS 'Usage Date'
FROM #D AS D
LEFT OUTER JOIN cte_ItemNums IT1
ON 1 = 1
LEFT OUTER JOIN #DailyUsage IT2
ON IT2.GL_DT = D.Date
AND IT1.BU_ITEM_WID = IT2.BU_ITEM_WID
GROUP BY IT1.ITEM2_NUM,
D.Date;

TSQL - Replace Cursor

I found in our database a cursor statement and I would like to replace it.
Declare #max_date datetime
Select #max_date = max(finished) From Payments
Declare #begin_date datetime = '2015-02-01'
Declare #end_of_last_month datetime
While #begin_date <= #max_date
Begin
SELECT #end_of_last_month = CAST(DATEADD(DAY, -1 , DATEFROMPARTS(YEAR(#begin_date),MONTH(#begin_date),1)) AS DATE) --AS end_of_last_month
Insert Into #table(Customer, ArticleTypeID, ArticleType, end_of_month, month, year)
Select Count(distinct (customerId)), prod.ArticleTypeID, at.ArticleType, #end_of_last_month, datepart(month, #end_of_last_month), datepart(year, #end_of_last_month)
From Customer cust
Inner join Payments pay ON pay.member_id = m.member_id
Inner Join Products prod ON prod.product_id = pay.product_id
Inner Join ArticleType at ON at.ArticleTypeID = prod.ArticleTypeID
Where #end_of_last_month between begin_date and expire_date
and completed = 1
Group by prod.ArticleTypeID, at.ArticleType
order by prod.ArticleTypeID, at.ArticleType
Set #begin_date = DATEADD(month, 1, #begin_date)
End
It groups all User per Month where the begin- and expire date in the actual Cursormonth.
Notes:
The user has different payment types, for e.g. 1 Month, 6 Month and so on.
Is it possible to rewrite the code - my problem is only the identification at the where clause (#end_of_last_month between begin_date and expire_date)
How can I handle this with joins or cte's?
What you need first, if not already is a numbers table
Using said Numbers table you can create a dynamic list of dates for "end_of_Last_Month" like so
;WITH ctexAllDates
AS (
SELECT end_of_last_month = DATEADD(DAY, -1, DATEADD(MONTH, N.N -1, #begin_date))
FROM
dbo.Numbers N
WHERE
N.N <= DATEDIFF(MONTH, #begin_date, #max_date) + 1
)
select * FROM ctexAllDates
Then combine with your query like so
;WITH ctexAllDates
AS (
SELECT end_of_last_month = DATEADD(DAY, -1, DATEADD(MONTH, N.N -1, #begin_date))
FROM
dbo.Numbers N
WHERE
N.N <= DATEDIFF(MONTH, #begin_date, #max_date) + 1
)
INSERT INTO #table
(
Customer
, ArticleTypeID
, ArticleType
, end_of_month
, month
, year
)
SELECT
COUNT(DISTINCT (customerId))
, prod.ArticleTypeID
, at.ArticleType
, A.end_of_last_month
, DATEPART(MONTH, A.end_of_last_month)
, DATEPART(YEAR, A.end_of_last_month)
FROM
Customer cust
INNER JOIN Payments pay ON pay.member_id = m.member_id
INNER JOIN Products prod ON prod.product_id = pay.product_id
INNER JOIN ArticleType at ON at.ArticleTypeID = prod.ArticleTypeID
LEFT JOIN ctexAllDates A ON A.end_of_last_month BETWEEN begin_date AND expire_date
WHERE completed = 1
GROUP BY
prod.ArticleTypeID
, at.ArticleType
, A.end_of_last_month
ORDER BY
prod.ArticleTypeID
, at.ArticleType;

How to exclude nights from a TSQL query?

I'm writing a TSQL query to find the next available datetime from a list of appointments. So far what I've managed to get working does find the gaps in a time query but I can't seem to find a great way to exclude nights (after 7pm lets say).
;WITH CTE
AS ( SELECT
ID,StartAptDate,EndAptDate,
RowNumber = ROW_NUMBER() OVER( ORDER BY StartAptDate ASC )
FROM Appointments WHERE StylistId = 1 AND StartAptDate > CAST( CONVERT( CHAR(8), GetDate(), 112) AS DATETIME)
)
SELECT FirstApptAvail = min( a.EndAptDate )
FROM CTE a
INNER JOIN CTE b
ON a.RowNumber = b.RowNumber - 1
WHERE datediff( minute, a.EndAptDate, b.StartAptDate) >= 15 AND ...
A little pseudo code for the ... would be something like this
(a.StartAptDate < GETDATE #7pm AND a.StartAptDate > GETDATE + 1 #8am)
The part I can't seem to get right is constructing the right side of each comparison. I need to exclude anything that might be returned between 7pm that night - 8am the next morning.
Thank you in advance
Thanks for the quick feedback - it looks like I was able to get the desired result using the BETWEEN statement mentioned in the comments above. I first made the startdate and enddate in question time specific (meaning the date part was 1900 / 01 / 01 so it didn't matter) This way I could use the time ONLY to compare with.
;WITH CTE
AS ( SELECT
ID,StartAptDate,EndAptDate,
RowNumber = ROW_NUMBER() OVER( ORDER BY StartAptDate ASC )
FROM Appointments WHERE StylistId = 1 AND StartAptDate > CAST( CONVERT( CHAR(8), GetDate() - 5, 112) AS DATETIME)
)
SELECT FirstApptAvail = min( a.EndAptDate )
FROM CTE a
INNER JOIN CTE b
ON a.RowNumber = b.RowNumber - 1
WHERE datediff( minute, a.EndAptDate, b.StartAptDate) >= 15 AND (CAST ( CONVERT( CHAR(8), a.StartAptDate, 108) AS DATETIME) BETWEEN '1900-01-01 07:59:59' AND '1900-01-01 18:59:59' AND CAST ( CONVERT( CHAR(8), a.EndAptDate, 108) AS DATETIME) BETWEEN '1900-01-01 07:59:59' AND '1900-01-01 18:59:59')