Showing posts with label believe. Show all posts
Showing posts with label believe. Show all posts

Wednesday, March 28, 2012

Poor performance for business day calculation from aspfaq sample

Greeting, below is the complete SQL taken from aspfaq.com (retrieved
from this newsgroup I believe) The query takes about two minutes to
run. Does anybody have a better set based way (sub-second response) to
determine business days?

CREATE TABLE dbo.Calendar

(

dt SMALLDATETIME NOT NULL PRIMARY KEY
CLUSTERED, -- Date value

IsWeekday BIT,
-- Is this date a weekday (M -
F)

IsHoliday BIT,
-- Is this date a holiday

Y SMALLINT,
-- Year the date falls in

FY SMALLINT,
-- Fiscal Year (needed?)

Q TINYINT,
-- Quarter date falls in

M TINYINT,
-- Numeric month of date

D TINYINT,
-- Numeric day of date

DW TINYINT,
-- Numeric DayOfWeek
(Sunda=1,Monday=2)

MonthName VARCHAR(9),
-- String name of month

DayName VARCHAR(9),
-- String name of day

W TINYINT
-- Week number

)

GO

-- Start & End Dates

DECLARE @.StartDate DATETIME

DECLARE @.EndDate DATETIME

SET @.StartDate = DATEADD(d, -1, '20000101')

SET @.EndDate = DATEADD(d, -1, '20300101')

-- Total number of dates to generate

DECLARE @.Days INT

SET @.Days = DATEDIFF(DAY, @.StartDate, @.EndDate)

-- Create temporary Numbers table

CREATE TABLE #Numbers

(

Number INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED

)

-- Insert a number into our temp table for each date to be generated

WHILE COALESCE(SCOPE_IDENTITY(), 0) <= @.Days

BEGIN

INSERT #Numbers DEFAULT VALUES

END

-- Generate a date for each day in our timespan

INSERT Calendar(dt)

SELECT DATEADD(DAY, Number, @.StartDate)

FROM #Numbers

WHERE Number <= @.Days

ORDER BY Number

-- Remove the temporary Numbers table

DROP TABLE #Numbers

GO

-- Update other columns

UPDATE dbo.Calendar SET

IsWeekday = CASE WHEN DATEPART(DW, dt) IN (1, 7) THEN 0
ELSE 1 END,

IsHoliday = 0,

Y = YEAR(dt),

FY = YEAR(dt),

Q = CASE

WHEN MONTH(dt) <= 3 THEN 1

WHEN MONTH(dt) <= 6 THEN 2

WHEN MONTH(dt) <= 9 THEN 3

ELSE 4 END,

M = MONTH(dt),

D = DAY(dt),

DW = DATEPART(DW, dt),

MonthName = DATENAME(MONTH, dt),

DayName = DATENAME(DW, dt),

W = DATEPART(WK, dt)

-- Query in question (takes almost 2 minutes to execute and return a
value)

SELECT

C.dt

FROM

Calendar C

WHERE

C.IsWeekDay = 1

AND C.IsHoliday = 0

AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )Here's an easy one:

SELECT
CASE
WHEN ([Date] % 7) > 1 THEN 'Business day'
ELSE 'Weekend day'
END AS "IsBusinessDay"
FROM table

The symbol % here is the modulo operator.
The remainder of dividing Date by 7 returns
0 for Saturday, 1 for Sunday and up to 6 for Friday.
Remainders from 2 to 6 correspond to Monday to Friday.

GeoSynch

"pb648174" <google@.webpaul.net> wrote in message
news:1113345847.550164.212820@.l41g2000cwc.googlegr oups.com...
> Greeting, below is the complete SQL taken from aspfaq.com (retrieved
> from this newsgroup I believe) The query takes about two minutes to
> run. Does anybody have a better set based way (sub-second response) to
> determine business days?
> CREATE TABLE dbo.Calendar
> (
> dt SMALLDATETIME NOT NULL PRIMARY KEY
> CLUSTERED, -- Date value
> IsWeekday BIT,
> -- Is this date a weekday (M -
> F)
> IsHoliday BIT,
> -- Is this date a holiday
> Y SMALLINT,
> -- Year the date falls in
> FY SMALLINT,
> -- Fiscal Year (needed?)
> Q TINYINT,
> -- Quarter date falls in
> M TINYINT,
> -- Numeric month of date
> D TINYINT,
> -- Numeric day of date
> DW TINYINT,
> -- Numeric DayOfWeek
> (Sunda=1,Monday=2)
> MonthName VARCHAR(9),
> -- String name of month
> DayName VARCHAR(9),
> -- String name of day
> W TINYINT
> -- Week number
> )
> GO
>
> -- Start & End Dates
> DECLARE @.StartDate DATETIME
> DECLARE @.EndDate DATETIME
> SET @.StartDate = DATEADD(d, -1, '20000101')
> SET @.EndDate = DATEADD(d, -1, '20300101')
>
> -- Total number of dates to generate
> DECLARE @.Days INT
> SET @.Days = DATEDIFF(DAY, @.StartDate, @.EndDate)
>
> -- Create temporary Numbers table
> CREATE TABLE #Numbers
> (
> Number INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED
> )
>
> -- Insert a number into our temp table for each date to be generated
> WHILE COALESCE(SCOPE_IDENTITY(), 0) <= @.Days
> BEGIN
> INSERT #Numbers DEFAULT VALUES
> END
>
> -- Generate a date for each day in our timespan
> INSERT Calendar(dt)
> SELECT DATEADD(DAY, Number, @.StartDate)
> FROM #Numbers
> WHERE Number <= @.Days
> ORDER BY Number
>
> -- Remove the temporary Numbers table
> DROP TABLE #Numbers
> GO
>
> -- Update other columns
> UPDATE dbo.Calendar SET
> IsWeekday = CASE WHEN DATEPART(DW, dt) IN (1, 7) THEN 0
> ELSE 1 END,
> IsHoliday = 0,
> Y = YEAR(dt),
> FY = YEAR(dt),
> Q = CASE
> WHEN MONTH(dt) <= 3 THEN 1
> WHEN MONTH(dt) <= 6 THEN 2
> WHEN MONTH(dt) <= 9 THEN 3
> ELSE 4 END,
> M = MONTH(dt),
> D = DAY(dt),
> DW = DATEPART(DW, dt),
> MonthName = DATENAME(MONTH, dt),
> DayName = DATENAME(DW, dt),
> W = DATEPART(WK, dt)
>
>
> -- Query in question (takes almost 2 minutes to execute and return a
> value)
> SELECT
> C.dt
> FROM
> Calendar C
> WHERE
> C.IsWeekDay = 1
> AND C.IsHoliday = 0
> AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
> GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )|||We already have the isWeekday column in the table - what we need to
know is, how do I add 9 business days to a particular date efficiently?|||On 12 Apr 2005 15:44:07 -0700, pb648174 wrote:

