March 1, 2007 at 7:49 am
We are accessing a database that is normalized to the point of inefficiency. To retrieve the data a number of Views have been created to return the data ina usable format. Not only does the custom application call these views directly but the views reference other views and performance suffers considerably. We are attempting to use stored procedures to create temp tables to be used in place of the views as follows...
View 1 contains 4 references to view 2 as follows
Create view1 as select * from view2 join (select ... from view 2 where ...) UNION select ... from view2 join (select ... from view2 where ...)
View5 contains a select that joins view2 and view4
View7 contains a select that joins view 4 to view5
Is all rather ugly and can be extremely slow. I have attempted to rewrite the views as stored procedures that create temp tables #TMP1, TMP2, TMP4, #TMP5, TMP7 in place of the views. In each stored proc I check for the existence of the temp tables (formerly views) needed and if not in existence create them as follows
Proc 1
if not exists (select * from sysobjects where name = '#tmp2' and xtype='u')
begin
exec CREATE_TMP2
end
if not exists (select * from sysobjects where name = '#TMP1' and xtype='u')
begin
select * into #tmp1 from {original view 1 code}
end --uses #TMP2 in place of VEIW2
etc ...
So basically in each view I ensure any references already exist as Temp Tables and if not call a proc to create them. In this manner each view code is referenced only one time. At the end of the users calling script a stored proc to drop all temp tables will be called.
My current problem is that temp tables created in a SP are not available to the calling script. To fix this I can use global temp tables (##TMP1, ##TMP2, etc...). However there are multiple users (2-5) so I would like to create the tables with the SPID attached SELECT @TAB1='##TMP1_'+@@SPID
The code is very complicated and I do not wish to use EXEC statements to refer to these tables and the user calling .NET code should be able to refer by Temp Table name. I would rather simply be able to refer to the table in a TSQL statement directly as Select * from @Tab1 (where @tab1 would be something like ##TMP1_171)
The views currently work but are exceedingly slow and the temp table concept with stored procedures cuts execution time dramatically. Also the logic embedded in the current VIEWS must be retained.
Thoughts?
Thanks
Mike Hoyt
March 1, 2007 at 12:45 pm
It seems to me that replacing those views with global temp tables would be a really bad idea. You definitely do not want several denormalized copies of your database in tempdb.
I am sure we can give you a much better solution. First of all, what's stopping you from having the sp return the data directly to the client?
Second, Do you really need these nested views? Why not just write stored procs (or a single view) to return the data you need?
SQL guy and Houston Magician
March 1, 2007 at 1:20 pm
Robert
I was asked to make seven VIEWS that are used repeatedly more efficient. The views are on an extremely normalized database and the view themselves are Butt Ugly. I will include HOWEBVAC, HOWEBVAC1 and HOWEBVAC4 for your amusement. Be aware that HOWEBVAC5 refers to HOWEBVAC1 and HOWEBVAC4 and HOWEBVAC7 refers to HOWEBVAC4 and HOWEBVAC5. In this scenario often the same views are called repeatedly while performing a single select on another view! For this reason I thought that creating a Temp table would be more efficient. I have already checked all indicies, etc... Open to other ideas ...
Mike
ALTER View HOWEBVAC as
--Get all entries except 878-VAC non paid blockers, except for 878-blockers that have no thru date (meaning they are not linked to a week)
SELECT PAYCODENAME, FROMDATE, THRUDATE, AMOUNT, PAYCODEDTE, EFFECTDTE,
(case(select count(*) from HOWEBVAC1 a where a.PERSONNUM = b.PERSONNUM and a.FROMDATE = b.FROMDATE and SUBSTRING(a.PAYCODENAME,1,3) <> '140')
when 0 then 'N'
when 1 then 'N'
else 'Y'
END
)
AS BLOCKERS,
APPR, SIGNEDOFF, HISTEDIT, PERSONNUM, PERSONFULLNAME, BADGENUM, COMMENTTEXT, FISCYEAR
from HOWEBVAC1 b where
( NOT (substring(PAYCODENAME,1,3) = '878' and isnull(Thrudate,'') <> ''))
UNION
--Create a Week record for any 878-VAC non-paid blockers (can't actually create 40 hour 878-VAC card and 5 x 8 hours 878-VACs or we
--will have duplicates i.e. 80 hrs. The user will be able to add a 40 hour 878-VAC record, and if from and thru dates are entered
--it will actually create the split records only. This will save the user time. This part of the script needs to group the 5 x 8
--878-VAC cards togethter to make a record for the view.
SELECT a.PAYCODENAME,a.FROMDATE,a.THRUDATE,SUM(a.AMOUNT) AS AMOUNT,MAX(a.PAYCODEDTE) AS PAYCODEDTE,'' as EFFECTDTE, 'Y' AS BLOCKERS,
MIN(a.APPR) AS APPR,MIN(a.SIGNEDOFF) as SIGNEDOFF, MIN(a.HISTEDIT) as HISTEDIT,a.PERSONNUM,a.PERSONFULLNAME,a.BADGENUM,MAX(a.COMMENTTEXT) as
COMMENTTEXT,a.FISCYEAR
from HOWEBVAC1 a left outer join
(select * from HOWEBVAC1 where substring(PAYCODENAME,1,3) in ('110','112','115','130') and isnull(Thrudate,'') <> '') b
on a.fromdate = b.fromdate and a.personnum = b.personnum where
substring(a.PAYCODENAME,1,3) = '878' and isnull(a.Thrudate,'') <> '' and b.PAYCODENAME is NULL
group by a.PAYCODENAME,a.FROMDATE,a.THRUDATE,a.PERSONNUM,a.PERSONFULLNAME,a.BADGENUM,a.FISCYEAR
ALTER View HOWEBVAC1 as
SELECT PC1.NAME PAYCODENAME,
CASE
WHEN ADJAPPLYDTM <> APPLYDTM AND CM.COMMENTTEXT IS NULL THEN CONVERT(VARCHAR(10), ADJAPPLYDTM, 101 )
WHEN (CM.COMMENTTEXT IS NOT NULL AND substring(PC1.NAME,1,3) = '140')
THEN SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )
WHEN BB.FromDate IS NULL
THEN CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )
ELSE BB.FromDate
END AS FROMDATE,
CASE
WHEN BB.ThruDate IS NULL
THEN '' /* CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )*/
ELSE BB.ThruDate
END AS THRUDATE,
CAST(TI.DURATIONSECSQTY AS FLOAT (6,2)) / 3600 AS AMOUNT,
case
WHEN ADJAPPLYDTM <> APPLYDTM THEN CONVERT(VARCHAR(10), ADJAPPLYDTM, 101 )
ELSE
CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )
END AS PAYCODEDTE,
case
WHEN ADJAPPLYDTM <> APPLYDTM THEN CONVERT(VARCHAR(10), APPLYDTM, 101 )
ELSE
''
END AS EFFECTDTE,
CASE WHEN MGR.PERSONID IS NULL THEN 'N' ELSE 'Y' END AS APPR,
CASE
WHEN datediff(day,TI.EVENTDTM,ve.mgrsignoffthrudtm)>= 0 then 'Y'
ELSE 'N'
END AS SIGNEDOFF,
CASE datediff(day,wt.ADJAPPLYDTM,wt.APPLYDTM)
WHEN 0 THEN 'N'
ELSE 'Y'
END as HISTEDIT,
VE.PERSONNUM,
VE.FULLNM AS PERSONFULLNAME,
BA.BADGENUM,CM.COMMENTTEXT,
CASE
WHEN ADJAPPLYDTM <> APPLYDTM AND CM.COMMENTTEXT IS NULL THEN dbo.FN_FiscalYear(ADJAPPLYDTM)
WHEN (CM.COMMENTTEXT IS NOT NULL AND substring(PC1.NAME,1,3) = '140')
THEN
dbo.FN_FiscalYear(convert(datetime,SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )))
WHEN BB.FromDate IS NULL
THEN dbo.FN_FiscalYear(TI.EVENTDTM)
ELSE dbo.FN_FiscalYear(BB.FromDate)
END AS FISCYEAR
FROM TIMESHEETITEM TI JOIN
WTKEMPLOYEE WE ON (TI.EMPLOYEEID = WE.EMPLOYEEID) JOIN
PERSON VE ON (WE.PERSONID = VE.PERSONID) LEFT OUTER JOIN
BADGEASSIGN ba ON WE.PERSONID = ba.PERSONID AND getdate() BETWEEN ba.EFFECTIVEDTM AND ba.EXPIRATIONDTM LEFT OUTER JOIN
WFCTOTAL wt on TI.TIMESHEETITEMID = wt.TIMESHEETITEMID LEFT OUTER JOIN
--LABORACCT LA1 on LA1.LABORACCTID = TI.LABORACCTID LEFT OUTER JOIN
PAYCODE PC1 ON (TI.PAYCODEID = PC1.PAYCODEID) LEFT OUTER JOIN
DATASOURCE DS ON (TI.DATASOURCEID = DS.DATASOURCEID) LEFT OUTER JOIN
CLIENTCONTEXT CC ON (DS.CLIENTCONTEXTID = CC.CLIENTCONTEXTID) LEFT OUTER JOIN
TSCOMMENTMM TC ON (TI.TIMESHEETITEMID = TC.TIMESHEETITEMID) LEFT OUTER JOIN
COMMENTS CM ON (TC.COMMENTID = CM.COMMENTID) LEFT OUTER JOIN
(Select Min(FromDate) as FromDate,
Max(ThruDate)as ThruDate,
PERSONNUM, COMMENTTEXT,FISCYEAR
FROM dbo.HOWEBVAC6
group by personnum, commenttext,FISCYEAR ) BB
ON (CM.COMMENTTEXT = BB.COMMENTTEXT and
BB.FISCYEAR =
(CASE
WHEN ADJAPPLYDTM <> APPLYDTM AND CM.COMMENTTEXT IS NULL THEN dbo.FN_FiscalYear(ADJAPPLYDTM)
WHEN (CM.COMMENTTEXT IS NOT NULL AND substring(PC1.NAME,1,3) = '140')
THEN
dbo.FN_FiscalYear(convert(datetime,SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )))
WHEN BB.FromDate IS NULL
THEN dbo.FN_FiscalYear(TI.EVENTDTM)
ELSE dbo.FN_FiscalYear(BB.FromDate)
END) )
AND (VE.PERSONNUM = BB.PERSONNUM) LEFT OUTER JOIN
MGRAPPROVAL MGR ON (TI.EMPLOYEEID = MGR.PERSONID) AND
(TI.EVENTDTM = MGR.APPROVALDTM)
WHERE substring(PC1.NAME,1,3) in ('110','112','115','140','130','878')
--AND TI.EVENTDTM >= convert(varchar(4),getdate(),20)+ '/01/01'
AND TI.DELETEDSW = 0
ALTER View HOWEBVAC4 as
SELECT
Distinct PC1.NAME PAYCODENAME,
CASE
WHEN ADJAPPLYDTM <> WT.APPLYDTM AND CM.COMMENTTEXT IS NULL THEN CONVERT(VARCHAR(10), ADJAPPLYDTM, 101 )
WHEN (CM.COMMENTTEXT IS NOT NULL AND PC1.NAME = '140-Holiday Pay')
THEN SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )
WHEN BB.FromDate IS NULL
THEN CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )
ELSE BB.FromDate
END AS FROMDATE,
CASE
WHEN BB.ThruDate IS NULL
THEN ''
ELSE BB.ThruDate
END AS THRUDATE,
CAST(TI.DURATIONSECSQTY AS FLOAT (6,2)) / 3600 AS AMOUNT,
case
WHEN ADJAPPLYDTM <> WT.APPLYDTM THEN CONVERT(VARCHAR(10), ADJAPPLYDTM, 101 )
ELSE
CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )
END AS PAYCODEDTE,
case
WHEN ADJAPPLYDTM <> WT.APPLYDTM THEN CONVERT(VARCHAR(10), WT.APPLYDTM, 101 )
ELSE
''
END AS EFFECTDTE,
CASE WHEN MGR.PERSONID IS NULL THEN 'N' ELSE 'Y' END AS APPR,
CASE
WHEN datediff(day,TI.EVENTDTM,ve.mgrsignoffthrudtm)>= 0 then 'Y'
ELSE 'N'
END AS SIGNEDOFF,
CASE datediff(day,wt.ADJAPPLYDTM,wt.APPLYDTM)
WHEN 0 THEN 'N'
ELSE 'Y'
END as HISTEDIT,
VE.PERSONNUM,
VE.FULLNM AS PERSONFULLNAME,
BA.BADGENUM,CM.COMMENTTEXT,
CASE
WHEN ADJAPPLYDTM <> WT.APPLYDTM AND CM.COMMENTTEXT IS NULL THEN dbo.FN_FiscalYear(ADJAPPLYDTM)
WHEN (CM.COMMENTTEXT IS NOT NULL AND PC1.NAME = '140-Holiday Pay')
THEN
dbo.FN_FiscalYear(convert(datetime,SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )))
WHEN BB.FromDate IS NULL
THEN dbo.FN_FiscalYear(TI.EVENTDTM)
ELSE dbo.FN_FiscalYear(BB.FromDate)
END AS FISCYEAR,
LAC.LABORLEV3NM AS Department,
LAC2.LABORLEV3NM as LL3,
LAC2.LABORLEV4NM as LL4,
LAC2.LABORLEV5NM as LL5,
LAC2.LABORLEV6NM as LL6
--VP.HOMELABORLEVELNAME3 AS Department
FROM TIMESHEETITEM TI JOIN
WTKEMPLOYEE WE ON (TI.EMPLOYEEID = WE.EMPLOYEEID AND TI.DELETEDSW = 0) JOIN
PERSON VE ON (WE.PERSONID = VE.PERSONID) LEFT OUTER JOIN
BADGEASSIGN ba ON WE.PERSONID = ba.PERSONID AND getdate() BETWEEN ba.EFFECTIVEDTM AND ba.EXPIRATIONDTM LEFT OUTER JOIN
WFCTOTAL wt on TI.TIMESHEETITEMID = wt.TIMESHEETITEMID LEFT OUTER JOIN
--LABORACCT LA1 on LA1.LABORACCTID = TI.LABORACCTID LEFT OUTER JOIN
PAYCODE PC1 ON (TI.PAYCODEID = PC1.PAYCODEID) LEFT OUTER JOIN
DATASOURCE DS ON (TI.DATASOURCEID = DS.DATASOURCEID) LEFT OUTER JOIN
CLIENTCONTEXT CC ON (DS.CLIENTCONTEXTID = CC.CLIENTCONTEXTID) LEFT OUTER JOIN
TSCOMMENTMM TC ON (TI.TIMESHEETITEMID = TC.TIMESHEETITEMID) LEFT OUTER JOIN
COMMENTS CM ON (TC.COMMENTID = CM.COMMENTID) LEFT OUTER JOIN
vu_WFAREPTOTAL WFA on (WFA.employeeid = ti.employeeid and WFA.paycodeid = ti.paycodeid and wfa.applydtm = ti.eventdtm and wfa.hoursqty = (ti.durationsecsqty/3600))
LEFT OUTER JOIN WFAREPLABACCT WFAL on WFAL.WFAREPACTYSPANID = WFA.WFAREPACTYSPANID LEFT OUTER JOIN
LABORACCT LAB on LAB.LABORACCTID = WFAL.LABORACCTID LEFT OUTER JOIN
COMBHOMEACCT CH on (CH.employeeid = TI.EMPLOYEEID and getdate() BETWEEN CH.EFFECTIVEDTM and CH.EXPIRATIONDTM) LEFT OUTER JOIN
LABORACCT LAC on LAC.LABORACCTID = CH.LABORACCTID LEFT OUTER JOIN
COMBHOMEACCT CH2 on (CH2.employeeid = TI.EMPLOYEEID and (case
WHEN ADJAPPLYDTM <> WT.APPLYDTM THEN CONVERT(VARCHAR(10), ADJAPPLYDTM, 101 )
ELSE
CONVERT(VARCHAR(10), TI.EVENTDTM, 101 )
END ) BETWEEN CH2.EFFECTIVEDTM and CH2.EXPIRATIONDTM) LEFT OUTER JOIN
LABORACCT LAC2 on LAC2.LABORACCTID = CH2.LABORACCTID LEFT OUTER JOIN
--VP_PERSON VP on VP.personid = TI.EMPLOYEEID LEFT OUTER JOIN
(Select Min(FromDate) as FromDate,
Max(ThruDate)as ThruDate,
PERSONNUM, COMMENTTEXT,FISCYEAR
FROM dbo.HOWEBVAC6
group by personnum, commenttext,FISCYEAR ) BB
ON (CM.COMMENTTEXT = BB.COMMENTTEXT and
BB.FISCYEAR =
(CASE
WHEN ADJAPPLYDTM <> WT.APPLYDTM AND CM.COMMENTTEXT IS NULL THEN dbo.FN_FiscalYear(ADJAPPLYDTM)
WHEN (CM.COMMENTTEXT IS NOT NULL AND PC1.NAME = '140-Holiday Pay')
THEN
dbo.FN_FiscalYear(convert(datetime,SUBSTRING(CM.COMMENTTEXT,1,2) + '/' + SUBSTRING(CM.COMMENTTEXT,4,2) + '/' + CONVERT(VARCHAR(4), TI.EVENTDTM, 20 )))
WHEN BB.FromDate IS NULL
THEN dbo.FN_FiscalYear(TI.EVENTDTM)
ELSE dbo.FN_FiscalYear(BB.FromDate)
END) )
AND (VE.PERSONNUM = BB.PERSONNUM) LEFT OUTER JOIN
MGRAPPROVAL MGR ON (TI.EMPLOYEEID = MGR.PERSONID) AND
(TI.EVENTDTM = MGR.APPROVALDTM)
WHERE PC1.NAME in ('110-VAC Taken','112-VAC Prepay','115-VAC Untaken','130-VAC Terminat','140-Holiday Pay','878-VAC')
--AND TI.EVENTDTM >= convert(varchar(4),getdate(),20)+ '/01/01'
AND TI.DELETEDSW = 0 --and TI.LABORACCTID is not null
March 1, 2007 at 2:03 pm
How tied are you to these views? I know you need to retain the logic. But are you at a point where these views? You said that you were asked to improve the performance of these views. Did you say that because these views absolutely must stay (for whatever reason)?
How many different queries do you run against these views? Can we break out some use cases?
SQL guy and Houston Magician
March 1, 2007 at 3:53 pm
A consultant for the software we purchased came to our site and developed the first version of these. Our software developer on this project then made them more efficient.
Lets assume we are tied to this logic. If this were a one user system there would not be a problem and I could proceed.
Mike
March 1, 2007 at 11:06 pm
How about this approach? Assume this is a stored proc that will simulate the output of View1, which itself references View2, which itself references View3
CREATE PROCEDURE CallingProcForView1
AS
-----------
if not exists (select * from sysobjects where name = '#tmp1' and xtype='u')
create table #tmp1 -- simulates the result set of View1
( Field1 INT NOT NULL
, Field2 INT etc )
if not exists (select * from sysobjects where name = '#tmp2' and xtype='u')
create table #tmp2 -- simulates the result set of View2
( Field1 INT NOT NULL
, Field2 INT etc )
if not exists (select * from sysobjects where name = '#tmp3' and xtype='u')
create table #tmp3 -- simulates the result set of View3
( Field1 INT NOT NULL
, Field2 INT etc )
-----------
EXEC ValuesForView3
EXEC ValuesForView2
EXEC ValuesForView1
-----------
SELECT * FROM #tmp1
------------------------------------------------------------------------
CREATE PROCEDURE ValuesForView3
AS
INSERT INTO #tmp3 -- this temp table is created ALWAYS in the calling proc!
( Field1, Field2, etc)
SELECT blah, blah, etc
FROM {from statement for View3}
------------------------------------------------------------------------
CREATE PROCEDURE ValuesForView2
AS
INSERT INTO #tmp2 -- this temp table is created ALWAYS in the calling proc!
( Field1, Field2, etc)
SELECT blah, blah, etc
FROM {from statement for View2, rewritten to change the reference to View3 with a reference to #tmp3}
------------------------------------------------------------------------
CREATE PROCEDURE ValuesForView1
AS
INSERT INTO #tmp3 -- this temp table is created ALWAYS in the calling proc!
( Field1, Field2, etc)
SELECT blah, blah, etc
FROM {from statement for View1, rewritten to change the reference to View2 with a reference to #tmp2}
-----------
You would need to create calling procedures for each View, and procs for populating the views for each view. To get the data, always go through the calling proc, which creates the required temp tables for all the cascading stored procs it references. When the calling proc ends, it will then automatically drop all these instances of the temp tables that have been used in the calling proc, and all the child procs.
This is the approach I am taking to solve a similar issue with a client, though the views that I have to deal were created by a different consultant, one who didn't have access to the same mind altering drugs as the one you dealt with.
March 2, 2007 at 6:44 am
Grasshopper
Thank you. I think this is the approach I will have to use. Requiress a bit more work on my part creating the table definitions as opposed to SELECT * INTO but is probably the best way to do it. A similar suggestion was made on another forum as well which confirms this ...
Mike
March 2, 2007 at 4:35 pm
Hi Mike,
I noticed a typo in a bit of the code - see correction below
CREATE PROCEDURE ValuesForView1
AS
INSERT INTO #tmp1 -- this temp table is created ALWAYS in the calling proc!
( Field1, Field2, etc)
SELECT blah, blah, etc
FROM {from statement for View1, rewritten to change the reference to View2 with a reference to #tmp2}
Also, I'm curious about more info on this approach, as I dreamed it up and tried it with some test SPs on the dev database, but haven't implemented it yet. Could you give me the link to the other forum where you found something similar suggested?
thanks, Malcolm
Viewing 8 posts - 1 through 7 (of 7 total)
You must be logged in to reply to this topic. Login to reply