> Greeting, below is the complete SQL taken from aspfaq.com (retrieved
> from this newsgroup I believe) The query takes about two minutes to
> run. Does anybody have a better set based way (sub-second response) to
> determine business days?
[snip]
> -- Query in question (takes almost 2 minutes to execute and return a
> value)
> SELECT
> C.dt
> FROM
> Calendar C
> WHERE
> C.IsWeekDay = 1
> AND C.IsHoliday = 0
> AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
> GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )

It looks to me like you're looking for the day that is nine business days
from today. At least, once I let the query above run for the two minutes, I
got April 26th when I ran it today (April 13th).

If that's so, then you *know* ahead of time that that day will be greater
than today and less than 60 days from today, right? Unless there's some
weird span of sixty consecutive holidays, anyway. So you can add that fact
to the main where clause, and that will speed it up mightily:

SELECT
C.dt
FROM
Calendar C
WHERE
C.IsWeekDay = 1
AND C.IsHoliday = 0
AND C.dt BETWEEN GETDATE() AND DATEADD(d,60,GETDATE())
AND 9 = (
SELECT COUNT(*) FROM Calendar C2
WHERE C2.dt >= GETDATE()
AND C2.dt <= C.dt
AND C2.IsWeekDay = 1
AND C2.IsHoliday = 0)

Another thing you can do is to add indexes to the IsWeekDay and IsHoliday
columns (which requires changing them from BIT to TINYINT, since BIT can't
be indexed). Or even add a covering index on (DT, IsWeekDay, IsHoliday).
But that won't be necessary if you bound the main SELECT as I did -- I got
subsecond performance for the query above.|||That does execute much faster, but 50 is an arbitray number, and since
I will need to use it to schedule events up to one year or even
multiple years in the future, that won't work. In particular, for a set
of scheduled tasks which have a startdate and a lagdays column, I need
to calculate the end date for those tasks based on the above calendar
with business days/holidays entered. How would I do that with the above
query without the "bounding" limitation, returned as a set and with
decent performance?|||Here are some ideas:

1. This is fast but unfortunately in SQL2000 it can't be parameterized
without dynamic SQL:

SELECT MAX(dt)
FROM
(SELECT TOP 9 dt
FROM Calendar
WHERE dt >= CURRENT_TIMESTAMP
AND isweekday = 1
AND isholiday = 0
ORDER BY dt) AS T

2. This one can be parameterized but I wouldn't generally recommend it
because it relies on undocumented behaviour:

DECLARE @.days INTEGER, @.dt DATETIME

SET @.days = 9
SET ROWCOUNT @.days

SELECT @.dt = dt
FROM Calendar
WHERE dt >= CURRENT_TIMESTAMP
AND isweekday = 1
AND isholiday = 0
ORDER BY dt

SELECT @.dt

3. Extending Ross's suggestion, it shouldn't be difficult to calculate
a sensible upper bound for the query, even based on larger date ranges:

SELECT C.dt
FROM Calendar C
WHERE C.isweekday = 1
AND C.isholiday = 0
AND C.dt BETWEEN GETDATE() AND DATEADD(d,@.days*0.30+60.0,GETDATE())
AND 9 = (
SELECT COUNT(*) FROM Calendar C2
WHERE C2.dt >= GETDATE()
AND C2.dt <= C.dt
AND C2.IsWeekDay = 1
AND C2.IsHoliday = 0)

I think this last method is the best option, together with Ross's
suggestions on index improvements.

--
David Portas
SQL Server MVP
--|||And given that I have a table Called ScheduleTask with columns
StartDate & Duration, how do I for a group of rows, calculate the item
with the largest end date if the duration is based on the above
business days?

Why did you use 30%? Shouldn't it be 70% since I assume you are taking
out what you are guessing will be the weekends and holidays and adding
in a buffer of 60 days?|||On 14 Apr 2005 07:09:32 -0700, pb648174 wrote:

> That does execute much faster, but 50 is an arbitray number, and since
> I will need to use it to schedule events up to one year or even
> multiple years in the future, that won't work. In particular, for a set
> of scheduled tasks which have a startdate and a lagdays column, I need
> to calculate the end date for those tasks based on the above calendar
> with business days/holidays entered. How would I do that with the above
> query without the "bounding" limitation, returned as a set and with
> decent performance?

If you need to get sets, I think the best idea is to number the
business-days in the calendar table.

ALTER dbo.Calendar
ADD BusinessDayNum INT NOT NULL DEFAULT (0)

UPDATE dbo.calendar
SET BusinessDayNum = (
SELECT COUNT(*) FROM calendar C2
WHERE c2.isWeekday=1 and c2.isHoliday=0
AND C2.dt <= Calendar.dt
)
WHERE isWeekday=1 AND isHoliday=0

CREATE INDEX idx_Cal_BDN ON dbo.Calendar (BusinessDayNum)

Now you can do this:

CREATE TABLE #myTable
( startDate DATETIME NOT NULL, DurationDays INT NOT NULL)

INSERT #myTable VALUES ('2003-05-14',10)
INSERT #myTable VALUES ('2003-05-15',12)
INSERT #myTable VALUES ('2003-05-16',14)
INSERT #myTable VALUES ('2004-05-14',10)
INSERT #myTable VALUES ('2004-05-15',12)
INSERT #myTable VALUES ('2004-05-16',14)

SELECT T.startDate, T.DurationDays, C2.dt "endDate"
FROM dbo.Calendar C2, dbo.Calendar C1, #myTable T
WHERE C1.DT = T.startDate
AND C2.BusinessDayNum = C1.BusinessDayNum + T.DurationDays

startDate DurationDays endDate
-------- ---- -------
2003-05-14 00:00:00.000 10 2003-05-28 00:00:00
2003-05-15 00:00:00.000 12 2003-06-02 00:00:00
2003-05-16 00:00:00.000 14 2003-06-05 00:00:00
2004-05-14 00:00:00.000 10 2004-05-28 00:00:00
2004-05-15 00:00:00.000 12 2000-01-18 00:00:00
2004-05-16 00:00:00.000 14 2000-01-20 00:00:00

Unfortunately, you will need to have ALL of your holidays set before you do
this, and if your holidays change you should redo the UPDATE.|||That's why this won't work... We will actually be storing the holidays
per user in a separate table. We are basically trying to duplicate MS
Project functionality and are now thinking we should do it all in
application logic instead of SQL, i.e. port in all the task data, do
all the calculations and then do a couple hundred updates for all of
the task data. SQL gurus, please show me a way to not have to do that...|||"pb648174" <google@.webpaul.net> wrote in message
news:1113487772.592286.157190@.z14g2000cwz.googlegr oups.com...
> That does execute much faster, but 50 is an arbitray number, and since
> I will need to use it to schedule events up to one year or even
> multiple years in the future, that won't work. In particular, for a set
> of scheduled tasks which have a startdate and a lagdays column, I need
> to calculate the end date for those tasks based on the above calendar
> with business days/holidays entered. How would I do that with the above
> query without the "bounding" limitation, returned as a set and with
> decent performance?

Maybe try something like:
AND C.dt BETWEEN startdate AND DATEADD(d,3*duration,startdate)

Good Luck,
Jim|||The design for the Calendar table you have is not that good -- BIT
flags and other proprietary datatypes are a mess. There is no
Julianized day number, teh weekday numbers are nto ISO Standard, etc.

Sit down and design a good encoding for the type of day -- weekend,
holiday, workday, etc. A calendar table for US Secular holidays can be
built from the data at this website, so you will get the three-day
weekends:

http://www.smart.net/~mmontes/ushols.html

I would consider using a spreadsheet to get the raw data, since they
usually have good temporal functions. Try a query like this:

SELECT MIN(C1.cal_date)
FROM Calendar AS C1
WHERE @.workday_count
= (SELECT SUM(CASE WHEN C1.cal_date
BETWEEN CURRENT_TIMESTAMP
AND DATEADD(D,
(@.workday_count *10), CURRENT_TIMESTAMP)
AND day_type = 'workday'
THEN 1 ELSE 0 END);

I am assuming that the cal_date column is a clustered primary key so
the optimizer will know to stop and not scan the rest of the table on
the first match. Multiplying the count by ten is probably excessive,
but certainly safe. if that is too slow, change it to 2 or 3.|||I think this is a sign that SQL just isn't going to be able to handle
this kind of job effectively. There will be user holidays added to this
as well, so it is possible that someone could be out for two weeks or
more and that when adding 1 business day, *10 will not be sufficient. I
thought since I got this example from one of the regular poster's web
site that it would be straightforward to implement but that seems to
not be the case. I think we are just going to pull all the data in and
do the calculation in C# and then write all the data back out. Thanks
for the help everyone.|||I am getting a little lost here. The auxiliary Calendar table has
nothing to do with any particular user; it is for the enterprise as a
whole. A personal timesheet is a differrent matter, since each
employee's will be different.

Did you want to have code that says something like when day X is a
Moslem holiday and employee Y is a Moslem, then X is not a work day for
him? Ramada runs for a lunar month, for example, so if you do:

WHERE @.workday_count
= (SELECT SUM(CASE WHEN C1.cal_date
BETWEEN CURRENT_TIMESTAMP
AND DATEADD(D,
(@.workday_count *10 + 30), CURRENT_TIMESTAMP)
AND secular_day_type = 'workday'
AND relgion_day_type <> 'Moslem'
THEN 1 ELSE 0 END)

You know that you are safe. this same pattern can be extended to
include Mr. Y's vacation, sick and personal leave days with a join to
his personal timesheet. the predicate in the WHEN just gets uglier,
but so will any procedural code that has to have the same logic inside
a loop with IF-THEN statements. Timekeeping is not simple.|||The biggest problem with the query you posted is that the * 10 is not
sufficient for the previously posted reason. What I am saying about the
holidays is that we have an indeterminate amount of users and each will
have their own set of holidays so there will be a separate table for
those holidays and that the Calendar table would have the "master" or
default list of holidays.|||"pb648174" <google@.webpaul.net> wrote in message
news:1113826655.615952.214820@.o13g2000cwo.googlegr oups.com...
> The biggest problem with the query you posted is that the * 10 is not
> sufficient for the previously posted reason. What I am saying about the
> holidays is that we have an indeterminate amount of users and each will
> have their own set of holidays so there will be a separate table for
> those holidays and that the Calendar table would have the "master" or
> default list of holidays.

If you have a calendar table with all of the dates and day types:
(Dt, daytype)

And you have a user table with dates and day types only for those days that
are different
(User, Dt, daytype)

Then you can get bob's calendar with

Select Dt, Coalesce(u.daytype,m.daytype) as daytype
from calendar as m left join usercalendar as u on m.dt = u.dt
Where u.user = 'bob'

Regards,
Jim|||Table joins I don't have a problem with.. My problem is the following,
which I have mentioned in each of the past couple replies:

"There will be user holidays added to this
as well, so it is possible that someone could be out for two weeks or
more and that when adding 1 business day, *10 will not be sufficient."

and

"The biggest problem with the query you posted is that the * 10 is not
sufficient for the previously posted reason"

because a person could have two weeks of vacation, a month of sick
leave, etc. I need something that performs well and doesn't include
some "hacked" factor to make it perform well. The first hack was the
Calendar table, which made this "* x" factor necessary as an additional
hack...

So far I don't see any indication that SQL will be able to handle this
kind of task.|||OK here goes:

1. Create a table (temp or static with ONLY working days and a
contiguous numbering sequence between one day and the next working day)
- that makes it very easy to do the offset.

2. adding the user holiday is easy (find the number of days holiday
between the 2 dates and recalculate)

Sample code (written assuming UK date format - should work with US):

SET NOCOUNT ON

DECLARE @.StartDate datetime
DECLARE @.DaysOffset int

SET @.StartDate = '2005-04-20'
SET @.DaysOffset = 5

DECLARE @.DateWithOffsetNoUserHolidays datetime
DECLARE @.TempDay datetime
DECLARE @.nSequence int
DECLARE @.UserWorkingDayHolidays int
DECLARE @.DateWithOffset datetime

SET @.UserWorkingDayHolidays = 0

CREATE TABLE #WorkingDays ([Date] smalldatetime NOT NULL, [sequence] int
NOT NULL)

SET @.nSequence = 0
SET @.TempDay = '2005-01-01'

WHILE @.TempDay < '2006-12-31'
BEGIN

IF DATEPART(dw, @.TempDay) BETWEEN 2 AND 6 -- AND NOT EXISTS(SELECT 1
FROM BankHolidays WHERE [Date] = @.TempDay
BEGIN
INSERT INTO #WorkingDays VALUES(@.TempDay, @.nSequence)
SET @.nSequence = @.nSequence + 1
END

SELECT @.TempDay = DATEADD(dd,1, @.TempDay)
END

SELECT @.DateWithOffsetNoUserHolidays = [Date]
FROM #WorkingDays
WHERE [Sequence] = (SELECT [Sequence] + @.DaysOffset FROM #WorkingDays
WHERE [Date] = @.StartDate)

/*Get number of user holidays during period between @.StartDate and
@.DateWithOffset and redo:
e.g.
SELECT @.UserWorkingDayHolidays = Count(*)
FROM UserHolidays
WHERE Date BETWEEN @.StartDate AND @.DateWithOffsetNoUserHolidays AND
User = ...
*/

SELECT @.DateWithOffset = [Date]
FROM #WorkingDays
WHERE [Sequence] = (SELECT [Sequence] + @.DaysOffset +
@.UserWorkingDayHolidays FROM #WorkingDays WHERE [Date] = @.StartDate)

DROP TABLE #WorkingDays

SELECT @.DateWithOffset

*** Sent via Developersdex http://www.developersdex.com ***|||My last effort was flawed for the user holiday element of your
requirement. Here's a better effort, but doesn't work for calculating
bussiness days in the past - you'd have to change the penultimate select
for that.

Change @.StartDate and @.BusinessDaysOffset to change the inputs. Should
be sub-second even for a 10

The script build a temporary table of working days the first time it's
run - better to have a permanent table reallty.

Duncan

SET NOCOUNT ON

DECLARE @.StartDate datetime
DECLARE @.BusinessDaysOffset int

SET @.StartDate = '2005-04-21'
SET @.BusinessDaysOffset = 2

IF @.BusinessDaysOffset < 1
RAISERROR('Offset must be > 0',16,1)

/* Create sample temp table for user holidays */
IF OBJECT_ID('tempdb..#UserHolidays') IS NULL
BEGIN
CREATE TABLE #UserHolidays (UserId int NOT NULL, HolidayDate
smalldatetime)

/* Add tomorrow as a holiday for user 1 - date must not contain time -
so that's why there's so many CASTs */
INSERT INTO #UserHolidays VALUES (1, CAST(CAST(CAST(DATEADD(d, 1,
getdate()) as real) as int) as smalldatetime))
END

DECLARE @.TempDay datetime
DECLARE @.nSequence int

/* Get date without time element */
SET @.StartDate = CAST(CAST(CAST(@.StartDate as real) as int) as
smalldatetime)

IF OBJECT_ID('tempdb..#WorkingDays') IS NULL
BEGIN
/* better to use a permanent table as the data doesn't change very
often - but this is just an example */
CREATE TABLE #WorkingDays ([Date] smalldatetime NOT NULL, [sequence]
int NOT NULL)
CREATE UNIQUE INDEX ID_WorkingDays_Date ON #WorkingDays ([Date])

SET @.nSequence = 0
SET @.TempDay = '2005-01-01'

WHILE @.TempDay < '2030-12-31'
BEGIN
/* don't add Saturday or Sunday - or bank holidays from table */
IF DATEPART(dw, @.TempDay) BETWEEN 2 AND 6 /* AND NOT EXISTS(SELECT
1 FROM BankHolidays WHERE [Date] = @.TempDay) */
BEGIN
INSERT INTO #WorkingDays VALUES(@.TempDay, @.nSequence)
SET @.nSequence = @.nSequence + 1
END

SELECT @.TempDay = DATEADD(dd,1, @.TempDay)
END
END
/*Temp table populated */

/* Create temp table to contain working days for the user and after date
specified*/
CREATE TABLE #UserWorkingDays ([Date] smalldatetime NOT NULL)

CREATE UNIQUE INDEX ID_UserWorkingDays_Date ON #UserWorkingDays ([Date])

/*Limit the number of rows to the number of working days required */
SET ROWCOUNT @.BusinessDaysOffset

/* Insert working days after current one where not joined to user's
holiday table*/
INSERT INTO #UserWorkingDays ([Date])
SELECT WD.[Date]
FROM #WorkingDays AS WD
LEFT JOIN #UserHolidays AS UH ON WD.[Date] = UH.HolidayDate AND
UH.UserId = 1
WHERE UH.HolidayDate IS NULL AND [Date] > @.StartDate
ORDER BY WD.[Date]

/* Get last working day */
SELECT MAX([Date])
FROM #UserWorkingDays

DROP TABLE #UserWorkingDays

*** Sent via Developersdex http://www.developersdex.com ***|||I had tried the rowcount solution previously and it did not work for
some (unposted, due to other queries around the calculations and we
will eventually need time) reasons. We have given up on doing this in
SQL and are doing it in C# quite successfully - thank you everyone for
the help.sql

Poor Performance - Nested Views & Complex Joins

The code below is from a nested view, which I've read should be avoided. I've also noticed GETDATE() is used, which I believe causes GETDATE() to be executed for every record selected (correct me if I'm wrong). I'm also guessing a JOIN containing a UNION against a SELECT statement is not a good idea. What other problems do you notice?

SELECT trans.Entry_Code, trans.D_C, trans.ADP_Security_# ,
trans.TRID, trans.Batch_Code, trans.Last_Money,
null as Shares, Settle_date as Process_Date,
null as Closing_Price, trans.Dwnld_Date, trans.Acnt,
null as Mktval,
cast(Null as varchar(20)) as Cusip_#,
ACT.dbo.account.account_key AS account_key
FROM (SELECT * FROM ADPDBBOOK.dbo.YTD05B
WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)
UNION
SELECT * FROM ADPDBBOOK.dbo.YTD06B) trans
INNER JOIN ACT_DATA.dbo.account
ON ACT_DATA.dbo.account.account_key = RIGHT(trans.Acnt, 5)
INNER JOIN tbl_Accounts_TransactionalData
ON trans.Acnt = tbl_Accounts_TransactionalData.Acnt

Thanks, DaveYou've been told wrong.

There is nothing bad about nested subquery. They, like any coding technique, should not be used unnecesarilly, but the optimizer will incorporate them in its query plan. You can try eliminating the nested UNION subquery, but you will need to link the account and TransactionalData tables into both YTD tables. This may allow you to better benefit from indexing, but will incur twice as many table scans. So whether this ends up making your code more efficient depends heavily upon your data. You just have to try it and test for yourself.

In your code, GETDATE() will not be executed for every record. It will only be executed once at the start of the query. You WILL, however, lose any benefit of an index on Process_Date by using this syntax: WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)Use this syntax instead:WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)

Other problems? Dump the "SELECT *" and enumerator your columns. "SELECT *" is sloppy programming and has not business in production code.|||Nested views are not derived tables...there's a major differences...and yes avoid netsed views...BUT

FROM (SELECT * FROM ADPDBBOOK.dbo.YTD05B
WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)
UNION
SELECT * FROM ADPDBBOOK.dbo.YTD06B) trans

There is no good reason to do this|||I think Blindman may have meant to use

WHERE Process_Date > DATEadd(mm, -15, GETDATE())

instead of

WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)|||OK, I see it now

FROM ( SELECT *
FROM ADPDBBOOK.dbo.YTD05B
WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)
UNION
SELECT *
FROM ADPDBBOOK.dbo.YTD06B) trans
INNER JOIN ACT_DATA.dbo.account
ON ACT_DATA.dbo.account.account_key = RIGHT(trans.Acnt, 5)
INNER JOIN tbl_Accounts_TransactionalData
ON trans.Acnt = tbl_Accounts_TransactionalData.Acnt

First I would normalize the tables..YTD0xx tables should all be 1 table with a type of...whatever that stuff means..I'm guessing month?

Also, and I'm not sure, bu this might cause a scan: RIGHT(trans.Acnt, 5)

Why are the element size different? Did you overload 1 column with additional data instead of normalizing it into 2 columns?|||Thanks all. The design is definitely something that needs to be looked at.

I'm still not in favor of nested views. Most articles I've read about the pros and cons of nested views indicate a major con is how deeply nested they can become. While SQL Server does expand the views to their base table representation, this does create a certain amount of extra work behind the scenes that is not reported as part of the execution plan. I've also seen many times where developers find a view that comes close to meeting their needs and creates another view off of this view without taking the time to research if the first view is the base view. Several times this summer I've been asked to troubleshoot poor server performance where the CPUs are over 95% utilized. I ended up finding the source of the problem was a nested view. The execution plan revealed over 100-150 icons containing multiple table scans and involving 6-10 other views and multiple base tables all the result of one select statement off of a nested view. I know the argument can be made the real problem was a poorly designed nested view and not the use of nested views itself, however, at least in my experience, most people don't take the time needed to carefully analyze existing views before using them. I'm not even going to touch on the difficulty in troubleshooting performance problems caused by poorly designed nested views since I've typed too much already. Sorry about that.

Dave|||Dave why don't you post the ddl of the tables, sample data and expecxted results.

Read the hint sticky at the top of the board|||I think Blindman may have meant to use

WHERE Process_Date > DATEadd(mm, -15, GETDATE())

instead of

WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)

Yes. Copy/paste error. Thanks for catching it.

And yes, nested VIEWS are bad, if only because they serve more to obfuscate code than to clarify it.|||Unless I'm just WAY off base, the sample you posted isn't syntactically correct, so I'm not about to try to fix it until we get thing clarified. Simply reformatting what you posted, I get:SELECT
trans.Entry_Code, trans.D_C, trans.ADP_Security_#
, trans.TRID, trans.Batch_Code, trans.Last_Money
, null as Shares, Settle_date as Process_Date, null as Closing_Price
, trans.Dwnld_Date, trans.Acnt, null as Mktval
, cast(Null as varchar(20)) as Cusip_#
, ACT.dbo.account.account_key AS account_key
FROM (SELECT * FROM ADPDBBOOK.dbo.YTD05B
WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)
UNION SELECT *
FROM ADPDBBOOK.dbo.YTD06B) trans
INNER JOIN ACT_DATA.dbo.account
ON ACT_DATA.dbo.account.account_key = RIGHT(trans.Acnt, 5)
INNER JOIN tbl_Accounts_TransactionalData
ON trans.Acnt = tbl_Accounts_TransactionalData.AcntI guarantee you that won't work. Can you re-examine what you're doing, and cut-and-paste the actual code for us to help you fix?

-PatP|||Here's the actual code. Thanks

Insert Into actimize_Data.dbo.transactions (Transaction_Date_Time, Trans_Type_Cd, Account_Key, Local_Currency_Amount, Local_Currency_Cd, Row_Insert_Date)
SELECT Transaction_Date_Time, Trans_Type_Cd, Account_Key, Local_Currency_Amount, Local_Currency_Cd, Row_Insert_Date
FROM view_Transactions_1YR

CREATE VIEW dbo.view_Transactions_1YR
AS
SELECT Process_Date AS Transaction_Date_Time,
Transaction_Type AS Trans_Type_Cd,
RIGHT(Acnt, 5) AS Account_Key,
case WHEN last_money = 0.0
THEN Mktval * (CASE WHEN D_C = 'd' THEN - 1
ELSE 1 END)
ELSE Last_Money end as Local_Currency_Amount,
'USD' AS Local_Currency_Cd,
Dwnld_Date AS Row_Insert_Date
FROM dbo.view_TransactionsData_1YR trans
LEFT OUTER JOIN dbo.tbl_Transaction_Table
ON trans.Transaction_Type = dbo.tbl_Transaction_Table.Trans_Type_Cd
WHERE (dbo.tbl_Transaction_Table.Trans_Table = 'transactions')

CREATE VIEW dbo.view_TransactionsData_1YR
AS
SELECT transactions.Entry_Code, transactions.D_C,
transactions.ADP_Security_#,
dbo.getTransType(transactions.TRID,
transactions.Entry_Code, transactions.Batch_code,
transactions.D_C, transactions.ADP_Security_#,
transactions.Last_Money, transactions.Shares) AS Transaction_Type,
transactions.Process_Date, transactions.Shares,
transactions.Closing_Price, transactions.Dwnld_Date,
transactions.Acnt, transactions.Last_Money,
transactions.Mktval, transactions.Cusip_#, transactions.TRID
FROM (Select * from view_WBTransactions_DataIn_1YR
UNION
Select * from view_WBMargin_DataIn_1YR) transactions
WHERE (isnull(transactions.Entry_Code,'') NOT IN ('AIM', 'ALL', 'CEF', 'FED', 'FND', 'MMR', 'PPS', 'REI', 'WBR'))
And NOT (transactions.TRID = 'B' AND transactions.Entry_Code = 'NW3' ANd Batch_code ='3N')
And NOT (transactions.TRID = 'B' AND transactions.Entry_Code = 'CON' ANd Batch_code ='RG')
And NOT (transactions.TRID = 'B' AND transactions.Entry_Code = 'JNL' ANd Batch_code ='MF')
And NOT (transactions.TRID = 'B' AND transactions.Entry_Code = 'CHK' ANd Batch_code ='MN')
And NOT (transactions.TRID = 'B' AND transactions.Entry_Code = 'ADJ' ANd Batch_code ='AS')
And ( Batch_code <> 'RG')
And ( Batch_code <> 'OT')

CREATE TABLE [tbl_Transaction_Table] (
[Trans_Type_Cd] [varchar] (50) NOT NULL ,
[Trans_Table] [varchar] (50) NULL ,
CONSTRAINT [PK_tbl_Transaction_Table] PRIMARY KEY CLUSTERED
([Trans_Type_Cd]
) )

CREATE VIEW dbo.view_WBTransactions_DataIn_1YR
AS
SELECT trans.Entry_Code, trans.D_C, trans.ADP_Security_#, trans.TRID,
trans.Batch_code, trans.Last_Money, trans.Shares,
trans.Process_Date, trans.Closing_Price, trans.Dwnld_Date,
trans.Acnt, trans.Mktval, trans.Cusip_#, ACTIMIZE_DATA.dbo.account.account_key
FROM (SELECT * FROM ADPDBBOOK.dbo.YTD05B
WHERE (DATEDIFF(mm, Process_Date, GETDATE()) <= 15)
UNION
SELECT * FROM ADPDBBOOK.dbo.YTD06B) trans
INNER JOIN ACTIMIZE_DATA.dbo.account
ON ACTIMIZE_DATA.dbo.account.account_key = RIGHT(trans.Acnt, 5)
INNER JOIN tbl_Accounts_TransactionalData
ON trans.Acnt = tbl_Accounts_TransactionalData.Acnt

CREATE VIEW dbo.view_WBMargin_DataIn_1YR
AS
SELECT trans.Entry_Code, trans.D_C, trans.ADP_Security_# , trans.TRID,
trans.Batch_Code, trans.Last_Money, null as Shares,
Settle_date as Process_Date,
null as Closing_Price, trans.Dwnld_Date, trans.Acnt,
null as Mktval,
cast(Null as varchar(20)) as Cusip_#,
ACTIMIZE_DATA.dbo.account.account_key AS account_key
FROM (SELECT * FROM ADPDBMRGN.dbo.YTD05M
WHERE (DATEDIFF(mm, Dwnld_Date, GETDATE()) <= 14)
UNION
SELECT * FROM ADPDBMRGN.dbo.YTD06M) trans
INNER JOIN ACTIMIZE_DATA.dbo.account
ON ACTIMIZE_DATA.dbo.account.account_key = RIGHT(trans.Acnt, 5)
INNER JOIN dbo.tbl_Accounts_TransactionalData
ON trans.Acnt = dbo.tbl_Accounts_TransactionalData.Acnt

CREATE TABLE [YTD05B] (
[Branch] [varchar] (3) NULL ,
[TRID] [varchar] (1) NULL ,
[Entry_Code] [varchar] (3) NULL ,
[Batch_Code] [varchar] (2) NULL ,
[Acnt] [varchar] (9) NULL ,
[Process_Date] [datetime] NULL ,
[Settle_Date] [datetime] NULL ,
[D_C] [varchar] (1) NULL ,
[Shares] [float] NULL ,
[Security_Desc] [varchar] (30) NULL ,
[ADP_Security_#] [varchar] (7) NULL ,
[Cusip_#] [varchar] (9) NULL ,
[Last_Money] [float] NULL ,
[Closing_Price] [float] NULL ,
[Mktval] [float] NULL ,
[Dividend_Factor] [float] NULL ,
[AcntType] [varchar] (1) NULL ,
[Chkdigit] [varchar] (1) NULL ,
[Bond_Mat_Date] [datetime] NULL ,
[Bond_Int_Rate] [float] NULL ,
[Bond_Book_Value] [float] NULL ,
[Frac_Qty] [float] NULL ,
[Price_Multipler] [varchar] (1) NULL ,
[Option_Ind] [varchar] (1) NULL ,
[MSD_Code_1] [varchar] (1) NULL ,
[MSD_Code_25] [varchar] (4) NULL ,
[MSD_Code_67] [varchar] (2) NULL ,
[Security_Spin] [varchar] (1) NULL ,
[Dwnld_Date] [datetime] NULL ,
[AdvTC] [varchar] (2) NULL ,
[AdvST] [varchar] (2) NULL ,
[LedgerLM] [varchar] (6) NULL ,
[LedgerD] [varchar] (6) NULL
)

CREATE TABLE [YTD06B] (
[Branch] [varchar] (3) NULL ,
[TRID] [varchar] (1) NULL ,
[Entry_Code] [varchar] (3) NULL ,
[Batch_Code] [varchar] (2) NULL ,
[Acnt] [varchar] (9) NULL ,
[Process_Date] [datetime] NULL ,
[Settle_Date] [datetime] NULL ,
[D_C] [varchar] (1) NULL ,
[Shares] [float] NULL ,
[Security_Desc] [varchar] (30) NULL ,
[ADP_Security_#] [varchar] (7) NULL ,
[Cusip_#] [varchar] (9) NULL ,
[Last_Money] [float] NULL ,
[Closing_Price] [float] NULL ,
[Mktval] [float] NULL ,
[Dividend_Factor] [float] NULL ,
[AcntType] [varchar] (1) NULL ,
[Chkdigit] [varchar] (1) NULL ,
[Bond_Mat_Date] [datetime] NULL ,
[Bond_Int_Rate] [float] NULL ,
[Bond_Book_Value] [float] NULL ,
[Frac_Qty] [float] NULL ,
[Price_Multipler] [varchar] (1) NULL ,
[Option_Ind] [varchar] (1) NULL ,
[MSD_Code_1] [varchar] (1) NULL ,
[MSD_Code_25] [varchar] (4) NULL ,
[MSD_Code_67] [varchar] (2) NULL ,
[Security_Spin] [varchar] (1) NULL ,
[Dwnld_Date] [datetime] NULL ,
[AdvTC] [varchar] (2) NULL ,
[AdvST] [varchar] (2) NULL ,
[LedgerLM] [varchar] (6) NULL ,
[LedgerD] [varchar] (6) NULL
)

CREATE TABLE [account] (
[account_key] [varchar] (50) NOT NULL ,
[organization_key] [varchar] (50) NULL ,
[branch_key] [varchar] (50) NULL ,
[primary_representative_key] [varchar] (50) NULL ,
[split_key] [varchar] (50) NULL ,
[acct_num] [varchar] (50) NULL ,
[acct_first_name] [varchar] (50) NULL ,
[acct_middle_name] [varchar] (50) NULL ,
[acct_last_name] [varchar] (50) NULL ,
[acct_maiden_name] [varchar] (50) NULL ,
[acct_other_name] [varchar] (50) NULL ,
[acct_type_cd] [varchar] (50) NULL ,
[acct_classification_cd] [varchar] (50) NULL CONSTRAINT [DF_account_acct_classification_cd] DEFAULT ('N/A'),
[primary_party_key] [varchar] (50) NULL ,
[acct_open_date] [datetime] NULL ,
[acct_close_date] [datetime] NULL ,
[last_review_date] [datetime] NULL ,
[sector_cd] [varchar] (50) NULL ,
[investment_objective_cd] [varchar] (50) NULL ,
[risk_tolerance_cd] [varchar] (50) NULL ,
[credit_limit_local_currrency_value] [float] NULL ,
[stated_liquid_net_worth] [float] NULL ,
[stated_total_net_worth] [float] NULL ,
[stated_account_activity] [float] NULL ,
[stated_account_value] [float] NULL ,
[is_suspect] [bit] NULL ,
[is_dvp_rvp] [bit] NULL ,
[is_active] [bit] NULL ,
[is_online_account] [bit] NULL ,
[avg_balance] [float] NULL ,
[verification_status] [varchar] (50) NULL ,
[verification_date] [datetime] NULL ,
[household_id] [varchar] (50) NULL ,
[is_proprietary] [bit] NULL ,
[is_street_side] [bit] NULL ,
[is_error_account] [bit] NULL ,
[is_institutional] [bit] NULL ,
[is_employee_account] [bit] NULL ,
[is_tax_deferred] [bit] NULL ,
[is_single_joint] [bit] NULL ,
[is_fee_based_acct] [bit] NULL ,
[is_anonymous_acct] [bit] NULL ,
[is_option_approved] [bit] NULL ,
[option_approval_level_cd] [varchar] (50) NULL ,
[is_discretionary] [bit] NULL ,
[managed_acct_type] [varchar] (50) NULL ,
[row_update_date] [datetime] NULL ,
CONSTRAINT [pk_account] PRIMARY KEY CLUSTERED
(
[account_key]
) )

CREATE TABLE [tbl_Accounts_TransactionalData] (
[Acnt] [varchar] (9) NOT NULL ,
CONSTRAINT [PK_tbl_Accounts_TransactionalData] PRIMARY KEY CLUSTERED
(
[Acnt]
))

CREATE TABLE [YTD05M] (
[Branch] [varchar] (3) NULL ,
[TRID] [varchar] (1) NULL ,
[Entry_Code] [varchar] (3) NULL ,
[Batch_Code] [varchar] (2) NULL ,
[Acnt] [varchar] (9) NULL ,
[Settle_Date] [datetime] NULL ,
[D_C] [varchar] (1) NULL ,
[ADP_Security_#] [varchar] (7) NULL ,
[Last_Money] [float] NULL ,
[AcntType] [varchar] (1) NULL ,
[Chkdigit] [varchar] (1) NULL ,
[US_Tax_Status] [varchar] (1) NULL ,
[Security_Spin] [varchar] (1) NULL ,
[Dwnld_Date] [datetime] NULL ,
[ADVtc] [varchar] (2) NULL ,
[ADVst] [varchar] (2) NULL ,
[LedgerLM] [varchar] (6) NULL
)

CREATE TABLE [YTD06M] (
[Branch] [varchar] (3) NULL ,
[TRID] [varchar] (1) NULL ,
[Entry_Code] [varchar] (3) NULL ,
[Batch_Code] [varchar] (2) NULL ,
[Acnt] [varchar] (9) NULL ,
[Settle_Date] [datetime] NULL ,
[D_C] [varchar] (1) NULL ,
[ADP_Security_#] [varchar] (7) NULL ,
[Last_Money] [float] NULL ,
[AcntType] [varchar] (1) NULL ,
[Chkdigit] [varchar] (1) NULL ,
[US_Tax_Status] [varchar] (1) NULL ,
[Security_Spin] [varchar] (1) NULL ,
[Dwnld_Date] [datetime] NULL ,
[ADVtc] [varchar] (2) NULL ,
[ADVst] [varchar] (2) NULL ,
[LedgerLM] [varchar] (6)
)