1
0

usp_AdaptiveIndexDefrag.sql 177 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152
  1. -- If you are using AdaptiveIndexDefrag together with the maintenance plans in http://blogs.msdn.com/b/blogdoezequiel/archive/2012/09/18/about-maintenance-plans-grooming-sql-server.aspx
  2. -- please note that the job that runs AdaptiveIndexDefrag is expecting msdb. As such, change the database context accordingly.
  3. USE msdb
  4. GO
  5. SET NOCOUNT ON;
  6. DECLARE @deploymode bit
  7. SET @deploymode = 0 /* 0 = Upgrade from immediately previous version, preserving all historic data;
  8. 1 = Rewrite all objects, disregarding historic data */
  9. /* Scroll down to line 429 to the see notes, disclaimers, and licensing information */
  10. RAISERROR('Droping existing objects', 0, 42) WITH NOWAIT;
  11. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_CurrentExecStats')
  12. DROP VIEW vw_CurrentExecStats
  13. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_ErrLst30Days')
  14. DROP VIEW vw_ErrLst30Days
  15. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_LastRun_Log')
  16. DROP VIEW vw_LastRun_Log
  17. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_ErrLst24Hrs')
  18. DROP VIEW vw_ErrLst24Hrs
  19. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgSamplingLst30Days')
  20. DROP VIEW vw_AvgSamplingLst30Days
  21. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgTimeLst30Days ')
  22. DROP VIEW vw_AvgTimeLst30Days
  23. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgFragLst30Days')
  24. DROP VIEW vw_AvgFragLst30Days
  25. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgLargestLst30Days')
  26. DROP VIEW vw_AvgLargestLst30Days
  27. IF EXISTS(SELECT [object_id] FROM sys.views WHERE [name] = 'vw_AvgMostUsedLst30Days')
  28. DROP VIEW vw_AvgMostUsedLst30Days
  29. IF @deploymode = 0
  30. BEGIN
  31. RAISERROR('Preserving historic data', 0, 42) WITH NOWAIT;
  32. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
  33. BEGIN
  34. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_log', 'tbl_AdaptiveIndexDefrag_log_old';
  35. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_log_old.PK_AdaptiveIndexDefrag_log', N'PK_AdaptiveIndexDefrag_log_old', N'INDEX';
  36. END;
  37. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
  38. BEGIN
  39. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Stats_log', 'tbl_AdaptiveIndexDefrag_Stats_log_old';
  40. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Stats_log_old.PK_AdaptiveIndexDefrag_Stats_log', N'PK_AdaptiveIndexDefrag_Stats_log_old', N'INDEX';
  41. END;
  42. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
  43. BEGIN
  44. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Exceptions', 'tbl_AdaptiveIndexDefrag_Exceptions_old';
  45. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Exceptions_old.PK_AdaptiveIndexDefrag_Exceptions', N'PK_AdaptiveIndexDefrag_Exceptions_old', N'INDEX';
  46. END;
  47. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
  48. BEGIN
  49. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Working', 'tbl_AdaptiveIndexDefrag_Working_old';
  50. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Working_old.PK_AdaptiveIndexDefrag_Working', N'PK_AdaptiveIndexDefrag_Working_old', N'INDEX';
  51. END;
  52. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
  53. BEGIN
  54. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_Stats_Working', 'tbl_AdaptiveIndexDefrag_Stats_Working_old';
  55. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_Stats_Working_old.PK_AdaptiveIndexDefrag_Stats_Working', N'PK_AdaptiveIndexDefrag_Stats_Working_old', N'INDEX';
  56. END;
  57. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus') AND NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
  58. BEGIN
  59. EXEC sp_rename 'tbl_AdaptiveIndexDefrag_IxDisableStatus', 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old';
  60. EXEC sp_rename N'tbl_AdaptiveIndexDefrag_IxDisableStatus_old.PK_AdaptiveIndexDefrag_IxDisableStatus', N'PK_AdaptiveIndexDefrag_IxDisableStatus_old', N'INDEX';
  61. END;
  62. END
  63. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log')
  64. DROP TABLE tbl_AdaptiveIndexDefrag_log;
  65. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log')
  66. DROP TABLE tbl_AdaptiveIndexDefrag_Stats_log;
  67. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions')
  68. DROP TABLE tbl_AdaptiveIndexDefrag_Exceptions;
  69. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working')
  70. DROP TABLE tbl_AdaptiveIndexDefrag_Working;
  71. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working')
  72. DROP TABLE tbl_AdaptiveIndexDefrag_Stats_Working;
  73. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus')
  74. DROP TABLE tbl_AdaptiveIndexDefrag_IxDisableStatus;
  75. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_PurgeLogs'), N'IsProcedure') = 1
  76. DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_PurgeLogs;
  77. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_Exceptions'), N'IsProcedure') = 1
  78. DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_Exceptions;
  79. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_Exclusions'), N'IsProcedure') = 1
  80. DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_Exclusions;
  81. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_CurrentExecStats'), N'IsProcedure') = 1
  82. DROP PROCEDURE dbo.usp_CurrentExecStats;
  83. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag_CurrentExecStats'), N'IsProcedure') = 1
  84. DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag_CurrentExecStats;
  85. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log')
  86. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_log
  87. (indexDefrag_id int identity(1,1) NOT NULL
  88. , dbID int NOT NULL
  89. , dbName NVARCHAR(128) NOT NULL
  90. , objectID int NOT NULL
  91. , objectName NVARCHAR(256) NULL
  92. , indexID int NOT NULL
  93. , indexName NVARCHAR(256) NULL
  94. , partitionNumber smallint
  95. , fragmentation float NOT NULL
  96. , page_count bigint NOT NULL
  97. , range_scan_count bigint NULL
  98. , fill_factor int NULL
  99. , dateTimeStart DATETIME NOT NULL
  100. , dateTimeEnd DATETIME NULL
  101. , durationSeconds int NULL
  102. , sqlStatement VARCHAR(4000) NULL
  103. , errorMessage VARCHAR(1000) NULL
  104. CONSTRAINT PK_AdaptiveIndexDefrag_log PRIMARY KEY CLUSTERED (indexDefrag_id));
  105. CREATE INDEX IX_tbl_AdaptiveIndexDefrag_log ON [dbo].[tbl_AdaptiveIndexDefrag_log] ([dbID], [objectID], [indexName], [dateTimeEnd]);
  106. RAISERROR('tbl_AdaptiveIndexDefrag_log table created', 0, 42) WITH NOWAIT;
  107. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions')
  108. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Exceptions
  109. (dbID int NOT NULL
  110. , objectID int NOT NULL
  111. , indexID int NOT NULL
  112. , dbName NVARCHAR(128) NOT NULL
  113. , objectName NVARCHAR(256) NOT NULL
  114. , indexName NVARCHAR(256) NOT NULL
  115. , exclusionMask int NOT NULL
  116. /* Same as in msdb.dbo.sysschedules:
  117. 1=Sunday, 2=Monday, 4=Tuesday, 8=Wednesday, 16=Thursday, 32=Friday, 64=Saturday, 0=AllWeek, -1=Never
  118. For multiple days, sum the corresponding values*/
  119. CONSTRAINT PK_AdaptiveIndexDefrag_Exceptions PRIMARY KEY CLUSTERED (dbID, objectID, indexID));
  120. RAISERROR('tbl_AdaptiveIndexDefrag_Exceptions table created', 0, 42) WITH NOWAIT;
  121. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working')
  122. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Working
  123. (dbID int
  124. , objectID int
  125. , indexID int
  126. , partitionNumber smallint
  127. , dbName NVARCHAR(128)
  128. , schemaName NVARCHAR(128) NULL
  129. , objectName NVARCHAR(256) NULL
  130. , indexName NVARCHAR(256) NULL
  131. , fragmentation float
  132. , page_count int
  133. , is_primary_key bit
  134. , fill_factor int
  135. , is_disabled bit
  136. , is_padded bit
  137. , is_hypothetical bit
  138. , has_filter bit
  139. , allow_page_locks bit
  140. , range_scan_count bigint NULL
  141. , record_count bigint
  142. , [type] tinyint -- 0 = Heap; 1 = Clustered; 2 = Nonclustered; 3 = XML; 4 = Spatial; 5 = Clustered columnstore; 6 = Nonclustered columnstore; 7 = Nonclustered hash
  143. , scanDate DATETIME
  144. , defragDate DATETIME NULL
  145. , printStatus bit DEFAULT(0) -- Used for loop control when printing the SQL commands.
  146. , exclusionMask int DEFAULT(0)
  147. CONSTRAINT PK_AdaptiveIndexDefrag_Working PRIMARY KEY CLUSTERED(dbID, objectID, indexID, partitionNumber));
  148. RAISERROR('tbl_AdaptiveIndexDefrag_Working table created', 0, 42) WITH NOWAIT;
  149. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working')
  150. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  151. (dbID int
  152. , objectID int
  153. , statsID int
  154. , partitionNumber smallint
  155. , dbName NVARCHAR(128)
  156. , schemaName NVARCHAR(128) NULL
  157. , objectName NVARCHAR(256) NULL
  158. , statsName NVARCHAR(256)
  159. , [no_recompute] bit
  160. , [is_incremental] bit
  161. , scanDate DATETIME
  162. , updateDate DATETIME NULL
  163. , printStatus bit DEFAULT(0) -- Used for loop control when printing the SQL commands.
  164. CONSTRAINT PK_AdaptiveIndexDefrag_Stats_Working PRIMARY KEY CLUSTERED(dbID, objectID, statsID, partitionNumber));
  165. RAISERROR('tbl_AdaptiveIndexDefrag_Stats_Working table created', 0, 42) WITH NOWAIT;
  166. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log')
  167. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_Stats_log
  168. (statsUpdate_id int identity(1,1) NOT NULL
  169. , dbID int NOT NULL
  170. , dbName NVARCHAR(128) NULL
  171. , objectID int NOT NULL
  172. , objectName NVARCHAR(256) NULL
  173. , statsID int NOT NULL
  174. , statsName NVARCHAR(256) NULL
  175. , partitionNumber smallint
  176. , [rows] bigint
  177. , rows_sampled bigint
  178. , modification_counter bigint
  179. , [no_recompute] bit
  180. , dateTimeStart DATETIME NOT NULL
  181. , dateTimeEnd DATETIME NULL
  182. , durationSeconds int NULL
  183. , sqlStatement VARCHAR(4000) NULL
  184. , errorMessage VARCHAR(1000) NULL
  185. CONSTRAINT PK_AdaptiveIndexDefrag_Stats_log PRIMARY KEY CLUSTERED (statsUpdate_id));
  186. CREATE INDEX IX_tbl_AdaptiveIndexDefrag_Stats_log ON [dbo].[tbl_AdaptiveIndexDefrag_Stats_log] ([dbID], [objectID], [statsName], [dateTimeEnd]);
  187. RAISERROR('tbl_AdaptiveIndexDefrag_Stats_log table created', 0, 42) WITH NOWAIT;
  188. IF NOT EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus')
  189. CREATE TABLE dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus
  190. (disable_id int identity(1,1) NOT NULL
  191. , dbID int NOT NULL
  192. , objectID int NOT NULL
  193. , indexID int NOT NULL
  194. , [is_disabled] bit
  195. , dateTimeChange DATETIME NOT NULL
  196. CONSTRAINT PK_AdaptiveIndexDefrag_IxDisableStatus PRIMARY KEY CLUSTERED (disable_id));
  197. RAISERROR('tbl_AdaptiveIndexDefrag_IxDisableStatus table created', 0, 42) WITH NOWAIT;
  198. IF @deploymode = 0
  199. BEGIN
  200. RAISERROR('Copying old data...', 0, 42) WITH NOWAIT;
  201. BEGIN TRY
  202. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
  203. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_log ([dbID],[dbName],[objectID],[objectName]
  204. ,[indexID],[indexName],[partitionNumber],[fragmentation],[page_count]
  205. ,[range_scan_count],[fill_factor],[dateTimeStart],[dateTimeEnd]
  206. ,[durationSeconds],[sqlStatement],[errorMessage])
  207. SELECT [dbID],[dbName],[objectID],[objectName],[indexID]
  208. ,[indexName],[partitionNumber],[fragmentation],[page_count]
  209. ,[range_scan_count],[fill_factor],[dateTimeStart],[dateTimeEnd]
  210. ,[durationSeconds],[sqlStatement],[errorMessage]
  211. FROM dbo.tbl_AdaptiveIndexDefrag_log_old;
  212. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
  213. BEGIN
  214. IF (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'partitionNumber' OR sc.[name] = 'rows' OR sc.[name] = 'rows_sampled' OR sc.[name] = 'modification_counter') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 4
  215. BEGIN
  216. EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
  217. SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
  218. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
  219. END
  220. ELSE IF (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE sc.[name] = 'partitionNumber' AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 1
  221. AND (SELECT COUNT(sc.column_id) FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'rows' OR sc.[name] = 'rows_sampled' OR sc.[name] = 'modification_counter') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old') = 0
  222. BEGIN
  223. EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
  224. SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],-1,-1,-1,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
  225. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
  226. END
  227. ELSE
  228. BEGIN
  229. EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log ([dbID],[dbName],[objectID],[objectName],[statsID],[statsName],[partitionNumber],[rows],rows_sampled,modification_counter,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage])
  230. SELECT [dbID],[dbName],[objectID],[objectName],[statsID],[statsName],1,-1,-1,-1,[no_recompute],[dateTimeStart],[dateTimeEnd],[durationSeconds],[sqlStatement],[errorMessage]
  231. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old;')
  232. END
  233. END
  234. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
  235. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Exceptions ([dbID],[objectID],[indexID],[dbName]
  236. ,[objectName],[indexName],[exclusionMask])
  237. SELECT [dbID],[objectID],[indexID],[dbName]
  238. ,[objectName],[indexName],[exclusionMask]
  239. FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions_old;
  240. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
  241. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working ([dbID],[objectID],[indexID],[partitionNumber]
  242. ,[dbName],[schemaName],[objectName],[indexName],[fragmentation]
  243. ,[page_count],[fill_factor],[is_disabled],[is_padded],[is_hypothetical]
  244. ,[has_filter],[allow_page_locks],[range_scan_count],[record_count]
  245. ,[type],[scanDate],[defragDate],[printStatus],[exclusionMask])
  246. SELECT [dbID],[objectID],[indexID],[partitionNumber],[dbName]
  247. ,[schemaName],[objectName],[indexName],[fragmentation],[page_count]
  248. ,[fill_factor],[is_disabled],[is_padded],[is_hypothetical],[has_filter]
  249. ,[allow_page_locks],[range_scan_count],[record_count],[type],[scanDate]
  250. ,[defragDate],[printStatus],[exclusionMask]
  251. FROM dbo.tbl_AdaptiveIndexDefrag_Working_old;
  252. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
  253. BEGIN
  254. IF EXISTS (SELECT sc.column_id FROM sys.tables st INNER JOIN sys.columns sc ON st.[object_id] = sc.[object_id] WHERE (sc.[name] = 'partitionNumber' OR sc.[name] = 'is_incremental') AND st.[name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
  255. BEGIN
  256. EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_Working ([dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus])
  257. SELECT [dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus]
  258. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old;')
  259. END
  260. ELSE
  261. BEGIN
  262. EXEC ('INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_Working ([dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],[partitionNumber],[no_recompute],[is_incremental],[scanDate],[updateDate],[printStatus])
  263. SELECT [dbID],[objectID],[statsID],[dbName],[schemaName],[objectName],[statsName],1,[no_recompute],0,[scanDate],[updateDate],[printStatus]
  264. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old;')
  265. END
  266. END
  267. IF EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus') AND EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
  268. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus ([dbID],[objectID],[indexID],[is_disabled],dateTimeChange)
  269. SELECT [dbID],[objectID],[indexID],[is_disabled],dateTimeChange
  270. FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus_old;
  271. END TRY
  272. BEGIN CATCH
  273. RAISERROR('Could not copy old data back. Check for any previous errors.', 15, 42) WITH NOWAIT;
  274. RETURN
  275. END CATCH
  276. RAISERROR('Done copying old data...', 0, 42) WITH NOWAIT;
  277. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
  278. BEGIN
  279. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_log_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_log)
  280. DROP TABLE tbl_AdaptiveIndexDefrag_log_old
  281. END;
  282. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
  283. BEGIN
  284. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log)
  285. DROP TABLE tbl_AdaptiveIndexDefrag_Stats_log_old
  286. END;
  287. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
  288. BEGIN
  289. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions)
  290. DROP TABLE tbl_AdaptiveIndexDefrag_Exceptions_old
  291. END;
  292. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
  293. BEGIN
  294. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working)
  295. DROP TABLE tbl_AdaptiveIndexDefrag_Working_old
  296. END;
  297. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
  298. BEGIN
  299. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working)
  300. DROP TABLE tbl_AdaptiveIndexDefrag_Stats_Working_old
  301. END;
  302. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
  303. BEGIN
  304. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus_old) = (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus)
  305. DROP TABLE tbl_AdaptiveIndexDefrag_IxDisableStatus_old
  306. END;
  307. IF EXISTS (SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_Working_old')
  308. OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_log_old')
  309. OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Stats_log_old')
  310. OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Exceptions_old')
  311. OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_Working_old')
  312. OR EXISTS(SELECT [object_id] FROM sys.tables WHERE [name] = 'tbl_AdaptiveIndexDefrag_IxDisableStatus_old')
  313. BEGIN
  314. RAISERROR('Data mismatch. Keeping some or all old tables as <tablename_old>.', 0, 42) WITH NOWAIT;
  315. END
  316. ELSE
  317. BEGIN
  318. RAISERROR('Removed old tables...', 0, 42) WITH NOWAIT;
  319. END
  320. END;
  321. GO
  322. ------------------------------------------------------------------------------------------------------------------------------
  323. IF OBJECTPROPERTY(OBJECT_ID('dbo.usp_AdaptiveIndexDefrag'), N'IsProcedure') = 1
  324. BEGIN
  325. DROP PROCEDURE dbo.usp_AdaptiveIndexDefrag;
  326. PRINT 'Procedure usp_AdaptiveIndexDefrag dropped';
  327. END;
  328. GO
  329. CREATE PROCEDURE dbo.usp_AdaptiveIndexDefrag
  330. @Exec_Print bit = 1
  331. /* 1 = execute commands; 0 = print commands only */
  332. , @printCmds bit = 0
  333. /* 1 = print commands; 0 = do not print commands */
  334. , @outputResults bit = 0
  335. /* 1 = output fragmentation information;
  336. 0 = do not output */
  337. , @debugMode bit = 0
  338. /* display some useful comments to help determine if/where issues occur
  339. 1 = display debug comments;
  340. 0 = do not display debug comments*/
  341. , @timeLimit int = 480 /* defaults to 8 hours */
  342. /* Optional time limitation; expressed in minutes */
  343. , @dbScope NVARCHAR(256) = NULL
  344. /* Option to specify a database name; NULL will return all */
  345. , @tblName NVARCHAR(1000) = NULL -- schema.table_name
  346. /* Option to specify a table name; NULL will return all */
  347. , @defragOrderColumn NVARCHAR(20) = 'range_scan_count'
  348. /* Valid options are: range_scan_count, fragmentation, page_count */
  349. , @defragSortOrder NVARCHAR(4) = 'DESC'
  350. /* Valid options are: ASC, DESC */
  351. , @forceRescan bit = 0
  352. /* Whether to force a rescan of indexes into the tbl_AdaptiveIndexDefrag_Working table or not;
  353. 1 = force, 0 = use existing scan when available, used to continue where previous run left off */
  354. , @defragDelay CHAR(8) = '00:00:05'
  355. /* time to wait between defrag commands */
  356. , @ixtypeOption bit = NULL
  357. /* NULL = all indexes will be defragmented; 1 = only Clustered indexes will be defragmented; 0 = only Non-Clustered indexes will be defragmented (includes XML and Spatial); */
  358. , @minFragmentation float = 5.0
  359. /* in percent, will not defrag if fragmentation is less than specified */
  360. , @rebuildThreshold float = 30.0
  361. /* in percent, greater than @rebuildThreshold will result in rebuild instead of reorg */
  362. , @rebuildThreshold_cs float = 10.0
  363. /* in percent, greater than @rebuildThreshold_cs will result in rebuild the columnstore index */
  364. , @minPageCount int = 8
  365. /* Recommended is defrag when index is at least > 1 extent (8 pages) */
  366. , @maxPageCount int = NULL
  367. /* NULL = no limit */
  368. , @fillfactor bit = 1
  369. /* 1 = original from when the index was created or last defraged;
  370. 0 = default fillfactor */
  371. , @scanMode VARCHAR(10) = N'LIMITED'
  372. /* Options are LIMITED, SAMPLED, and DETAILED */
  373. , @onlineRebuild bit = 0
  374. /* 1 = online rebuild; 0 = offline rebuild; only in Enterprise Edition */
  375. , @sortInTempDB bit = 0
  376. /* 1 = perform sort operation in TempDB; 0 = perform sort operation in the indexes database */
  377. , @maxDopRestriction tinyint = NULL
  378. /* Option to restrict the number of processors for the operation; only in Enterprise Edition */
  379. , @updateStats bit = 1
  380. /* 1 = updates stats when reorganizing; 0 = does not update stats when reorganizing */
  381. , @updateStatsWhere bit = 0
  382. /* 1 = updates only index related stats; 0 = updates all stats in table */
  383. , @statsSample NCHAR(8) = NULL
  384. /* Valid options are: NULL, FULLSCAN, and RESAMPLE */
  385. , @statsThreshold float = NULL
  386. /* Valid options are: NULL to use default stats sample method (same as TF2371), float number greater or equal to 0.001 and less than 100 to use custom stats sample */
  387. , @statsMinRows bigint = NULL
  388. /* Valid options are: NULL , integer number that sets the min number of rows a table has to have to be considered for @statsThreshold use */
  389. , @ix_statsnorecompute bit = 0
  390. /* 1 = STATISTICS_NORECOMPUTE on; 0 = default which is with STATISTICS_NORECOMPUTE off */
  391. , @statsIncremental bit = NULL
  392. /* NULL = Keep server setting; 1 = Enable auto create statistics with Incremental; 0 = Disable auto create statistics with Incremental */
  393. , @dealMaxPartition bit = 0
  394. /* 0 = only right-most partition; 1 = exclude right-most populated partition; NULL = do not exclude; see notes for caveats; only in Enterprise Edition */
  395. , @dealLOB bit = 0
  396. /* 0 = compact LOBs when reorganizing (default behavior); 1 = does not compact LOBs when reorganizing */
  397. , @ignoreDropObj bit = 0
  398. /* 0 = includes errors about objects that have been dropped since the defrag cycle began (default behavior);
  399. 1 = for error reporting purposes, ignores the fact that objects have been dropped since the defrag cycle began */
  400. , @disableNCIX bit = 0
  401. /* 0 = does NOT disable non-clustered indexes prior to a rebuild;
  402. 1 = disables non-clustered indexes prior to a rebuild, if the database is not being replicated (space saving feature) */
  403. , @offlinelocktimeout int = -1
  404. /* -1 = (default) indicates no time-out period; Any other positive integer sets the number of milliseconds that will pass before Microsoft SQL Server returns a locking error */
  405. , @onlinelocktimeout int = 5
  406. /* 5 = (default) indicates a time-out period for locks to wait at low priority, expressed in minutes; this is valid from SQL Server 2014 onwards */
  407. , @abortAfterwait bit = 1
  408. /* NULL = (default) After lock timeout occurs, continue waiting for the lock with normal (regular) priority;
  409. 0 = Kill all user transactions that block the online index rebuild DDL operation so that the operation can continue.
  410. 1 = Exit the online index rebuild DDL operation currently being executed without taking any action.*/
  411. , @dealROWG bit = 0
  412. /* 0 = (default) compress closed rowgroups on columnstore.
  413. 1 = compress all rowgroups on columnstore, and not just closed ones.*/
  414. , @getBlobfrag bit = 0
  415. /* 0 = (default) exclude blobs from fragmentation scan.
  416. 1 = include blobs and off-row data when scanning for fragmentation.*/
  417. AS
  418. /*
  419. usp_AdaptiveIndexDefrag.sql - [email protected] (http://blogs.msdn.com/b/blogdoezequiel/)
  420. Inspired by Michelle Ufford (http://sqlfool.com)
  421. PURPOSE: Intelligent defrag on one or more indexes for one or more databases.
  422. DISCLAIMER:
  423. This code is not supported under any Microsoft standard support program or service.
  424. This code and information are provided "AS IS" without warranty of any kind, either expressed or implied.
  425. The entire risk arising out of the use or performance of the script and documentation remains with you.
  426. Furthermore, Microsoft, the author or "Blog do Ezequiel" team shall not be liable for any damages you may sustain by using this information, whether direct,
  427. indirect, special, incidental or consequential, including, without limitation, damages for loss of business profits, business interruption, loss of business information
  428. or other pecuniary loss even if it has been advised of the possibility of such damages.
  429. Read all the implementation and usage notes thoroughly.
  430. CHANGE LOG:
  431. v1 - 08-02-2011 - Initial release
  432. v1.1 - 15-02-2011 - Added support for maintaining current index padding options;
  433. Added logic for Exception of hypothetical objects;
  434. Deal with LOB compaction when reorganizing;
  435. Corrected bug with update stats kicking in when not supposed to;
  436. Corrected options not compatible with partitioned indexes;
  437. v1.2 - 10-03-2011 - Increased control over new or changed database handling;
  438. v1.2.1 - 22-03-2011 - Corrected method of finding available processors;
  439. v1.3 - 21-06-2011 - Added more options to act upon statistics (IX related or Table-wide);
  440. Added finer thresholds for updates on table-wide statistics when reorganizing (when SAMPLED or DETAILED scanMode is selected);
  441. Added option for no_recompute on index REBUILD;
  442. Added restrictions for spatial and XML indexes;
  443. Always rebuild filtered indexes;
  444. If found, output list of disabled or hypothetical indexes so that you can act on them;
  445. Added range scan count to logging table for comparison;
  446. Added update index related stats (with defaults) before rebuild operations. This provides better cardinality estimation, and thus a more time-efficient operation when rebuilding;
  447. Bug fix in Reorganize statements.
  448. Bug fix in one Rescanning condition.
  449. v1.3.1 - 28-06-2011 - Corrected issue with commands running on multiple partitions.
  450. Changed behaviour of update statistics when tables have multiple partitions.
  451. v1.3.2 - 01-07-2011 - Changed objects named %Exclusions to %Exceptions. When re-deploying, existing %Exclusions table will be renamed and not recreated.
  452. Added procedure to check current batch execution progress (usp_CurrentExecStats)
  453. v1.3.3 - 08-07-2011 - Corrected issue where explicit change in database scope parameter did not trigger rescan under certain conditions.
  454. Corrected statistics update thresholds.
  455. v1.3.4 - 22-07-2011 - Bug fix in indexes information regarding the sql version.
  456. v1.3.5 - 15-11-2011 - Bug fix in logging showing as NULL on some issued commands.
  457. Optimizations on support SP usp_AdaptiveIndexDefrag_Exceptions.
  458. v1.3.6 - 17-02-2012 - Allow larger object names in tables and indexes.
  459. v1.3.7 - 27-02-2012 - Enhanced error reporting view to incorporate stats updates;
  460. Bug fix when certain index options were chosen together.
  461. v1.3.8 - 28-02-2012 - Corrected view that reports last run;
  462. Added upgrade mode.
  463. v1.3.9 - 12-03-2012 - Fixed upgrade mode in case old data cannot be copied back.
  464. v1.4.0 - 12-04-2012 - Fixed issue with collation sensitive servers.
  465. v1.4.1 - 17-05-2012 - Fixed issue on support SP usp_AdaptiveIndexDefrag_Exceptions.
  466. v1.4.2 - 29-05-2012 - Fixed issue on support SP usp_AdaptiveIndexDefrag_CurrentExecStats,
  467. Fixed issue with large object IDs.
  468. v1.4.3 - 29-08-2012 - Fixed issue with upgrade mode data retention,
  469. Fixed issue with format dependent conversions.
  470. v1.4.4 - 10-09-2012 - Fixed issue where running the procedure to print commands only, previous execution errors would still be reported.
  471. v1.4.5 - 12-10-2012 - Added support for ignoring errors regarding database objects that were dropped since the defrag cycle began;
  472. Added support for disabling indexes before rebuilding (space saving feature) - see notes below on parameter @disableNCIX.
  473. v1.4.6 - 23-01-2013 - Added hard limit of 4 for MaxDOP setting;
  474. Changed default for statistics update to updates all stats in table, as opposed to just index related stats;
  475. Fixed issue on support SP usp_AdaptiveIndexDefrag_CurrentExecStats reporting incorrect number of already defraged indexes;
  476. Fixed null elimination message with vw_LastRun_Log;
  477. Incremented debug mode output;
  478. Redesigned table wide statistics update (updateStatsWhere parameter);
  479. Fixed issue with upgrade mode leaving old tables behind.
  480. v1.4.7 - 28-01-2013 - Fixed issue with exceptions not working with on some days i.e, on a day that should not be doing anything, it did;
  481. Tuned online rebuild options;
  482. Redesigned support SP usp_AdaptiveIndexDefrag_Exceptions.
  483. v1.4.9 - 11-04-2013 - Added support for Enterprise Core Edition;
  484. Added support for Always On secondary replicas;
  485. Changed maxdop hard limit to 8;
  486. Added support for sys.dm_db_stats_properties in statistics update, if on SQL 2008R2 SP2 or SQL 2012 SP1 or higher.
  487. v1.5.0 - 25-04-2013 - Fixed issue with online rebuilds;
  488. Fixed issue with commands not being printed when choosing @ExecPrint = 0.
  489. v1.5.1 - 01-05-2013 - Fixed issue with page locking off and trying index reorganize - should always rebuild;
  490. Fixed issue with specific db scope and Always On replica checking;
  491. Enhanced stats lookup for specific table scope;
  492. Fixed issue where disable index would also do extra update on previous index related statistic;
  493. Added support for online rebuild with LOBs in SQL Server 2012.
  494. v1.5.1.1- 02-05-2013 - Fixed MaxDOP issue introduced in v1.4.9;
  495. Fixed issue with DETAILED scan mode;
  496. Fixed issue with extended indexes not being picked up.
  497. v1.5.1.2- 05-05-2013 - Fixed issue with print command while executing introduced in v1.5.1;
  498. Fixed issue where a statistics update error would show in the log associated with an XML or Spatial index.
  499. v1.5.1.4- 10-05-2013 - Fixed issue with statistics update when there is no work to be done, introduced in v1.5.1.
  500. v1.5.2 - 17-06-2013 - Added option for lock timeout;
  501. Set deadlock priority to lowest possible;
  502. Simulate TF 2371 behavior on update statistics threshold;
  503. Fixed issue with @updateStatsWhere = 1 where not all non-index related statistics were updated.
  504. v1.5.3 - 02-07-2013 - Fixed issue with updating statistics and XML indexes;
  505. Fixed issue with log data being partially overwritten;
  506. Fixed issue where using @fillfactor to reset fill factor to default would not actually reset.
  507. v1.5.3.1- 08-07-2013 - Fixed issue where using @fillfactor to reset fill factor to default would output command error.
  508. v1.5.4 - 12-09-2013 - Changed system database exclusion choices;
  509. Fixed fill factor information not getting logged (thanks go to Chuck Lathrope);
  510. All statistics update now included in exception days rule.
  511. Changed partition handling to avoid unwarranted scanning and speed up process on tables with many partitions.
  512. v1.5.5 - 24-10-2013 - Added more verbose to debug mode;
  513. Fixed issue with error while keeping original fill factor when it was already set to 0 on the index;
  514. Fixed issue with error 35337 or 2706 on update statistics.
  515. v1.5.6 - 27-11-2013 - Added SQL 2014 support for online partition rebuild;
  516. Tuned LOB support with online operations;
  517. Improved detection of scope changes - saves unneeded database scans;
  518. Optimized defrag cycle pre-work with partially excluded DBs;
  519. Fixed issue with skipping partially excluded databases;
  520. Added resilience for CS collations.
  521. v1.5.7 - 14-01-2014 - Fixed issue on support SP usp_AdaptiveIndexDefrag_Exceptions with SQL Server 2005;
  522. Fixed issue with support SP usp_AdaptiveIndexDefrag_CurrentExecStats.
  523. v1.5.8 - 10-05-2014 - Added SQL 2014 support for Online Lock Priority;
  524. Fixed issue introduced in previous version where an Online rebuild operation could not be executed in SQL 2012.
  525. v1.5.9 - 17-11-2014 - Fixed issue on support SP usp_AdaptiveIndexDefrag_PurgeLogs.
  526. v1.6 - 18-11-2014 - Added resilience when objects are dropped while being scanned, avoiding error 2573.
  527. v1.6.1 - 04-02-2015 - Removed dependency of @scan_mode to use TF 2371 behavior for statistics update;
  528. Improved support for Columnstore indexes on SQL 2014, with specific rebuild threshold and reorg option.
  529. v1.6.2 - 10/3/2016 - Added option to determine whether to exclude blobs from fragmentation scan;
  530. Added support for incremental statistics;
  531. Fixed PK issue with columnstore fragmentation discovery.
  532. Fixed issue where auto created statistics would not be picked up for update.
  533. v1.6.3 - 10/14/2016 - Fixed issue with statistics collection in SQL Server 2012 and below;
  534. Fixed issue where indexes on views generated error 1934.
  535. v1.6.3.1 - 10/26/2016 - Fixed failed migration from v1.6.2 with NULL insert error;
  536. Fixed issue when running in debug mode.
  537. v1.6.3.2 - 11/4/2016 - Fixed DISABLE index applying to NCCI.
  538. Fixed statistics not being updated before index rebuild - introduced in v1.6.2;
  539. Fixed misplaced index disable statement if @Exec_Print = 0;
  540. Fixed issue with statistics collection in SQL Server 2012 and below;
  541. Added statistic related info to log table (rows, mod counter, rows sampled).
  542. v1.6.3.3 - 11/7/2016 - Rolled back previously reported issue with REORGANIZE and database names.
  543. v1.6.4 - 11/10/2016 - Fixed support for incremental statistics in SQL Server 2016 RTM.
  544. v1.6.4.1 - 11/16/2016 - Added support for incremental statistics in SQL Server 2016 SP1.
  545. v1.6.4.2 - 1/20/2017 - Fixed support for incremental statistics introduced error 4104.
  546. v1.6.5 - 2/18/2017 - Fixed empty columnstore indexes being picked up;
  547. Fixed orphaned statistic not being updated and preventing rescan;
  548. Fixed getting null comparison in sys.indexes (only on SQL 2005, SQL 2008 or SQL 2008R2 pre-SP2);
  549. Fixed insert error into tbl_AdaptiveIndexDefrag_Stats_log table.
  550. v1.6.5.1 - 3/3/2017 - Added custom threshold parameter for percent of changes needed to trigger statistics update, overriding default handling;
  551. Added parameter for min rows to be considered with custom threshold parameter.
  552. v1.6.5.2 - 4/13/2017 - Lowered min threshold for @statsThreshold setting.
  553. v1.6.5.3 - 4/30/2017 - Fixed error in debug summary.
  554. IMPORTANT:
  555. Execute in the database context of where you created the log and working tables.
  556. ALL parameters are optional. If not specified, the defaults for each parameter are used.
  557. @Exec_Print 1 = execute the SQL code generated by this SP;
  558. 0 = print commands only
  559. @printCmds 1 = print commands to screen;
  560. 0 = do not print commands
  561. @outputResults 1 = output fragmentation information after run completes;
  562. 0 = do not output fragmentation information
  563. @debugMode 1 = display debug comments;
  564. 0 = do not display debug comments
  565. @timeLimit Limits how much time can be spent performing index defrags; expressed in minutes.
  566. NOTE: The time limit is checked BEFORE an index defrag begins, thus a long index defrag can exceed the time limit.
  567. @dbScope Specify specific database name to defrag; if not specified, all non-system databases plus msdb and model will be defragmented.
  568. @tblName Specify if you only want to defrag indexes for a specific table, format = schema.table_name; if not specified, all tables will be defragmented.
  569. @defragOrderColumn Defines how to prioritize the order of defrags. Only used if @Exec_Print = 1.
  570. range_scan_count = count of range and table scans on the index; this is what can benefit the most from defragmentation;
  571. fragmentation = amount of fragmentation in the index;
  572. page_count = number of pages in the index; bigger indexes can take longer to defrag and thus generate more contention; may want to start with these;
  573. @defragSortOrder The sort order of the ORDER BY clause on the above query on how to prioritize the order of defrags.
  574. ASC (ascending)
  575. DESC (descending) is the default.
  576. @forceRescan Action on index rescan. If = 0, a rescan will not occur until all indexes have been defragmented. This can span multiple executions.
  577. 1 = force a rescan
  578. 0 = use previous scan, if there are indexes left to defrag
  579. @defragDelay Time to wait between defrag commands; gives the server a breathe between runs
  580. @ixtypeOption NULL = all indexes will be defragmented;
  581. 1 = only Clustered indexes will be defragmented;
  582. 0 = only Non-Clustered indexes will be defragmented (includes XML and Spatial Indexes);
  583. @minFragmentation Defaults to 5%, will not defrag if fragmentation is less.
  584. Refer to http://msdn.microsoft.com/en-us/library/ms189858.aspx
  585. @rebuildThreshold Defaults to 30%. greater than 30% will result in rebuild instead of reorganize.
  586. Refer to http://msdn.microsoft.com/en-us/library/ms189858.aspx
  587. @rebuildThreshold_csDefaults to 10%. Greater than 10% will result in columnstore rebuild.
  588. Refer to https://msdn.microsoft.com/en-us/data/dn589807(v=sql.120)
  589. @minPageCount Specifies how many pages must exist in an index in order to be considered for a defrag. Default to an extent. Refer to http://msdn.microsoft.com/en-us/library/ms189858.aspx
  590. NOTE: The @minPageCount will restrict the indexes that are stored in tbl_AdaptiveIndexDefrag_Working table and can render other options inoperative.
  591. @maxPageCount Specifies the maximum number of pages that can exist in an index and still be considered for a defrag.
  592. Useful for scheduling small indexes during business hours and large indexes for non-business hours.
  593. NOTE: The @maxPageCount will restrict the indexes selective for defrag;
  594. @fillfactor 1 = original from when the index was created or last defragmented;
  595. 0 = default fill factor
  596. @scanMode Specifies which scan mode to use to determine fragmentation levels. Options are:
  597. LIMITED = the fastest mode and scans the smallest number of pages.
  598. For an index, only the parent-level pages of the B-tree (that is, the pages above the leaf level) are scanned.
  599. For a heap, only the associated PFS and IAM pages are examined; the data pages of the heap are not scanned.
  600. Recommended for most cases.
  601. SAMPLED = returns statistics based on a 1 percent sample of all the pages in the index or heap.
  602. If the index or heap has fewer than 10,000 pages, DETAILED mode is used instead of SAMPLED.
  603. DETAILED = scans all pages and returns all statistics. Can cause performance issues.
  604. @onlineRebuild 1 = online rebuild if possible; only in Enterprise Edition;
  605. 0 = offline rebuild
  606. @sortInTempDB When 1, the sort results are stored in TempDB. When 0, the sort results are stored in the filegroup or partition scheme in which the resulting index is stored.
  607. If a sort operation is not required, or if the sort can be performed in memory, SORT_IN_TEMPDB is ignored.
  608. Enabling this option can result in faster defrags and prevent database file size inflation. Just have monitor TempDB closely.
  609. More information here: http://msdn.microsoft.com/en-us/library/ms188281.aspx and http://msdn.microsoft.com/en-us/library/ms179542.aspx and http://msdn.microsoft.com/en-us/library/ms191183.aspx
  610. 1 = perform sort operation in TempDB
  611. 0 = perform sort operation in the indexes database
  612. @maxDopRestriction Option to specify a processor limit for index rebuilds
  613. @updateStats 1 = updates stats when reorganizing;
  614. 0 = does not update stats when reorganizing
  615. @updateStatsWhere Update statistics within certain thresholds (http://support.microsoft.com/kb/195565/en-us)
  616. 1 = updates only index related stats;
  617. 0 = updates all stats in entire table
  618. @statsSample NULL = perform a sample scan on the target table or indexed view. The database engine automatically computes the required sample size;
  619. FULLSCAN = all rows in table or view should be read to gather the statistics;
  620. RESAMPLE = statistics will be gathered using an inherited sampling ratio for all existing statistics including indexes
  621. @statsThreshold Custom threshold of changes needed to trigger update statistics, overriding default handling;
  622. NULL = assume default handling which is similar to TF2371;
  623. A float number greater or equal to 0.001 and less than 100 to use custom stats sample
  624. @statsMinRows Sets the min number of rows a table has to have to be considered for @statsThreshold use;
  625. NULL = use @statsThreshold (if set) for any size table;
  626. An integer number that sets the min number of rows a table has to have to be considered for @statsThreshold use
  627. @ix_statsnorecompute 1 = STATISTICS_NORECOMPUTE on will disable the auto update statistics.
  628. If you are dealing with stats update with a custom job (or even with this code by updating statistics), you may use this option;
  629. 0 = default which is with STATISTICS_NORECOMPUTE off
  630. @statsIncremental When Incremental is ON, the statistics created are per partition statistics.
  631. When OFF, the statistics tree is dropped and SQL Server re-computes the statistics. This setting overrides the database level INCREMENTAL property. (http://msdn.microsoft.com/en-us/library/ms190397.aspx)
  632. NULL = Keep server setting;
  633. 1 = Enable auto create statistics with Incremental
  634. 0 = Disable auto create statistics with Incremental
  635. @dealMaxPartition If an index is partitioned, this option specifies whether to exclude the right-most populated partition, or act only on that same partition, excluding all others.
  636. Typically, this is the partition that is currently being written to in a sliding-window scenario.
  637. Enabling this feature may reduce contention. This may not be applicable in other types of partitioning scenarios.
  638. Non-partitioned indexes are unaffected by this option. Only in Enterprise Edition.
  639. 1 = exclude right-most populated partition
  640. 0 = only right-most populated partition (remember to verify @minPageCount, if partition is smaller than @minPageCount, it won't be considered)
  641. NULL = do not exclude any partitions
  642. @dealLOB Specifies that all pages that contain large object (LOB) data are compacted. The LOB data types are image, text, ntext, varchar(max), nvarchar(max), varbinary(max), and xml.
  643. Compacting this data can improve disk space use.
  644. Reorganizing a specified clustered index compacts all LOB columns that are contained in the clustered index.
  645. Reorganizing a non-clustered index compacts all LOB columns that are nonkey (included) columns in the index.
  646. 0 = compact LOBs when reorganizing (default behavior);
  647. 1 = does not compact LOBs when reorganizing
  648. @ignoreDropObj If a table or index is dropped after the defrag cycle has begun, you can choose to ignore those errors in the overall outcome,
  649. thus not showing a job as failed if the only errors present refer to dropped database objects.
  650. 0 = includes errors about objects that have been dropped since the defrag cycle began (default behavior);
  651. 1 = for error reporting purposes, ignores the fact that objects have been dropped since the defrag cycle began
  652. @disableNCIX If disk space is limited, it may be helpful to disable the non-clustered index before rebuilding it;
  653. When a non-clustered index is not disabled, the rebuild operation requires enough temporary disk space to store both the old and new index;
  654. However, by disabling and rebuilding a non-clustered index in separate transactions, the disk space made available by disabling the index can be reused by the subsequent rebuild or any other operation;
  655. No additional space is required except for temporary disk space for sorting; this is typically 20 percent of the index size;
  656. Does not disable indexes on partitioned tables when defragging a subset of existing partitions;
  657. Keeps track of whatever indexes were disabled by the defrag cycle. In case the defrag is cancelled, it will account for these disabled indexes in the next run.
  658. 0 = does NOT disable non-clustered indexes prior to a rebuild (default behavior);
  659. 1 = disables non-clustered indexes prior to a rebuild (space saving feature)
  660. @offlinelocktimeout As set in SET LOCK_TIMEOUT (http://msdn.microsoft.com/en-us/library/ms189470.aspx)
  661. -1 = (default) indicates no time-out period
  662. Any other positive integer = sets the number of milliseconds that will pass before Microsoft SQL Server returns a locking error
  663. @onlinelocktimeout Indicates a time-out period for locks to wait at low priority, expressed in minutes; this is valid from SQL Server 2014 onwards
  664. @abortAfterwait If the online low priority lock timeout occurs, this will set the action to perform afterwards.
  665. NULL = (default) After lock timeout occurs, continue waiting for the lock with normal (regular) priority;
  666. 1 = Exit the online index rebuild DDL operation currently being executed without taking any action;
  667. 2 = Kill all user transactions that block the online index rebuild DDL operation so that the operation can continue.
  668. @dealROWG Set Columnstore reorg option to compress all rowgroups, and not just closed ones
  669. 0 = (default) compress closed rowgroups on columnstore.
  670. 1 = compress all rowgroups on columnstore, and not just closed ones.
  671. @getBlobfrag Indicates whether to exclude or include blobs from fragmentation scan.
  672. 0 = (default) exclude blobs from fragmentation scan.
  673. 1 = include blobs and off-row data when scanning for fragmentation.
  674. -------------------------------------------------------
  675. Usage:
  676. EXEC dbo.usp_AdaptiveIndexDefrag
  677. or customize it like the example:
  678. EXEC dbo.usp_AdaptiveIndexDefrag
  679. @Exec_Print = 0
  680. , @printCmds = 1
  681. , @updateStats = 1
  682. , @updateStatsWhere = 1
  683. , @debugMode = 1
  684. , @outputResults = 1
  685. , @dbScope = 'AdventureWorks2008R2'
  686. , @forceRescan = 1
  687. , @maxDopRestriction = 2
  688. , @minPageCount = 8
  689. , @maxPageCount = NULL
  690. , @minFragmentation = 1
  691. , @rebuildThreshold = 1
  692. , @rebuildThreshold_cs = 1
  693. , @defragDelay = '00:00:05'
  694. , @defragOrderColumn = 'range_scan_count'
  695. , @dealMaxPartition = NULL
  696. , @disableNCIX = 1
  697. , @offlinelocktimeout = 180;
  698. */
  699. SET NOCOUNT ON;
  700. SET XACT_ABORT ON;
  701. SET QUOTED_IDENTIFIER ON;
  702. SET DATEFORMAT ymd;
  703. SET DEADLOCK_PRIORITY -10;
  704. -- Required so it can update stats on IxVws and FiltIxs
  705. SET ANSI_WARNINGS ON;
  706. SET ANSI_PADDING ON;
  707. SET ANSI_NULLS ON;
  708. SET ARITHABORT ON;
  709. SET CONCAT_NULL_YIELDS_NULL ON;
  710. SET NUMERIC_ROUNDABORT OFF;
  711. BEGIN
  712. BEGIN TRY
  713. /* Validating and normalizing options... */
  714. IF @debugMode = 1
  715. RAISERROR('Validating options...', 0, 42) WITH NOWAIT;
  716. IF @minFragmentation IS NULL OR @minFragmentation NOT BETWEEN 0.00 AND 100.0
  717. SET @minFragmentation = 5.0;
  718. IF @rebuildThreshold IS NULL OR @rebuildThreshold NOT BETWEEN 0.00 AND 100.0
  719. SET @rebuildThreshold = 30.0;
  720. IF @rebuildThreshold_cs IS NULL OR @rebuildThreshold_cs NOT BETWEEN 0.00 AND 100.0
  721. SET @rebuildThreshold_cs = 10.0;
  722. IF @statsThreshold IS NOT NULL AND @statsThreshold NOT BETWEEN 0.001 AND 100.0
  723. SET @statsThreshold = NULL;
  724. IF @timeLimit IS NULL
  725. SET @timeLimit = 480;
  726. /* Validate if table name is fully qualified and database scope is set */
  727. IF @tblName IS NOT NULL AND @tblName NOT LIKE '%.%'
  728. BEGIN
  729. RAISERROR('WARNING: Table name must be fully qualified. Input format should be <schema>.<table_name>.', 15, 42) WITH NOWAIT;
  730. RETURN
  731. END;
  732. /* Validate if database scope is set when table name is also set */
  733. IF @tblName IS NOT NULL AND @dbScope IS NULL
  734. BEGIN
  735. RAISERROR('WARNING: A database scope must be set when using table names.', 15, 42) WITH NOWAIT;
  736. RETURN
  737. END;
  738. /* Validate if database scope exists */
  739. IF @dbScope IS NOT NULL AND LOWER(@dbScope) NOT IN (SELECT LOWER([name]) FROM sys.databases WHERE LOWER([name]) NOT IN ('master', 'tempdb', 'model', 'reportservertempdb','semanticsdb') AND is_distributor = 0)
  740. BEGIN
  741. RAISERROR('WARNING: The database in scope does not exist or is a system database.', 15, 42) WITH NOWAIT;
  742. RETURN
  743. END;
  744. /* Validate offline lock timeout settings */
  745. IF @offlinelocktimeout IS NULL OR ISNUMERIC(@offlinelocktimeout) <> 1
  746. BEGIN
  747. RAISERROR('WARNING: Offline lock timeout must be set to an integer number.', 15, 42) WITH NOWAIT;
  748. RETURN
  749. END;
  750. IF @offlinelocktimeout <> -1 AND @offlinelocktimeout IS NOT NULL
  751. SET @offlinelocktimeout = ABS(@offlinelocktimeout)
  752. /* Validate online lock timeout settings */
  753. IF @onlinelocktimeout IS NULL OR ISNUMERIC(@onlinelocktimeout) <> 1
  754. BEGIN
  755. RAISERROR('WARNING: Online lock timeout must be set to an integer number.', 15, 42) WITH NOWAIT;
  756. RETURN
  757. END;
  758. IF @onlinelocktimeout <> 5 AND @onlinelocktimeout IS NOT NULL
  759. SET @onlinelocktimeout = ABS(@onlinelocktimeout)
  760. /* Validate online lock timeout wait action settings */
  761. IF @abortAfterwait IS NOT NULL AND @abortAfterwait NOT IN (0,1)
  762. BEGIN
  763. RAISERROR('WARNING: Online lock timeout action is invalid.', 15, 42) WITH NOWAIT;
  764. RETURN
  765. END;
  766. /* Validate amount of breather time to give between operations*/
  767. IF @defragDelay NOT LIKE '00:[0-5][0-9]:[0-5][0-9]'
  768. BEGIN
  769. SET @defragDelay = '00:00:05';
  770. RAISERROR('Defrag delay input not valid. Defaulting to 5s.', 0, 42) WITH NOWAIT;
  771. END;
  772. IF @defragOrderColumn IS NULL OR LOWER(@defragOrderColumn) NOT IN ('range_scan_count', 'fragmentation', 'page_count')
  773. BEGIN
  774. SET @defragOrderColumn = 'range_scan_count';
  775. RAISERROR('Defrag order input not valid. Defaulting to range_scan_count.', 0, 42) WITH NOWAIT;
  776. END;
  777. IF @defragSortOrder IS NULL OR UPPER(@defragSortOrder) NOT IN ('ASC', 'DESC')
  778. SET @defragSortOrder = 'DESC';
  779. IF UPPER(@scanMode) NOT IN ('LIMITED', 'SAMPLED', 'DETAILED')
  780. BEGIN
  781. SET @scanMode = 'LIMITED';
  782. RAISERROR('Index scan mode input not valid. Defaulting to LIMITED.', 0, 42) WITH NOWAIT;
  783. END;
  784. IF @ixtypeOption IS NOT NULL AND @ixtypeOption NOT IN (0,1)
  785. SET @ixtypeOption = NULL;
  786. IF @statsSample IS NOT NULL AND UPPER(@statsSample) NOT IN ('FULLSCAN', 'RESAMPLE')
  787. SET @statsSample = NULL;
  788. /* Find sql server version info */
  789. DECLARE @sqlmajorver int, @sqlminorver int, @sqlbuild int;
  790. SELECT @sqlmajorver = CONVERT(int, (@@microsoftversion / 0x1000000) & 0xff);
  791. SELECT @sqlminorver = CONVERT(int, (@@microsoftversion / 0x10000) & 0xff);
  792. SELECT @sqlbuild = CONVERT(int, @@microsoftversion & 0xffff);
  793. /* Recognize if database in scope is a Always On secondary replica */
  794. IF @dbScope IS NOT NULL AND @sqlmajorver >= 11
  795. BEGIN
  796. DECLARE @sqlcmdAO NVARCHAR(3000), @paramsAO NVARCHAR(50), @DBinAG int
  797. SET @sqlcmdAO = 'IF LOWER(@dbScopeIN) IN (SELECT LOWER(DB_NAME(dr.database_id))
  798. FROM sys.dm_hadr_database_replica_states dr
  799. INNER JOIN sys.dm_hadr_availability_replica_states rs ON dr.group_id = rs.group_id
  800. INNER JOIN sys.databases d ON dr.database_id = d.database_id
  801. WHERE rs.role = 2 -- Is Secondary
  802. AND dr.is_local = 1
  803. AND rs.is_local = 1)
  804. BEGIN
  805. SET @DBinAG_OUT = 1
  806. END
  807. ELSE
  808. BEGIN
  809. SET @DBinAG_OUT = 0
  810. END'
  811. SET @paramsAO = N'@dbScopeIN NVARCHAR(256), @DBinAG_OUT int OUTPUT'
  812. EXECUTE sp_executesql @sqlcmdAO, @paramsAO, @dbScopeIN = @dbScope, @DBinAG_OUT = @DBinAG OUTPUT
  813. IF @DBinAG = 1
  814. BEGIN
  815. RAISERROR('WARNING: Cannot defrag database in scope because it is part of an Always On secondary replica.', 15, 42) WITH NOWAIT;
  816. RETURN
  817. END
  818. END
  819. /* Check if database scope has changed, if rescan is not being forced */
  820. IF @forceRescan = 0 AND @dbScope IS NOT NULL -- Specific scope was set
  821. BEGIN
  822. IF (SELECT COUNT(DISTINCT [dbID]) FROM dbo.tbl_AdaptiveIndexDefrag_Working) = 1
  823. AND QUOTENAME(LOWER(@dbScope)) NOT IN (SELECT DISTINCT LOWER([dbName]) FROM dbo.tbl_AdaptiveIndexDefrag_Working UNION SELECT DISTINCT LOWER(dbName) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working)
  824. BEGIN
  825. SET @forceRescan = 1
  826. RAISERROR('Scope has changed. Forcing rescan of single database in scope...', 0, 42) WITH NOWAIT;
  827. END;
  828. END;
  829. /* Recognize if we have indexes of the chosen type left to defrag or stats left to update;
  830. otherwise force rescan of database(s), if rescan is not being forced */
  831. IF @forceRescan = 0
  832. AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [type] = 1 AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0) AND @ixtypeOption = 1)
  833. AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [type] <> 1 AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0) AND @ixtypeOption = 0)
  834. AND (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0 ) AND @ixtypeOption IS NULL)
  835. AND NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working AS idss WHERE idss.updateDate IS NULL AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0))
  836. BEGIN
  837. SET @forceRescan = 1
  838. RAISERROR('No indexes of the chosen type left to defrag nor statistics left to update. Forcing rescan...', 0, 42) WITH NOWAIT;
  839. END;
  840. /* Check if any databases where dropped or created since last run, if rescan is not being forced */
  841. IF @forceRescan = 0 AND @dbScope IS NULL
  842. BEGIN
  843. DECLARE @sqlcmd_CntSrc NVARCHAR(3000), @params_CntSrc NVARCHAR(50), @CountSrc int
  844. DECLARE @sqlcmd_CntTgt NVARCHAR(3000), @params_CntTgt NVARCHAR(50), @CountTgt int
  845. DECLARE @dbIDIX int, @hasIXs bit, @hasIXsCntsqlcmd NVARCHAR(3000), @hasIXsCntsqlcmdParams NVARCHAR(50)
  846. -- What is in working tables plus exceptions that still exist in server
  847. SET @sqlcmd_CntSrc = 'SELECT @CountSrc_OUT = COUNT(DISTINCT Working.[dbID]) FROM
  848. (SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Working
  849. UNION
  850. SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  851. UNION
  852. SELECT DISTINCT [dbID] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Exceptions
  853. WHERE [dbID] IN (SELECT DISTINCT database_id FROM master.sys.databases sd
  854. WHERE LOWER(sd.[name]) NOT IN (''master'', ''tempdb'', ''model'', ''reportservertempdb'',''semanticsdb'')
  855. AND [state] = 0 -- must be ONLINE
  856. AND is_read_only = 0 -- cannot be READ_ONLY
  857. AND is_distributor = 0)
  858. ) Working'
  859. SET @params_CntSrc = N'@CountSrc_OUT int OUTPUT'
  860. -- What exists in current instance, in ONLINE state and READ_WRITE
  861. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexFindInDatabaseList'))
  862. DROP TABLE #tblIndexFindInDatabaseList;
  863. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexFindInDatabaseList'))
  864. CREATE TABLE #tblIndexFindInDatabaseList
  865. (
  866. [dbID] int
  867. , hasIXs bit NOT NULL
  868. , scanStatus bit NULL
  869. );
  870. /* Retrieve the list of databases to loop, excluding Always On secondary replicas */
  871. SET @sqlcmd_CntTgt = 'SELECT [database_id], 0, 0 -- not yet scanned
  872. FROM master.sys.databases
  873. WHERE LOWER([name]) = ISNULL(LOWER(@dbScopeIN), LOWER([name]))
  874. AND LOWER([name]) NOT IN (''master'', ''tempdb'', ''model'', ''reportservertempdb'',''semanticsdb'') -- exclude system databases
  875. AND [state] = 0 -- must be ONLINE
  876. AND is_read_only = 0 -- cannot be READ_ONLY
  877. AND is_distributor = 0'
  878. IF @sqlmajorver >= 11 -- Except all local Always On secondary replicas
  879. BEGIN
  880. SET @sqlcmd_CntTgt = @sqlcmd_CntTgt + CHAR(10) + 'AND [database_id] NOT IN (SELECT dr.database_id FROM sys.dm_hadr_database_replica_states dr
  881. INNER JOIN sys.dm_hadr_availability_replica_states rs ON dr.group_id = rs.group_id
  882. INNER JOIN sys.databases d ON dr.database_id = d.database_id
  883. WHERE rs.role = 2 -- Is Secondary
  884. AND dr.is_local = 1
  885. AND rs.is_local = 1)'
  886. END
  887. SET @params_CntTgt = N'@dbScopeIN NVARCHAR(256)'
  888. INSERT INTO #tblIndexFindInDatabaseList
  889. EXECUTE sp_executesql @sqlcmd_CntTgt, @params_CntTgt, @dbScopeIN = @dbScope
  890. WHILE (SELECT COUNT(*) FROM #tblIndexFindInDatabaseList WHERE scanStatus = 0) > 0
  891. BEGIN
  892. SELECT TOP 1 @dbIDIX = [dbID] FROM #tblIndexFindInDatabaseList WHERE scanStatus = 0;
  893. SET @hasIXsCntsqlcmd = 'IF EXISTS (SELECT TOP 1 [index_id] from [' + DB_NAME(@dbIDIX) + '].sys.indexes AS si
  894. INNER JOIN [' + DB_NAME(@dbIDIX) + '].sys.objects so ON si.object_id = so.object_id
  895. WHERE so.is_ms_shipped = 0 AND [index_id] > 0 AND si.is_hypothetical = 0
  896. AND si.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + DB_NAME(@dbIDIX) + '].sys.internal_tables AS sit))
  897. OR
  898. EXISTS (SELECT TOP 1 [stats_id] from [' + DB_NAME(@dbIDIX) + '].sys.stats AS ss
  899. INNER JOIN [' + DB_NAME(@dbIDIX) + '].sys.objects so ON ss.[object_id] = so.[object_id]
  900. WHERE so.is_ms_shipped = 0
  901. AND ss.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + DB_NAME(@dbIDIX) + '].sys.internal_tables AS sit))
  902. BEGIN SET @hasIXsOUT = 1 END ELSE BEGIN SET @hasIXsOUT = 0 END'
  903. SET @hasIXsCntsqlcmdParams = '@hasIXsOUT int OUTPUT'
  904. EXECUTE sp_executesql @hasIXsCntsqlcmd, @hasIXsCntsqlcmdParams, @hasIXsOUT = @hasIXs OUTPUT
  905. UPDATE #tblIndexFindInDatabaseList
  906. SET hasIXs = @hasIXs, scanStatus = 1
  907. WHERE [dbID] = @dbIDIX
  908. END
  909. EXECUTE sp_executesql @sqlcmd_CntSrc, @params_CntSrc, @CountSrc_OUT = @CountSrc OUTPUT
  910. SELECT @CountTgt = COUNT([dbID]) FROM #tblIndexFindInDatabaseList WHERE hasIXs = 1
  911. IF @CountSrc <> @CountTgt -- current databases in working lists <> number of eligible databases in instance
  912. BEGIN
  913. SET @forceRescan = 1
  914. RAISERROR('Scope has changed. Forcing rescan...', 0, 42) WITH NOWAIT;
  915. END
  916. END
  917. IF @debugMode = 1
  918. RAISERROR('Starting up...', 0, 42) WITH NOWAIT;
  919. /* Declare variables */
  920. DECLARE @ver VARCHAR(10)
  921. , @objectID int
  922. , @dbID int
  923. , @dbName NVARCHAR(256)
  924. , @indexID int
  925. , @operationFlag bit -- 0 = Reorganize, 1 = Rebuild
  926. , @partitionCount bigint
  927. , @schemaName NVARCHAR(128)
  928. , @objectName NVARCHAR(256)
  929. , @indexName NVARCHAR(256)
  930. , @statsobjectID int
  931. , @statsschemaName NVARCHAR(128)
  932. , @statsName NVARCHAR(256)
  933. , @statsobjectName NVARCHAR(256)
  934. , @stats_norecompute bit
  935. , @stats_isincremental bit
  936. , @is_primary_key bit
  937. , @fill_factor int
  938. , @is_disabled bit
  939. , @is_padded bit
  940. , @has_filter bit
  941. , @partitionNumber smallint
  942. , @maxpartitionNumber smallint
  943. , @minpartitionNumber smallint
  944. , @fragmentation float
  945. , @pageCount int
  946. , @sqlcommand NVARCHAR(4000)
  947. , @sqlcommand2 NVARCHAR(600)
  948. , @sqldisablecommand NVARCHAR(600)
  949. , @sqlprecommand NVARCHAR(600)
  950. , @rebuildcommand NVARCHAR(600)
  951. , @dateTimeStart DATETIME
  952. , @dateTimeEnd DATETIME
  953. , @containsColumnstore int
  954. , @CStore_SQL NVARCHAR(4000)
  955. , @CStore_SQL_Param NVARCHAR(1000)
  956. , @editionCheck bit
  957. , @debugMessage VARCHAR(2048)
  958. , @updateSQL NVARCHAR(4000)
  959. , @partitionSQL NVARCHAR(4000)
  960. , @partitionSQL_Param NVARCHAR(1000)
  961. , @rowmodctrSQL NVARCHAR(4000)
  962. , @rowmodctrSQL_Param NVARCHAR(1000)
  963. , @rowmodctr bigint
  964. , @record_count bigint
  965. , @range_scan_count bigint
  966. , @getStatSQL NVARCHAR(4000)
  967. , @getStatSQL_Param NVARCHAR(1000)
  968. , @statsID int
  969. , @surrogateStatsID int
  970. , @ixtype tinyint -- 0 = Heap; 1 = Clustered; 2 = Nonclustered; 3 = XML; 4 = Spatial
  971. , @containsLOB int
  972. , @LOB_SQL NVARCHAR(4000)
  973. , @LOB_SQL_Param NVARCHAR(1000)
  974. , @indexDefrag_id int
  975. , @statsUpdate_id int
  976. , @startDateTime DATETIME
  977. , @endDateTime DATETIME
  978. , @getIndexSQL NVARCHAR(4000)
  979. , @getIndexSQL_Param NVARCHAR(4000)
  980. , @allowPageLockSQL NVARCHAR(4000)
  981. , @allowPageLockSQL_Param NVARCHAR(4000)
  982. , @allowPageLocks bit
  983. , @dealMaxPartitionSQL NVARCHAR(4000)
  984. , @cpucount smallint
  985. , @tblNameFQN NVARCHAR(1000)
  986. , @TableScanSQL NVARCHAR(2000)
  987. , @ixCntSource int
  988. , @ixCntTarget int
  989. , @ixCntsqlcmd NVARCHAR(1000)
  990. , @ixCntsqlcmdParams NVARCHAR(100)
  991. , @ColumnStoreGetIXSQL NVARCHAR(2000)
  992. , @ColumnStoreGetIXSQL_Param NVARCHAR(1000)
  993. , @rows bigint
  994. , @rows_sampled bigint
  995. /* Initialize variables */
  996. SELECT @startDateTime = GETDATE(), @endDateTime = DATEADD(minute, @timeLimit, GETDATE()), @operationFlag = NULL, @ver = '1.6.5.3';
  997. /* Create temporary tables */
  998. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragDatabaseList'))
  999. DROP TABLE #tblIndexDefragDatabaseList;
  1000. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragDatabaseList'))
  1001. CREATE TABLE #tblIndexDefragDatabaseList
  1002. (
  1003. dbID int
  1004. , dbName NVARCHAR(256)
  1005. , scanStatus bit NULL
  1006. );
  1007. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragmaxPartitionList'))
  1008. DROP TABLE #tblIndexDefragmaxPartitionList;
  1009. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragmaxPartitionList'))
  1010. CREATE TABLE #tblIndexDefragmaxPartitionList
  1011. (
  1012. objectID int
  1013. , indexID int
  1014. , maxPartition int
  1015. );
  1016. /* Create table for fragmentation scan per table, index and partition - slower but less chance of blocking*/
  1017. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragScanWorking'))
  1018. DROP TABLE #tblIndexDefragScanWorking;
  1019. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragScanWorking'))
  1020. CREATE TABLE #tblIndexDefragScanWorking
  1021. (
  1022. objectID int
  1023. , indexID int
  1024. , type tinyint
  1025. , partitionNumber int
  1026. , is_done bit
  1027. );
  1028. /* Find available processors*/
  1029. SELECT @cpucount = COUNT(*)
  1030. FROM sys.dm_os_schedulers
  1031. WHERE is_online = 1 AND scheduler_id < 255 AND status = 'VISIBLE ONLINE'
  1032. IF @maxDopRestriction IS NOT NULL AND @maxDopRestriction > @cpucount AND @cpucount <= 8
  1033. BEGIN
  1034. SET @maxDopRestriction = @cpucount
  1035. END
  1036. ELSE IF @maxDopRestriction IS NOT NULL AND ((@maxDopRestriction > @cpucount AND @cpucount > 8) OR @maxDopRestriction > 8)
  1037. BEGIN
  1038. SET @maxDopRestriction = 8;
  1039. END
  1040. /* Refer to http://msdn.microsoft.com/en-us/library/ms174396.aspx */
  1041. IF (SELECT SERVERPROPERTY('EditionID')) IN (1804890536, 1872460670, 610778273, -2117995310)
  1042. SET @editionCheck = 1 -- supports enterprise only features: online rebuilds, partitioned indexes and MaxDOP
  1043. ELSE
  1044. SET @editionCheck = 0; -- does not support enterprise only features: online rebuilds, partitioned indexes and MaxDOP
  1045. /* Output the parameters to work with */
  1046. IF @debugMode = 1
  1047. BEGIN
  1048. SELECT @debugMessage = CHAR(10) + 'Executing AdaptiveIndexDefrag v' + @ver + ' on ' + @@VERSION + '.
  1049. The selected parameters are:
  1050. Defragment indexes with fragmentation greater or equal to ' + CAST(@minFragmentation AS NVARCHAR(10)) + ';
  1051. Rebuild indexes with fragmentation greater than ' + CAST(@rebuildThreshold AS NVARCHAR(10)) + ';
  1052. Rebuild columnstore indexes with fragmentation greater than ' + CAST(@rebuildThreshold_cs AS NVARCHAR(10)) + ';
  1053. ' + CASE WHEN @disableNCIX = 1 THEN 'Non-clustered indexes will be disabled prior to rebuild;
  1054. ' ELSE '' END + 'Defragment ' + CASE WHEN @ixtypeOption IS NULL THEN 'ALL indexes' WHEN @ixtypeOption = 1 THEN 'only CLUSTERED indexes' ELSE 'only NON-CLUSTERED, XML and Spatial indexes' END + ';
  1055. Commands' + CASE WHEN @Exec_Print = 1 THEN ' WILL' ELSE ' WILL NOT' END + ' be executed automatically;
  1056. Defragment indexes in ' + @defragSortOrder + ' order of the ' + UPPER(@defragOrderColumn) + ' value;
  1057. Time limit' + CASE WHEN @timeLimit IS NULL THEN ' was not specified;' ELSE ' was specified and is ' + CAST(@timeLimit AS NVARCHAR(10)) END + ' minutes;
  1058. ' + CASE WHEN @dbScope IS NULL THEN 'ALL databases' ELSE 'The ' + @dbScope + ' database' END + ' will be defragmented;
  1059. ' + CASE WHEN @tblName IS NULL THEN 'ALL tables' ELSE 'The ' + @tblName + ' table' END + ' will be defragmented;
  1060. ' + 'We' + CASE WHEN EXISTS(SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL) AND @forceRescan = 0 THEN ' will resume any existing previous run. If so, we WILL NOT' ELSE ' WILL' END + ' be rescanning indexes;
  1061. The scan will be performed in ' + @scanMode + ' mode;
  1062. LOBs will ' + CASE WHEN @dealLOB = 1 THEN 'NOT ' ELSE '' END + 'be compacted;
  1063. Limit defrags to indexes' + CASE WHEN @maxPageCount IS NULL THEN ' with more than ' + CAST(@minPageCount AS NVARCHAR(10)) ELSE
  1064. ' between ' + CAST(@minPageCount AS NVARCHAR(10)) + ' and ' + CAST(@maxPageCount AS NVARCHAR(10)) END + ' pages;
  1065. Indexes will be defragmented' + CASE WHEN @onlineRebuild = 0 OR @editionCheck = 0 THEN ' OFFLINE;' ELSE ' ONLINE;' END + '
  1066. Indexes will be sorted in' + CASE WHEN @sortInTempDB = 0 THEN ' the DATABASE;' ELSE ' TEMPDB;' END + '
  1067. Indexes will have' + CASE WHEN @fillfactor = 1 THEN ' its ORIGINAL' ELSE ' the DEFAULT' END + ' Fill Factor;' +
  1068. CASE WHEN @dealMaxPartition = 1 AND @editionCheck = 1 THEN '
  1069. The right-most populated partitions will be ignored;'
  1070. WHEN @dealMaxPartition = 0 AND @editionCheck = 1 THEN '
  1071. Only the right-most populated partitions will be considered if greater than ' + CAST(@minPageCount AS NVARCHAR(10)) + ' page(s);'
  1072. ELSE CHAR(10) + 'All partitions will be considered;' END +
  1073. CHAR(10) + 'Statistics ' + CASE WHEN @updateStats = 1 THEN 'WILL' ELSE 'WILL NOT' END + ' be updated ' + CASE WHEN @updateStatsWhere = 1 THEN 'on reorganized indexes;' ELSE 'on all stats belonging to parent table;' END +
  1074. CASE WHEN @updateStats = 1 AND @statsSample IS NOT NULL THEN CHAR(10) + 'Statistics will be updated with ' + @statsSample + '.' ELSE '' END +
  1075. CHAR(10) + 'Statistics will be updated using ' + CASE WHEN @statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.001 AND 100.0 THEN 'a threshold of ' + CONVERT(VARCHAR, @statsThreshold) + ' percent' ELSE ' a calculated threshold similar to TF2371' END +
  1076. + ' on tables ' + CASE WHEN @statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.01 AND 100.0 AND @statsMinRows IS NOT NULL THEN 'with a min of ' + CONVERT(VARCHAR, @statsMinRows) + ' rows.' WHEN @statsMinRows IS NOT NULL THEN ' of any size.' ELSE '.' END +
  1077. CHAR(10) + 'Statistics will be updated with Incremental property (if any) ' + CASE WHEN @statsIncremental = 1 THEN 'as ON' WHEN @statsIncremental = 0 THEN 'as OFF' ELSE 'not changed from current setting' END + '.' +
  1078. CHAR(10) + 'Defragmentation will use ' + CASE WHEN @editionCheck = 0 OR @maxDopRestriction IS NULL THEN 'system defaults for processors;'
  1079. ELSE CAST(@maxDopRestriction AS VARCHAR(2)) + ' processors;' END +
  1080. CHAR(10) + 'Lock timeout is set to ' + CASE WHEN @offlinelocktimeout <> -1 AND @offlinelocktimeout IS NOT NULL THEN CONVERT(NVARCHAR(15), @offlinelocktimeout) ELSE 'system default' END + ' for offline rebuilds;' +
  1081. CHAR(10) + 'From SQL Server 2014, lock timeout is set to ' + CONVERT(NVARCHAR(15), @onlinelocktimeout) + ' for online rebuilds;' +
  1082. CHAR(10) + 'From SQL Server 2014, lock timeout action is set to ' + CASE WHEN @abortAfterwait = 0 THEN 'BLOCKERS' WHEN @abortAfterwait = 1 THEN 'SELF' ELSE 'NONE' END + ' for online rebuilds;' +
  1083. CHAR(10) + CASE WHEN @printCmds = 1 THEN ' DO print' ELSE ' DO NOT print' END + ' the sql commands;' +
  1084. CHAR(10) + CASE WHEN @outputResults = 1 THEN ' DO output' ELSE ' DO NOT output' END + ' fragmentation levels;
  1085. Wait ' + @defragDelay + ' (hh:mm:ss) between index operations;
  1086. Execute in' + CASE WHEN @debugMode = 1 THEN ' DEBUG' ELSE ' SILENT' END + ' mode.' + CHAR(10);
  1087. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1088. END;
  1089. /* If we are scanning the database(s), do some pre-work */
  1090. IF @forceRescan = 1 OR (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)
  1091. AND NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working AS idss WHERE idss.updateDate IS NULL AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)))
  1092. BEGIN
  1093. IF @debugMode = 1
  1094. RAISERROR('Listing databases...', 0, 42) WITH NOWAIT;
  1095. /* Retrieve the list of databases to loop, exclusing Always On secondary replicas */
  1096. DECLARE @sqlcmdAO2 NVARCHAR(4000), @paramsAO2 NVARCHAR(50)
  1097. IF @debugMode = 1 AND @sqlmajorver >= 11
  1098. RAISERROR('Retrieving list of databases to loop, excluding Always On secondary replicas...', 0, 42) WITH NOWAIT;
  1099. IF @debugMode = 1 AND @sqlmajorver < 11
  1100. RAISERROR('Retrieving list of databases to loop...', 0, 42) WITH NOWAIT;
  1101. SET @sqlcmdAO2 = 'SELECT [database_id], name, 0 -- not yet scanned for fragmentation
  1102. FROM master.sys.databases
  1103. WHERE LOWER([name]) = ISNULL(LOWER(@dbScopeIN), LOWER([name]))
  1104. AND LOWER([name]) NOT IN (''master'', ''tempdb'', ''model'', ''reportservertempdb'',''semanticsdb'') -- exclude system databases
  1105. AND [state] = 0 -- must be ONLINE
  1106. AND is_read_only = 0 -- cannot be READ_ONLY
  1107. AND is_distributor = 0'
  1108. IF @sqlmajorver >= 11 -- Except all local Always On secondary replicas
  1109. BEGIN
  1110. SET @sqlcmdAO2 = @sqlcmdAO2 + CHAR(10) + 'AND [database_id] NOT IN (SELECT dr.database_id FROM sys.dm_hadr_database_replica_states dr
  1111. INNER JOIN sys.dm_hadr_availability_replica_states rs ON dr.group_id = rs.group_id
  1112. INNER JOIN sys.databases d ON dr.database_id = d.database_id
  1113. WHERE rs.role = 2 -- Is Secondary
  1114. AND dr.is_local = 1
  1115. AND rs.is_local = 1)'
  1116. END
  1117. SET @paramsAO2 = N'@dbScopeIN NVARCHAR(256)'
  1118. INSERT INTO #tblIndexDefragDatabaseList
  1119. EXECUTE sp_executesql @sqlcmdAO2, @paramsAO2, @dbScopeIN = @dbScope
  1120. IF @debugMode = 1
  1121. RAISERROR('Cross checking with exceptions for today...', 0, 42) WITH NOWAIT;
  1122. /* Avoid scanning databases that have all its indexes in the exceptions table i.e, fully excluded */
  1123. WHILE (SELECT COUNT(*) FROM #tblIndexDefragDatabaseList WHERE scanStatus = 0) > 0
  1124. BEGIN
  1125. SELECT TOP 1 @dbID = dbID FROM #tblIndexDefragDatabaseList WHERE scanStatus = 0;
  1126. SELECT @ixCntSource = COUNT([indexName]) FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions WHERE [dbID] = @dbID AND exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0
  1127. SET @ixCntsqlcmd = 'SELECT @ixCntTargetOUT = COUNT(si.index_id) FROM [' + DB_NAME(@dbID) + '].sys.indexes si
  1128. INNER JOIN [' + DB_NAME(@dbID) + '].sys.objects so ON si.object_id = so.object_id
  1129. WHERE so.is_ms_shipped = 0 AND si.index_id > 0 AND si.is_hypothetical = 0
  1130. AND si.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + DB_NAME(@dbID) + '].sys.internal_tables AS sit)' -- Exclude Heaps, Internal and Hypothetical objects
  1131. SET @ixCntsqlcmdParams = '@ixCntTargetOUT int OUTPUT'
  1132. EXECUTE sp_executesql @ixCntsqlcmd, @ixCntsqlcmdParams, @ixCntTargetOUT = @ixCntTarget OUTPUT
  1133. IF @ixCntSource = @ixCntTarget AND @ixCntSource > 0 -- All database objects are excluded, so skip database scanning
  1134. BEGIN
  1135. UPDATE #tblIndexDefragDatabaseList
  1136. SET scanStatus = NULL
  1137. WHERE dbID = @dbID;
  1138. IF @debugMode = 1
  1139. SELECT @debugMessage = ' Database ' + DB_NAME(@dbID) + ' is fully excluded from todays work.';
  1140. IF @debugMode = 1
  1141. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1142. END
  1143. IF @ixCntSource < @ixCntTarget AND @ixCntSource > 0 -- Only some database objects are excluded, so scan anyway and deal with exclusions on a granular level
  1144. BEGIN
  1145. UPDATE #tblIndexDefragDatabaseList
  1146. SET scanStatus = 1
  1147. WHERE dbID = @dbID;
  1148. IF @debugMode = 1
  1149. SELECT @debugMessage = ' Database ' + DB_NAME(@dbID) + ' is partially excluded from todays work.';
  1150. IF @debugMode = 1
  1151. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1152. END
  1153. IF @ixCntSource = 0 -- Database does not have excluded objects
  1154. BEGIN
  1155. UPDATE #tblIndexDefragDatabaseList
  1156. SET scanStatus = 1
  1157. WHERE dbID = @dbID;
  1158. END;
  1159. END;
  1160. /* Delete databases that are fully excluded for today */
  1161. DELETE FROM #tblIndexDefragDatabaseList
  1162. WHERE scanStatus IS NULL;
  1163. /* Reset status after cross check with exceptions */
  1164. UPDATE #tblIndexDefragDatabaseList
  1165. SET scanStatus = 0;
  1166. END
  1167. /* Check to see if we have indexes of the chosen type in need of defrag, or stats to update; otherwise, allow re-scanning the database(s) */
  1168. IF @forceRescan = 1 OR (NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL AND [exclusionMask] & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)
  1169. AND NOT EXISTS (SELECT TOP 1 * FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working AS idss WHERE idss.updateDate IS NULL AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)))
  1170. BEGIN
  1171. IF @debugMode = 1
  1172. RAISERROR('Preparing for new database scan...', 0, 42) WITH NOWAIT;
  1173. /* Truncate list of indexes and stats to prepare for a new scan */
  1174. TRUNCATE TABLE dbo.tbl_AdaptiveIndexDefrag_Working;
  1175. TRUNCATE TABLE dbo.tbl_AdaptiveIndexDefrag_Stats_Working;
  1176. END
  1177. ELSE
  1178. BEGIN
  1179. /* Print an error message if there are any indexes left to defragment according to the chosen criteria */
  1180. IF @debugMode = 1
  1181. RAISERROR('There are still fragmented indexes or out-of-date stats from last execution. Resuming...', 0, 42) WITH NOWAIT;
  1182. END
  1183. /* Scan the database(s) */
  1184. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working) = 0
  1185. BEGIN
  1186. IF @debugMode = 1
  1187. RAISERROR('Scanning database(s)...', 0, 42) WITH NOWAIT;
  1188. IF @debugMode = 1
  1189. RAISERROR(' Looping through list of databases and checking for fragmentation...', 0, 42) WITH NOWAIT;
  1190. /* Loop through list of databases */
  1191. WHILE (SELECT COUNT(*) FROM #tblIndexDefragDatabaseList WHERE scanStatus = 0) > 0
  1192. BEGIN
  1193. SELECT TOP 1 @dbID = dbID FROM #tblIndexDefragDatabaseList WHERE scanStatus = 0;
  1194. IF @debugMode = 1
  1195. SELECT @debugMessage = ' Working on ' + DB_NAME(@dbID) + '...';
  1196. IF @debugMode = 1
  1197. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1198. IF @dbScope IS NOT NULL AND @tblName IS NOT NULL
  1199. SELECT @tblNameFQN = @dbScope + '.' + @tblName
  1200. /* Set partitioning rebuild options; requires Enterprise Edition */
  1201. IF @dealMaxPartition IS NOT NULL AND @editionCheck = 0
  1202. SET @dealMaxPartition = NULL;
  1203. /* Truncate list of tables, indexes and partitions to prepare for a new scan */
  1204. TRUNCATE TABLE #tblIndexDefragScanWorking;
  1205. IF @debugMode = 1
  1206. RAISERROR(' Building list of objects in database...', 0, 42) WITH NOWAIT;
  1207. SELECT @TableScanSQL = 'SELECT si.[object_id], si.index_id, si.type, sp.partition_number, 0
  1208. FROM [' + DB_NAME(@dbID) + '].sys.indexes si
  1209. INNER JOIN [' + DB_NAME(@dbID) + '].sys.partitions sp ON si.[object_id] = sp.[object_id] AND si.index_id = sp.index_id
  1210. INNER JOIN [' + DB_NAME(@dbID) + '].sys.tables AS mst ON mst.[object_id] = si.[object_id]
  1211. INNER JOIN [' + DB_NAME(@dbID) + '].sys.schemas AS t ON t.[schema_id] = mst.[schema_id]' +
  1212. CASE WHEN @dbScope IS NULL AND @tblName IS NULL THEN CHAR(10) + 'LEFT JOIN [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Exceptions AS ide ON ide.[dbID] = ' + CONVERT(NVARCHAR(10),@dbID) + ' AND ide.objectID = si.[object_id] AND ide.indexID = si.index_id' ELSE '' END +
  1213. CHAR(10) + 'WHERE mst.is_ms_shipped = 0 ' + CASE WHEN @dbScope IS NULL AND @tblName IS NULL THEN 'AND (ide.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0 OR ide.exclusionMask IS NULL)' ELSE '' END + '
  1214. AND si.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + DB_NAME(@dbID) + '].sys.internal_tables AS sit)' +
  1215. CASE WHEN @dbScope IS NOT NULL AND @tblName IS NOT NULL THEN '
  1216. AND t.name + ''.'' + mst.name = ''' + @tblName + ''';' ELSE ';' END
  1217. INSERT INTO #tblIndexDefragScanWorking
  1218. EXEC sp_executesql @TableScanSQL;
  1219. /* Do we want to act on a subset of existing partitions? */
  1220. IF @dealMaxPartition = 1 OR @dealMaxPartition = 0
  1221. BEGIN
  1222. IF @debugMode = 1
  1223. RAISERROR(' Setting partition handling...', 0, 42) WITH NOWAIT;
  1224. SET @dealMaxPartitionSQL = 'SELECT [object_id], index_id, MAX(partition_number) AS [maxPartition] FROM [' + DB_NAME(@dbID) + '].sys.partitions WHERE partition_number > 1 AND [rows] > 0 GROUP BY object_id, index_id;';
  1225. INSERT INTO #tblIndexDefragmaxPartitionList
  1226. EXEC sp_executesql @dealMaxPartitionSQL;
  1227. END;
  1228. /* We don't want to defrag the right-most populated partition, so delete any records for partitioned indexes where partition = MAX(partition) */
  1229. IF @dealMaxPartition = 1 AND @editionCheck = 1
  1230. BEGIN
  1231. IF @debugMode = 1
  1232. RAISERROR(' Ignoring right-most populated partition...', 0, 42) WITH NOWAIT;
  1233. DELETE ids
  1234. FROM #tblIndexDefragScanWorking AS ids
  1235. INNER JOIN #tblIndexDefragmaxPartitionList AS mpl ON ids.objectID = mpl.objectID AND ids.indexID = mpl.indexID AND ids.partitionNumber = mpl.maxPartition;
  1236. END;
  1237. /* We only want to defrag the right-most populated partition, so delete any records for partitioned indexes where partition <> MAX(partition) */
  1238. IF @dealMaxPartition = 0 AND @editionCheck = 1
  1239. BEGIN
  1240. IF @debugMode = 1
  1241. RAISERROR(' Setting only right-most populated partition...', 0, 42) WITH NOWAIT;
  1242. DELETE ids
  1243. FROM #tblIndexDefragScanWorking AS ids
  1244. INNER JOIN #tblIndexDefragmaxPartitionList AS mpl ON ids.objectID = mpl.objectID AND ids.indexID = mpl.indexID AND ids.partitionNumber <> mpl.maxPartition;
  1245. END;
  1246. /* Determine which indexes to defragment using user-defined parameters */
  1247. IF @debugMode = 1
  1248. RAISERROR(' Filtering indexes according to ixtypeOption parameter...', 0, 42) WITH NOWAIT;
  1249. IF @ixtypeOption IS NULL
  1250. BEGIN
  1251. DELETE FROM #tblIndexDefragScanWorking
  1252. WHERE [type] = 0; -- ignore heaps
  1253. END
  1254. ELSE IF @ixtypeOption = 1
  1255. BEGIN
  1256. DELETE FROM #tblIndexDefragScanWorking
  1257. WHERE [type] NOT IN (1,5); -- keep only clustered index
  1258. END
  1259. ELSE IF @ixtypeOption = 0
  1260. BEGIN
  1261. DELETE FROM #tblIndexDefragScanWorking
  1262. WHERE [type] NOT IN (2,6); -- keep only non-clustered indexes
  1263. END;
  1264. -- Get rowstore indexes to work on
  1265. IF @debugMode = 1
  1266. RAISERROR(' Getting rowstore indexes...', 0, 42) WITH NOWAIT;
  1267. WHILE (SELECT COUNT(*) FROM #tblIndexDefragScanWorking WHERE is_done = 0 AND [type] IN (1,2)) > 0
  1268. BEGIN
  1269. SELECT TOP 1 @objectID = objectID, @indexID = indexID, @partitionNumber = partitionNumber
  1270. FROM #tblIndexDefragScanWorking WHERE is_done = 0 AND type IN (1,2)
  1271. BEGIN TRY
  1272. IF @getBlobfrag = 1
  1273. BEGIN
  1274. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working (dbID, dbName, objectID, indexID, partitionNumber, fragmentation, page_count, range_scan_count, record_count, scanDate)
  1275. SELECT @dbID AS [dbID], QUOTENAME(DB_NAME(ps.database_id)) AS [dbName], @objectID AS [objectID], @indexID AS [indexID], ps.partition_number AS [partitionNumber], SUM(ps.avg_fragmentation_in_percent) AS [fragmentation], SUM(ps.page_count) AS [page_count], os.range_scan_count, ps.record_count, GETDATE() AS [scanDate]
  1276. FROM sys.dm_db_index_physical_stats(@dbID, @objectID, @indexID, @partitionNumber, @scanMode) AS ps
  1277. LEFT JOIN sys.dm_db_index_operational_stats(@dbID, @objectID, @indexID, @partitionNumber) AS os ON ps.database_id = os.database_id AND ps.object_id = os.object_id AND ps.index_id = os.index_id AND ps.partition_number = os.partition_number
  1278. WHERE avg_fragmentation_in_percent >= @minFragmentation
  1279. AND ps.page_count >= @minPageCount
  1280. AND ps.index_level = 0 -- leaf-level nodes only, supports @scanMode
  1281. AND ps.alloc_unit_type_desc = 'IN_ROW_DATA' -- exclude blobs
  1282. GROUP BY ps.database_id, QUOTENAME(DB_NAME(ps.database_id)), ps.partition_number, os.range_scan_count, ps.record_count
  1283. OPTION (MAXDOP 2);
  1284. END
  1285. ELSE IF @getBlobfrag = 0
  1286. BEGIN
  1287. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working (dbID, dbName, objectID, indexID, partitionNumber, fragmentation, page_count, range_scan_count, record_count, scanDate)
  1288. SELECT @dbID AS [dbID], QUOTENAME(DB_NAME(ps.database_id)) AS [dbName], @objectID AS [objectID], @indexID AS [indexID], ps.partition_number AS [partitionNumber], SUM(ps.avg_fragmentation_in_percent) AS [fragmentation], SUM(ps.page_count) AS [page_count], os.range_scan_count, ps.record_count, GETDATE() AS [scanDate]
  1289. FROM sys.dm_db_index_physical_stats(@dbID, @objectID, @indexID, @partitionNumber, @scanMode) AS ps
  1290. LEFT JOIN sys.dm_db_index_operational_stats(@dbID, @objectID, @indexID, @partitionNumber) AS os ON ps.database_id = os.database_id AND ps.object_id = os.object_id AND ps.index_id = os.index_id AND ps.partition_number = os.partition_number
  1291. WHERE avg_fragmentation_in_percent >= @minFragmentation
  1292. AND ps.page_count >= @minPageCount
  1293. AND ps.index_level = 0 -- leaf-level nodes only, supports @scanMode
  1294. GROUP BY ps.database_id, QUOTENAME(DB_NAME(ps.database_id)), ps.partition_number, os.range_scan_count, ps.record_count
  1295. OPTION (MAXDOP 2);
  1296. END
  1297. END TRY
  1298. BEGIN CATCH
  1299. IF @debugMode = 1
  1300. BEGIN
  1301. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred while determining which rowstore indexes to defragment. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1302. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1303. --RAISERROR(' An error has occurred executing the pre-command! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 0, 42) WITH NOWAIT;
  1304. END
  1305. END CATCH
  1306. UPDATE #tblIndexDefragScanWorking
  1307. SET is_done = 1
  1308. WHERE objectID = @objectID AND indexID = @indexID AND partitionNumber = @partitionNumber
  1309. END;
  1310. -- Get columnstore indexes to work on
  1311. IF @debugMode = 1 AND @sqlmajorver >= 12
  1312. RAISERROR(' Getting columnstore indexes...', 0, 42) WITH NOWAIT;
  1313. IF @sqlmajorver >= 12
  1314. BEGIN
  1315. WHILE (SELECT COUNT(*) FROM #tblIndexDefragScanWorking WHERE is_done = 0 AND [type] IN (5,6)) > 0
  1316. BEGIN
  1317. SELECT TOP 1 @objectID = objectID, @indexID = indexID, @partitionNumber = partitionNumber
  1318. FROM #tblIndexDefragScanWorking WHERE is_done = 0 AND type IN (5,6)
  1319. BEGIN TRY
  1320. SELECT @ColumnStoreGetIXSQL = 'USE [' + DB_NAME(@dbID) + ']; SELECT @dbID_In, DB_NAME(@dbID_In), rg.object_id, rg.index_id, rg.partition_number, SUM((ISNULL(rg.deleted_rows,1)*100)/rg.total_rows) AS [fragmentation], SUM(ISNULL(rg.size_in_bytes,1)/1024/8) AS [simulated_page_count], SUM(rg.total_rows) AS total_rows, GETDATE() AS [scanDate]
  1321. FROM sys.column_store_row_groups rg
  1322. WHERE rg.object_id = @objectID_In
  1323. AND rg.index_id = @indexID_In
  1324. AND rg.partition_number = @partitionNumber_In
  1325. AND rg.state = 3 -- Only COMPRESSED row groups
  1326. GROUP BY rg.object_id, rg.index_id, rg.partition_number
  1327. HAVING SUM(ISNULL(rg.size_in_bytes,1)/1024/8) >= @minPageCount_In
  1328. OPTION (MAXDOP 2)'
  1329. SET @ColumnStoreGetIXSQL_Param = N'@dbID_In int, @objectID_In int, @indexID_In int, @partitionNumber_In smallint, @minPageCount_in int';
  1330. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Working (dbID, dbName, objectID, indexID, partitionNumber, fragmentation, page_count, record_count, scanDate)
  1331. EXECUTE sp_executesql @ColumnStoreGetIXSQL, @ColumnStoreGetIXSQL_Param, @dbID_In = @dbID, @objectID_In = @objectID, @indexID_In = @indexID, @partitionNumber_In = @partitionNumber, @minPageCount_in = @minPageCount;
  1332. END TRY
  1333. BEGIN CATCH
  1334. IF @debugMode = 1
  1335. BEGIN
  1336. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred while determining which columnstore indexes to defragment. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1337. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1338. --RAISERROR(' An error has occurred executing the pre-command! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 0, 42) WITH NOWAIT;
  1339. END
  1340. END CATCH
  1341. UPDATE #tblIndexDefragScanWorking
  1342. SET is_done = 1
  1343. WHERE objectID = @objectID AND indexID = @indexID AND partitionNumber = @partitionNumber
  1344. END
  1345. END;
  1346. IF @debugMode = 1
  1347. RAISERROR(' Looking up additional index information...', 0, 42) WITH NOWAIT;
  1348. /* Look up index status for various purposes */
  1349. SELECT @updateSQL = N'UPDATE ids
  1350. SET schemaName = QUOTENAME(s.name), objectName = QUOTENAME(o.name), indexName = QUOTENAME(i.name), is_primary_key = i.is_primary_key, fill_factor = i.fill_factor, is_disabled = i.is_disabled, is_padded = i.is_padded, is_hypothetical = i.is_hypothetical, has_filter = ' + CASE WHEN @sqlmajorver >= 10 THEN 'i.has_filter' ELSE '0' END + ', allow_page_locks = i.allow_page_locks, type = i.type
  1351. FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Working ids
  1352. INNER JOIN [' + DB_NAME(@dbID) + '].sys.objects AS o ON ids.objectID = o.object_id
  1353. INNER JOIN [' + DB_NAME(@dbID) + '].sys.indexes AS i ON o.object_id = i.object_id AND ids.indexID = i.index_id
  1354. INNER JOIN [' + DB_NAME(@dbID) + '].sys.schemas AS s ON o.schema_id = s.schema_id
  1355. WHERE o.object_id = ids.objectID AND i.index_id = ids.indexID AND i.type > 0
  1356. AND o.object_id NOT IN (SELECT sit.object_id FROM [' + DB_NAME(@dbID) + '].sys.internal_tables AS sit)
  1357. AND ids.[dbID] = ' + CAST(@dbID AS NVARCHAR(10));
  1358. EXECUTE sp_executesql @updateSQL;
  1359. IF @scanMode = 'LIMITED'
  1360. BEGIN
  1361. SELECT @updateSQL = N'UPDATE ids
  1362. SET record_count = [rows]
  1363. FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Working ids
  1364. INNER JOIN [' + DB_NAME(@dbID) + '].sys.partitions AS p ON ids.objectID = p.[object_id] AND ids.indexID = p.index_id AND ids.partitionNumber = p.partition_number
  1365. WHERE ids.[dbID] = ' + CAST(@dbID AS NVARCHAR(10));
  1366. EXECUTE sp_executesql @updateSQL;
  1367. END
  1368. IF @debugMode = 1
  1369. RAISERROR(' Looking up additional statistic information...', 0, 42) WITH NOWAIT;
  1370. /* Look up stats information for various purposes */
  1371. IF @tblName IS NULL
  1372. BEGIN
  1373. SELECT @updateSQL = N'USE [' + DB_NAME(@dbID) + '];
  1374. SELECT DISTINCT ' + CAST(@dbID AS NVARCHAR(10)) + ', ''' + QUOTENAME(DB_NAME(@dbID)) + ''', ss.[object_id], ss.stats_id, ' + CASE WHEN ((@sqlmajorver = 12 AND @sqlbuild >= 5000) OR @sqlmajorver > 12) THEN 'ISNULL(sp.partition_number,1),' ELSE '1,' END + '
  1375. QUOTENAME(s.name), QUOTENAME(so.name), QUOTENAME(ss.name), ss.[no_recompute], ' + CASE WHEN @sqlmajorver < 12 THEN '0 AS ' ELSE 'ss.' END + '[is_incremental], GETDATE() AS scanDate
  1376. FROM sys.stats ss
  1377. INNER JOIN sys.objects so ON ss.[object_id] = so.[object_id]
  1378. INNER JOIN sys.schemas s ON so.[schema_id] = s.[schema_id]
  1379. LEFT JOIN sys.indexes si ON ss.[object_id] = si.[object_id] and ss.name = si.name
  1380. ' + CASE WHEN ((@sqlmajorver = 12 AND @sqlbuild >= 5000) OR @sqlmajorver > 12) THEN 'CROSS APPLY sys.dm_db_stats_properties_internal(ss.[object_id], ss.stats_id) sp' ELSE '' END + '
  1381. WHERE is_ms_shipped = 0 ' + CASE WHEN @sqlmajorver >= 12 THEN 'AND ss.is_temporary = 0' ELSE '' END + '
  1382. AND so.[object_id] NOT IN (SELECT sit.[object_id] FROM sys.internal_tables AS sit)
  1383. AND so.[type] IN (''U'',''V'')
  1384. AND (si.[type] IS NULL OR si.[type] NOT IN (5,6,7))' -- Avoid error 35337
  1385. END
  1386. ELSE
  1387. BEGIN
  1388. DECLARE @tblNameOnly NVARCHAR(1000), @schemaNameOnly NVARCHAR(128)
  1389. SELECT @tblNameOnly = RIGHT(@tblName, LEN(@tblName) - CHARINDEX('.', @tblName, 1)), @schemaNameOnly = LEFT(@tblName, CHARINDEX('.', @tblName, 1) -1)
  1390. SELECT @updateSQL = N'USE [' + DB_NAME(@dbID) + '];
  1391. SELECT DISTINCT ' + CAST(@dbID AS NVARCHAR(10)) + ', ''' + QUOTENAME(DB_NAME(@dbID)) + ''', ss.[object_id], ss.stats_id, ' + CASE WHEN ((@sqlmajorver = 12 AND @sqlbuild >= 5000) OR @sqlmajorver > 12) THEN 'ISNULL(sp.partition_number,1),' ELSE '1,' END + '
  1392. QUOTENAME(s.name), QUOTENAME(so.name), QUOTENAME(ss.name), ss.[no_recompute], ' + CASE WHEN @sqlmajorver < 12 THEN '0 AS ' ELSE 'ss.' END + '[is_incremental], GETDATE() AS scanDate
  1393. FROM sys.stats ss
  1394. INNER JOIN sys.objects so ON ss.[object_id] = so.[object_id]
  1395. INNER JOIN sys.schemas s ON so.[schema_id] = s.[schema_id]
  1396. LEFT JOIN sys.indexes si ON ss.[object_id] = si.[object_id] and ss.name = si.name
  1397. ' + CASE WHEN @sqlmajorver >= 12 THEN 'CROSS APPLY sys.dm_db_stats_properties_internal(ss.[object_id], ss.stats_id) sp' ELSE '' END + '
  1398. WHERE is_ms_shipped = 0 ' + CASE WHEN @sqlmajorver >= 12 THEN 'AND ss.is_temporary = 0' ELSE '' END + '
  1399. AND so.[object_id] NOT IN (SELECT sit.[object_id] FROM sys.internal_tables AS sit)
  1400. AND s.name = ''' + @schemaNameOnly + '''
  1401. AND so.name = ''' + @tblNameOnly + '''
  1402. AND so.[type] IN (''U'',''V'')
  1403. AND (si.[type] IS NULL OR si.[type] NOT IN (5,6,7))' -- Avoid error 35337
  1404. END
  1405. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_Working (dbID, dbName, objectID, statsID, partitionNumber, schemaName, objectName, statsName, [no_recompute], [is_incremental], scanDate)
  1406. EXECUTE sp_executesql @updateSQL;
  1407. /* Keep track of which databases have already been scanned */
  1408. UPDATE #tblIndexDefragDatabaseList
  1409. SET scanStatus = 1
  1410. WHERE dbID = @dbID;
  1411. END;
  1412. /* Delete any records for disabled (except those disabled by the defrag cycle itself) or hypothetical indexes */
  1413. IF @debugMode = 1
  1414. RAISERROR(' Listing and removing disabled indexes (except those disabled by the defrag cycle itself) or hypothetical indexes from loop...', 0, 42) WITH NOWAIT;
  1415. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working AS ids WHERE ids.is_disabled = 1 OR ids.is_hypothetical = 1) > 0
  1416. DELETE ids
  1417. FROM dbo.tbl_AdaptiveIndexDefrag_Working AS ids
  1418. LEFT JOIN dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus AS ids_disable ON ids.objectID = ids_disable.objectID AND ids.indexID = ids_disable.indexID AND ids.[dbID] = ids_disable.dbID
  1419. WHERE ids.is_disabled = 1 OR ids.is_hypothetical = 1 AND ids_disable.indexID IS NULL;
  1420. IF @debugMode = 1
  1421. RAISERROR(' Updating Exception mask for any index that has a restriction on the days it CANNOT be defragmented...', 0, 42) WITH NOWAIT;
  1422. /* Update our Exception mask for any index that has a restriction on the days it CANNOT be defragmented
  1423. based on 1=Sunday, 2=Monday, 4=Tuesday, 8=Wednesday, 16=Thursday, 32=Friday, 64=Saturday, 127=AllWeek
  1424. */
  1425. UPDATE ids
  1426. SET ids.exclusionMask = ide.exclusionMask
  1427. FROM dbo.tbl_AdaptiveIndexDefrag_Working AS ids
  1428. INNER JOIN dbo.tbl_AdaptiveIndexDefrag_Exceptions AS ide ON ids.[dbID] = ide.[dbID] AND ids.objectID = ide.objectID AND ids.indexID = ide.indexID;
  1429. END;
  1430. IF @debugMode = 1
  1431. SELECT @debugMessage = 'Looping through batch list... There are ' + CAST(COUNT(DISTINCT indexName) AS NVARCHAR(10)) + ' indexes to defragment in ' + CAST(COUNT(DISTINCT dbName) AS NVARCHAR(10)) + ' database(s)!'
  1432. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  1433. WHERE defragDate IS NULL AND page_count BETWEEN @minPageCount AND ISNULL(@maxPageCount, page_count) AND exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0;
  1434. IF @debugMode = 1
  1435. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1436. IF @debugMode = 1
  1437. BEGIN
  1438. IF @updateStatsWhere = 1
  1439. BEGIN
  1440. SELECT @debugMessage = 'Looping through batch list... There are ' + CAST(COUNT(DISTINCT statsName) AS NVARCHAR(10)) + ' index related statistics to update in ' + CAST(COUNT(DISTINCT idss.dbName) AS NVARCHAR(10)) + ' database(s)!'
  1441. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss
  1442. INNER JOIN dbo.tbl_AdaptiveIndexDefrag_Working ids ON ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID
  1443. WHERE idss.schemaName = ids.schemaName AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0;
  1444. END
  1445. ELSE
  1446. BEGIN
  1447. SELECT @debugMessage = 'Looping through batch list... There are ' + CAST(COUNT(DISTINCT statsName) AS NVARCHAR(10)) + ' index related statistics to update in ' + CAST(COUNT(DISTINCT idss.dbName) AS NVARCHAR(10)) + ' database(s),'
  1448. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss
  1449. INNER JOIN dbo.tbl_AdaptiveIndexDefrag_Working ids ON ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID
  1450. WHERE idss.schemaName = ids.schemaName AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0;
  1451. SELECT @debugMessage = @debugMessage + ' plus ' + CAST(COUNT(DISTINCT statsName) AS NVARCHAR(10)) + ' other statistics to update in ' + CAST(COUNT(DISTINCT dbName) AS NVARCHAR(10)) + ' database(s)!'
  1452. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss
  1453. WHERE idss.updateDate IS NULL
  1454. AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0);
  1455. END
  1456. END
  1457. IF @debugMode = 1
  1458. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1459. IF @Exec_Print = 0 AND @printCmds = 1
  1460. BEGIN
  1461. RAISERROR(' Printing SQL statements...', 0, 42) WITH NOWAIT;
  1462. END;
  1463. /* Begin defragmentation loop */
  1464. WHILE (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE ((@Exec_Print = 1 AND defragDate IS NULL) OR (@Exec_Print = 0 AND defragDate IS NULL AND printStatus = 0)) AND exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0 AND page_count BETWEEN @minPageCount AND ISNULL(@maxPageCount, page_count)) > 0
  1465. BEGIN
  1466. /* Check to see if we need to exit loop because of our time limit */
  1467. IF ISNULL(@endDateTime, GETDATE()) < GETDATE()
  1468. RAISERROR('Time limit has been exceeded for this maintenance window!', 16, 42) WITH NOWAIT;
  1469. IF @debugMode = 1
  1470. RAISERROR(' Selecting an index to defragment...', 0, 42) WITH NOWAIT;
  1471. /* Select the index with highest priority, based on the values submitted; Verify date constraint for this index in the Exception table */
  1472. SET @getIndexSQL = N'SELECT TOP 1 @objectID_Out = objectID, @indexID_Out = indexID, @dbID_Out = dbID
  1473. FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NULL '
  1474. + CASE WHEN @Exec_Print = 0 THEN 'AND printStatus = 0 ' ELSE '' END + '
  1475. AND exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0
  1476. AND page_count BETWEEN @p_minPageCount AND ISNULL(@p_maxPageCount, page_count)
  1477. ORDER BY + ' + @defragOrderColumn + ' ' + @defragSortOrder;
  1478. SET @getIndexSQL_Param = N'@objectID_Out int OUTPUT, @indexID_Out int OUTPUT, @dbID_Out int OUTPUT, @p_minPageCount int, @p_maxPageCount int';
  1479. EXECUTE sp_executesql @getIndexSQL, @getIndexSQL_Param, @p_minPageCount = @minPageCount, @p_maxPageCount = @maxPageCount, @objectID_Out = @objectID OUTPUT, @indexID_Out = @indexID OUTPUT, @dbID_Out = @dbID OUTPUT;
  1480. IF @debugMode = 1
  1481. RAISERROR(' Getting partition count...', 0, 42) WITH NOWAIT;
  1482. /* Determine if the index is partitioned */
  1483. SELECT @partitionCount = MAX(partitionNumber)
  1484. FROM dbo.tbl_AdaptiveIndexDefrag_Working AS ids
  1485. WHERE objectID = @objectID AND indexID = @indexID AND dbID = @dbID
  1486. IF @debugMode = 1
  1487. RAISERROR(' Getting selected index information...', 0, 42) WITH NOWAIT;
  1488. /* Get object names and info */
  1489. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1490. BEGIN
  1491. SELECT TOP 1 @objectName = objectName, @schemaName = schemaName, @indexName = indexName, @dbName = dbName, @fragmentation = fragmentation, @partitionNumber = partitionNumber, @pageCount = page_count, @range_scan_count = range_scan_count, @is_primary_key = is_primary_key, @fill_factor = fill_factor, @record_count = record_count, @ixtype = [type], @is_disabled = is_disabled, @is_padded = is_padded, @has_filter = has_filter
  1492. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  1493. WHERE objectID = @objectID AND indexID = @indexID AND dbID = @dbID AND ((@Exec_Print = 1 AND defragDate IS NULL) OR (@Exec_Print = 0 AND defragDate IS NULL AND printStatus = 0));
  1494. END
  1495. ELSE
  1496. BEGIN
  1497. SELECT TOP 1 @objectName = objectName, @schemaName = schemaName, @indexName = indexName, @dbName = dbName, @fragmentation = fragmentation, @partitionNumber = NULL, @pageCount = page_count, @range_scan_count = range_scan_count, @is_primary_key = is_primary_key, @fill_factor = fill_factor, @record_count = record_count, @ixtype = [type], @is_disabled = is_disabled, @is_padded = is_padded, @has_filter = has_filter
  1498. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  1499. WHERE objectID = @objectID AND indexID = @indexID AND dbID = @dbID AND ((@Exec_Print = 1 AND defragDate IS NULL) OR (@Exec_Print = 0 AND defragDate IS NULL AND printStatus = 0));
  1500. END
  1501. /* Determine maximum partition number for use with stats update*/
  1502. IF @updateStats = 1
  1503. BEGIN
  1504. SELECT @maxpartitionNumber = MAX(partitionNumber), @minpartitionNumber = MIN(partitionNumber)
  1505. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  1506. WHERE objectID = @objectID AND indexID = @indexID AND dbID = @dbID;
  1507. END
  1508. IF @debugMode = 1
  1509. RAISERROR(' Checking if any LOBs exist...', 0, 42) WITH NOWAIT;
  1510. SET @containsLOB = 0
  1511. /* Determine if the index contains LOBs, with info from sys.types */
  1512. IF @ixtype = 2 AND @sqlmajorver < 11 -- Nonclustered and LOBs in INCLUDED columns? Up to SQL 2008R2
  1513. BEGIN
  1514. SELECT @LOB_SQL = 'SELECT @containsLOB_OUT = COUNT(*) FROM ' + @dbName + '.sys.columns c WITH (NOLOCK)
  1515. INNER JOIN ' + @dbName + '.sys.index_columns ic WITH (NOLOCK) ON c.[object_id] = ic.[object_id] AND c.column_id = ic.column_id
  1516. INNER JOIN ' + @dbName + '.sys.indexes i WITH (NOLOCK) ON i.[object_id] = ic.[object_id] and i.index_id = ic.index_id
  1517. WHERE max_length = -1 AND ic.is_included_column = 1
  1518. AND i.object_id = ' + CAST(@objectID AS NVARCHAR(10)) + ' AND i.index_id = ' + CAST(@indexID AS NVARCHAR(10)) + ';'
  1519. /* max_length = -1 for VARBINARY(MAX), VARCHAR(MAX), NVARCHAR(MAX), XML */
  1520. ,@LOB_SQL_Param = '@containsLOB_OUT int OUTPUT';
  1521. EXECUTE sp_executesql @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;
  1522. IF @debugMode = 1 AND @containsLOB > 0 AND @onlineRebuild = 1
  1523. RAISERROR(' Online rebuild not possible on indexes with LOBs in INCLUDED columns...', 0, 42) WITH NOWAIT;
  1524. END
  1525. IF @ixtype = 1 -- Clustered and has LOBs in table?
  1526. BEGIN
  1527. SELECT @LOB_SQL = 'SELECT @containsLOB_OUT = COUNT(*) FROM ' + @dbName + '.sys.columns c WITH (NOLOCK)
  1528. INNER JOIN ' + @dbName + '.sys.indexes i WITH (NOLOCK) ON c.[object_id] = i.[object_id]
  1529. WHERE system_type_id IN (34, 35, 99) ' + CASE WHEN @sqlmajorver < 11 THEN 'OR max_length = -1 ' ELSE '' END +
  1530. 'AND i.object_id = ' + CAST(@objectID AS NVARCHAR(10)) + ' AND i.index_id = ' + CAST(@indexID AS NVARCHAR(10)) + ';'
  1531. /* system_type_id = 34 for IMAGE, 35 for TEXT, 99 for NTEXT,
  1532. max_length = -1 for VARBINARY(MAX), VARCHAR(MAX), NVARCHAR(MAX), XML */
  1533. ,@LOB_SQL_Param = '@containsLOB_OUT int OUTPUT';
  1534. EXECUTE sp_executesql @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OUTPUT;
  1535. IF @debugMode = 1 AND @containsLOB > 0 AND @onlineRebuild = 1
  1536. RAISERROR(' Online rebuild not possible on clustered index when certain LOBs exist in table...', 0, 42) WITH NOWAIT;
  1537. END
  1538. IF @debugMode = 1 AND (@sqlmajorver >= 11 OR @ixtype IN (5,6))
  1539. RAISERROR(' Checking for Columnstore index...', 0, 42) WITH NOWAIT;
  1540. SET @containsColumnstore = 0
  1541. IF @ixtype NOT IN (5,6) -- Not already in the scope of a Columnstore index
  1542. AND @sqlmajorver >= 11 -- Parent table has Columnstore indexes?
  1543. BEGIN
  1544. SELECT @CStore_SQL = 'SELECT @containsColumnstore_OUT = COUNT(*) FROM ' + @dbName + '.sys.indexes i WITH (NOLOCK) WHERE i.object_id = ' + CAST(@objectID AS NVARCHAR(10)) + ' AND i.type IN (5,6);'
  1545. ,@CStore_SQL_Param = '@containsColumnstore_OUT int OUTPUT';
  1546. EXECUTE sp_executesql @CStore_SQL, @CStore_SQL_Param, @containsColumnstore_OUT = @containsColumnstore OUTPUT;
  1547. IF @debugMode = 1 AND @containsColumnstore > 0 AND @onlineRebuild = 1
  1548. RAISERROR(' Online rebuild not possible when parent table has Columnstore index...', 0, 42) WITH NOWAIT;
  1549. END
  1550. IF @ixtype IN (5,6)
  1551. BEGIN
  1552. SET @containsColumnstore = 1
  1553. IF @debugMode = 1 AND @containsColumnstore > 0 AND @onlineRebuild = 1
  1554. RAISERROR(' Online rebuild not possible on Columnstore indexes...', 0, 42) WITH NOWAIT;
  1555. END
  1556. IF @debugMode = 1
  1557. RAISERROR(' Checking if index does not allow page locks...', 0, 42) WITH NOWAIT;
  1558. /* Determine if page locks are not allowed; these must always rebuild; if @forceRescan = 0 then always check in real time in case it has changed*/
  1559. IF @forceRescan = 0
  1560. BEGIN
  1561. SELECT @allowPageLockSQL = 'SELECT @allowPageLocks_OUT = allow_page_locks FROM ' + @dbName + '.sys.indexes WHERE [object_id] = ' + CAST(@objectID AS NVARCHAR(10)) + ' AND [index_id] = ' + CAST(@indexID AS NVARCHAR(10)) + ';'
  1562. ,@allowPageLockSQL_Param = '@allowPageLocks_OUT bit OUTPUT';
  1563. EXECUTE sp_executesql @allowPageLockSQL, @allowPageLockSQL_Param, @allowPageLocks_OUT = @allowPageLocks OUTPUT;
  1564. END
  1565. ELSE
  1566. BEGIN
  1567. SELECT @allowPageLocks = [allow_page_locks] FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE objectID = @objectID AND indexID = @indexID AND [dbID] = @dbID
  1568. END
  1569. IF @debugMode = 1 AND @allowPageLocks = 0
  1570. RAISERROR(' Index does not allow page locks...', 0, 42) WITH NOWAIT;
  1571. IF @debugMode = 1
  1572. BEGIN
  1573. SELECT @debugMessage = ' Found ' + CONVERT(NVARCHAR(10), @fragmentation) + ' percent fragmentation on index ' + @indexName + '...';
  1574. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1575. END
  1576. IF @debugMode = 1
  1577. RAISERROR(' Building SQL statements...', 0, 42) WITH NOWAIT;
  1578. /* If there's not a lot of fragmentation, or if we have a LOB, we should reorganize.
  1579. Filtered indexes or indexes that do not allow page locks should always rebuild. */
  1580. IF (@fragmentation < @rebuildThreshold AND @ixtype IN (1,2) AND @has_filter = 0 AND @allowPageLocks = 1)
  1581. OR (@fragmentation < @rebuildThreshold_cs AND @ixtype IN (5,6))
  1582. BEGIN
  1583. SET @operationFlag = 0
  1584. /* Set Reorg command */
  1585. SET @sqlcommand = N'ALTER INDEX ' + @indexName + N' ON ' + @dbName + N'.' + @schemaName + N'.' + @objectName + N' REORGANIZE';
  1586. /* Set partition reorg options; requires Enterprise Edition; valid only if more than one partition exists */
  1587. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1588. SET @sqlcommand = @sqlcommand + N' PARTITION = ' + CAST(@partitionNumber AS NVARCHAR(10));
  1589. /* Set LOB reorg options; valid only if no more than one partition exists */
  1590. IF @dealLOB = 1 AND @partitionCount = 1 AND @ixtype IN (1,2)
  1591. SET @sqlcommand = @sqlcommand + N' WITH (LOB_COMPACTION = OFF)';
  1592. IF @dealLOB = 0 AND @partitionCount = 1 AND @ixtype IN (1,2)
  1593. SET @sqlcommand = @sqlcommand + N' WITH (LOB_COMPACTION = ON)';
  1594. /* Set Columnstore reorg option to compress all rowgroups, and not just closed ones */
  1595. IF @sqlmajorver >= 12 AND @dealROWG = 1 AND @ixtype IN (5,6)
  1596. SET @sqlcommand = @sqlcommand + N' WITH (COMPRESS_ALL_ROW_GROUPS = ON)';
  1597. SET @sqlcommand = @sqlcommand + N';';
  1598. END
  1599. /* If the index is heavily fragmented and doesn't contain any partitions,
  1600. or if the index does not allow page locks, or if it is a filtered index, rebuild it */
  1601. ELSE IF (@fragmentation >= @rebuildThreshold AND @ixtype IN (1,2))
  1602. OR (@fragmentation >= @rebuildThreshold_cs AND @ixtype IN (5,6))
  1603. OR @has_filter = 1 OR @allowPageLocks = 0
  1604. BEGIN
  1605. SET @rebuildcommand = N' REBUILD'
  1606. SET @operationFlag = 1
  1607. /* Set partition rebuild options; requires Enterprise Edition; valid only if more than one partition exists */
  1608. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1609. SET @rebuildcommand = @rebuildcommand + N' PARTITION = ' + CAST(@partitionNumber AS NVARCHAR(10));
  1610. --ELSE IF @dealMaxPartition IS NULL AND @editionCheck = 1 AND @partitionCount > 1
  1611. --SET @rebuildcommand = @rebuildcommand + N' PARTITION = ALL';
  1612. /* Disallow disabling indexes on partitioned tables when defraging a subset of existing partitions */
  1613. IF @dealMaxPartition IS NOT NULL AND @partitionCount > 1
  1614. SET @disableNCIX = 0
  1615. /* Set defrag options*/
  1616. SET @rebuildcommand = @rebuildcommand + N' WITH ('
  1617. /* Set index pad options; not compatible with partition operations*/
  1618. IF @is_padded = 1 AND (@dealMaxPartition IS NULL OR (@dealMaxPartition IS NOT NULL AND @partitionCount = 1))
  1619. SET @rebuildcommand = @rebuildcommand + N'PAD_INDEX = ON, '
  1620. /* Set online rebuild options; requires Enterprise Edition; not compatible with partition operations, Columnstore indexes in table and XML or Spatial indexes.
  1621. Up to SQL Server 2008R2, not compatible with clustered indexes with LOB columnns in table or non-clustered indexes with LOBs in INCLUDED columns.
  1622. In SQL Server 2012, not compatible with clustered indexes with LOB columnns in table.*/
  1623. IF @sqlmajorver <= 11 AND @onlineRebuild = 1 AND @editionCheck = 1
  1624. AND @ixtype IN (1,2) AND @containsLOB = 0
  1625. AND (@dealMaxPartition IS NULL OR (@dealMaxPartition IS NOT NULL AND @partitionCount = 1))
  1626. SET @rebuildcommand = @rebuildcommand + N'ONLINE = ON, ';
  1627. /* Set online rebuild options; requires Enterprise Edition; not compatible with partition operations, Columnstore indexes in table and XML or Spatial indexes.
  1628. In SQL Server 2014, not compatible with clustered indexes with LOB columnns in table, but compatible with partition operations.
  1629. Also, we can use Lock Priority with online indexing.*/
  1630. IF @sqlmajorver > 11 AND @onlineRebuild = 1 AND @editionCheck = 1
  1631. AND @ixtype IN (1,2) AND @containsLOB = 0
  1632. SELECT @rebuildcommand = @rebuildcommand + N'ONLINE = ON (WAIT_AT_LOW_PRIORITY (MAX_DURATION = ' + CONVERT(NVARCHAR(15), @onlinelocktimeout) + ', ABORT_AFTER_WAIT = ' + CASE WHEN @abortAfterwait = 0 THEN 'BLOCKERS' WHEN @abortAfterwait = 1 THEN 'SELF' ELSE 'NONE' END + ')), '
  1633. /* Set fill factor operation preferences; not compatible with partition operations and Columnstore indexes*/
  1634. IF @fillfactor = 1 AND (@dealMaxPartition IS NULL OR (@dealMaxPartition IS NOT NULL AND @partitionCount = 1)) AND @ixtype IN (1,2)
  1635. SET @rebuildcommand = @rebuildcommand + N'FILLFACTOR = ' + CONVERT(NVARCHAR, CASE WHEN @fill_factor = 0 THEN 100 ELSE @fill_factor END) + N', ';
  1636. IF @fillfactor = 0 AND (@dealMaxPartition IS NULL OR (@dealMaxPartition IS NOT NULL AND @partitionCount = 1)) AND @ixtype IN (1,2)
  1637. SET @rebuildcommand = @rebuildcommand + N'FILLFACTOR = 100, ';
  1638. /* Set sort operation preferences */
  1639. IF @sortInTempDB = 1 AND @ixtype IN (1,2)
  1640. SET @rebuildcommand = @rebuildcommand + N'SORT_IN_TEMPDB = ON, ';
  1641. IF @sortInTempDB = 0 AND @ixtype IN (1,2)
  1642. SET @rebuildcommand = @rebuildcommand + N'SORT_IN_TEMPDB = OFF, ';
  1643. /* Set NO_RECOMPUTE preference */
  1644. IF @ix_statsnorecompute = 1 AND @ixtype IN (1,2)
  1645. SET @sqlcommand = @sqlcommand + N' STATISTICS_NORECOMPUTE = ON, ';
  1646. IF @ix_statsnorecompute = 0 AND @ixtype IN (1,2)
  1647. SET @sqlcommand = @sqlcommand + N' STATISTICS_NORECOMPUTE = OFF, ';
  1648. /* Set processor restriction options; requires Enterprise Edition */
  1649. IF @maxDopRestriction IS NOT NULL AND @editionCheck = 1
  1650. SET @rebuildcommand = @rebuildcommand + N'MAXDOP = ' + CAST(@maxDopRestriction AS VARCHAR(2)) + N');';
  1651. ELSE
  1652. SET @rebuildcommand = @rebuildcommand + N');';
  1653. IF @rebuildcommand LIKE '% WITH ();'
  1654. SET @rebuildcommand = REPLACE(@rebuildcommand, ' WITH ()', '')
  1655. /* Set NCIX disable command, except for clustered index*/
  1656. SET @sqldisablecommand = NULL
  1657. IF @disableNCIX = 1 AND @ixtype = 2 AND @is_primary_key = 0
  1658. BEGIN
  1659. SET @sqldisablecommand = N'ALTER INDEX ' + @indexName + N' ON ' + @dbName + N'.' + @schemaName + N'.' + @objectName + ' DISABLE;';
  1660. END
  1661. /* Set update statistics command for index only, before rebuild, as rebuild performance is dependent on statistics (only working on non-partitioned tables)
  1662. http://blogs.msdn.com/b/psssql/archive/2009/03/18/be-aware-of-parallel-index-creation-performance-issues.aspx */
  1663. SET @sqlprecommand = NULL
  1664. /* Is stat incremental? */
  1665. SELECT TOP 1 @stats_isincremental = [is_incremental] FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1666. WHERE dbName = @dbName AND schemaName = @schemaName AND objectName = @objectName AND statsName = @indexName;
  1667. IF (@sqlmajorver < 13 OR @partitionCount = 1) AND @sqldisablecommand IS NULL AND @ixtype IN (1,2)
  1668. BEGIN
  1669. SET @sqlprecommand = N'UPDATE STATISTICS ' + @dbName + N'.' + @schemaName + N'.' + @objectName + N' (' + @indexName + N')'
  1670. SET @sqlprecommand = @sqlprecommand + N'; '
  1671. END
  1672. ELSE IF @sqlmajorver >= 13 AND @partitionCount > 1 AND @stats_isincremental = 1 AND @sqldisablecommand IS NULL AND @ixtype IN (1,2)
  1673. BEGIN
  1674. SET @sqlprecommand = N'UPDATE STATISTICS ' + @dbName + N'.' + @schemaName + N'.' + @objectName + N' (' + @indexName + N') WITH RESAMPLE ON PARTITIONS(' + CONVERT(NVARCHAR(10), @partitionNumber) + N')'
  1675. SET @sqlprecommand = @sqlprecommand + N'; '
  1676. END
  1677. /* Set Rebuild command */
  1678. SET @sqlcommand = N'ALTER INDEX ' + @indexName + N' ON ' + @dbName + N'.' + @schemaName + N'.' + @objectName + REPLACE(@rebuildcommand,', )', ')');
  1679. /* For offline rebuilds, set lock timeout if not default */
  1680. IF @onlineRebuild = 0 AND @offlinelocktimeout > -1 AND @offlinelocktimeout IS NOT NULL
  1681. SET @sqlcommand = 'SET LOCK_TIMEOUT ' + CONVERT(NVARCHAR(15), @offlinelocktimeout) + '; ' + @sqlcommand
  1682. END
  1683. ELSE
  1684. BEGIN
  1685. /* Print an error message if any indexes happen to not meet the criteria above */
  1686. IF @debugMode = 1
  1687. BEGIN
  1688. SET @debugMessage = 'We are unable to defrag index ' + @indexName + N' on table ' + @dbName + N'.' + @schemaName + N'.' + @objectName
  1689. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1690. END
  1691. END;
  1692. IF @operationFlag = 0 AND @sqlprecommand IS NOT NULL
  1693. SET @sqlprecommand = NULL
  1694. IF @operationFlag = 0 AND @sqldisablecommand IS NOT NULL
  1695. SET @sqldisablecommand = NULL
  1696. /* Are we executing the SQL? If so, do it */
  1697. IF @Exec_Print = 1
  1698. BEGIN
  1699. /* Get the time for logging purposes */
  1700. SET @dateTimeStart = GETDATE();
  1701. /* Start log actions */
  1702. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1703. BEGIN
  1704. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_log (dbID, dbName, objectID, objectName, indexID, indexName, partitionNumber, fragmentation, page_count, range_scan_count, fill_factor, dateTimeStart, sqlStatement)
  1705. SELECT @dbID, @dbName, @objectID, @objectName, @indexID, @indexName, @partitionNumber, @fragmentation, @pageCount, @range_scan_count, @fill_factor, @dateTimeStart, ISNULL(@sqlprecommand, '') + @sqlcommand;
  1706. END
  1707. ELSE
  1708. BEGIN
  1709. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_log (dbID, dbName, objectID, objectName, indexID, indexName, partitionNumber, fragmentation, page_count, range_scan_count, fill_factor, dateTimeStart, sqlStatement)
  1710. SELECT @dbID, @dbName, @objectID, @objectName, @indexID, @indexName, 1, @fragmentation, @pageCount, @range_scan_count, @fill_factor, @dateTimeStart, ISNULL(@sqlprecommand, '') + @sqlcommand;
  1711. END
  1712. SET @indexDefrag_id = SCOPE_IDENTITY();
  1713. IF @sqlprecommand IS NULL AND @sqldisablecommand IS NULL
  1714. BEGIN
  1715. SET @debugMessage = ' ' + @sqlcommand;
  1716. END
  1717. ELSE IF @sqlprecommand IS NOT NULL AND @sqldisablecommand IS NULL
  1718. BEGIN
  1719. SET @debugMessage = ' ' + @sqlprecommand + CHAR(10) + ' ' + @sqlcommand;
  1720. END;
  1721. ELSE IF @sqlprecommand IS NULL AND @sqldisablecommand IS NOT NULL
  1722. BEGIN
  1723. SET @debugMessage = ' ' + @sqldisablecommand + CHAR(10) + ' ' + @sqlcommand;
  1724. END;
  1725. ELSE IF @sqlprecommand IS NOT NULL AND @sqldisablecommand IS NOT NULL
  1726. BEGIN
  1727. SET @debugMessage = ' ' + @sqldisablecommand + CHAR(10) + ' ' + @sqlprecommand + CHAR(10) + ' ' + @sqlcommand;
  1728. END;
  1729. /* Print the commands we'll be executing, if specified to do so */
  1730. IF (@debugMode = 1 OR @printCmds = 1) AND @sqlcommand IS NOT NULL
  1731. BEGIN
  1732. RAISERROR(' Printing SQL statements...', 0, 42) WITH NOWAIT;
  1733. SET @debugMessage = ' Executing: ' + CHAR(10) + @debugMessage;
  1734. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1735. END;
  1736. /* Execute default update stats on index only. With better stats, index rebuild process will generally have better performance */
  1737. IF @operationFlag = 1
  1738. BEGIN
  1739. BEGIN TRY
  1740. EXECUTE sp_executesql @sqlprecommand;
  1741. SET @sqlprecommand = NULL
  1742. SET @dateTimeEnd = GETDATE();
  1743. /* Update log with completion time */
  1744. UPDATE dbo.tbl_AdaptiveIndexDefrag_log
  1745. SET dateTimeEnd = @dateTimeEnd, durationSeconds = DATEDIFF(second, @dateTimeStart, @dateTimeEnd)
  1746. WHERE indexDefrag_id = @indexDefrag_id AND dateTimeEnd IS NULL;
  1747. /* If rebuilding, update statistics log with completion time */
  1748. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1749. BEGIN
  1750. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log (dbID, dbName, objectID, objectName, statsID, statsName, [partitionNumber], [rows], [rows_sampled], [modification_counter], [no_recompute], dateTimeStart, dateTimeEnd, durationSeconds, sqlStatement)
  1751. SELECT @dbID, @dbName, @objectID, @objectName, statsID, statsName, @partitionNumber, -1, -1, -1, [no_recompute], @dateTimeStart, @dateTimeEnd, DATEDIFF(second, @dateTimeStart, @dateTimeEnd), @sqlcommand
  1752. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1753. WHERE objectID = @objectID AND dbID = @dbID
  1754. AND statsName = @indexName
  1755. AND ((@Exec_Print = 1 AND updateDate IS NULL) OR (@Exec_Print = 0 AND updateDate IS NULL AND printStatus = 0));
  1756. END
  1757. ELSE
  1758. BEGIN
  1759. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log (dbID, dbName, objectID, objectName, statsID, statsName, [partitionNumber], [rows], [rows_sampled], [modification_counter],[no_recompute], dateTimeStart, dateTimeEnd, durationSeconds, sqlStatement)
  1760. SELECT @dbID, @dbName, @objectID, @objectName, statsID, statsName, 1, -1, -1, -1, [no_recompute], @dateTimeStart, @dateTimeEnd, DATEDIFF(second, @dateTimeStart, @dateTimeEnd), @sqlcommand
  1761. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1762. WHERE objectID = @objectID AND dbID = @dbID
  1763. AND statsName = @indexName
  1764. AND ((@Exec_Print = 1 AND updateDate IS NULL) OR (@Exec_Print = 0 AND updateDate IS NULL AND printStatus = 0));
  1765. END
  1766. END TRY
  1767. BEGIN CATCH
  1768. /* Update log with error message */
  1769. UPDATE dbo.tbl_AdaptiveIndexDefrag_log
  1770. SET dateTimeEnd = GETDATE(), durationSeconds = -1, errorMessage = 'Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing the pre-command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1771. WHERE indexDefrag_id = @indexDefrag_id AND dateTimeEnd IS NULL;
  1772. IF @debugMode = 1
  1773. BEGIN
  1774. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing the pre-command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1775. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1776. --RAISERROR(' An error has occurred executing the pre-command! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 0, 42) WITH NOWAIT;
  1777. END
  1778. END CATCH
  1779. END;
  1780. /* Execute NCIX disable command */
  1781. IF @operationFlag = 1 AND @disableNCIX = 1 AND @indexID > 1 AND (@sqldisablecommand IS NOT NULL OR LEN(@sqldisablecommand) > 0)
  1782. BEGIN
  1783. BEGIN TRY
  1784. EXECUTE sp_executesql @sqldisablecommand;
  1785. /* Insert into working table for disabled state control */
  1786. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus (dbID, objectID, indexID, [is_disabled], dateTimeChange)
  1787. SELECT @dbID, @objectID, @indexID, 1, GETDATE()
  1788. END TRY
  1789. BEGIN CATCH
  1790. /* Delete from working table for disabled state control */
  1791. DELETE FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus
  1792. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID;
  1793. IF @debugMode = 1
  1794. BEGIN
  1795. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing the disable index command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1796. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1797. --RAISERROR(' An error has occurred executing the disable index command! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 0, 42) WITH NOWAIT;
  1798. END
  1799. END CATCH
  1800. END;
  1801. /* Execute defrag! */
  1802. BEGIN TRY
  1803. EXECUTE sp_executesql @sqlcommand;
  1804. SET @dateTimeEnd = GETDATE();
  1805. UPDATE dbo.tbl_AdaptiveIndexDefrag_log
  1806. /* Update log with completion time */
  1807. SET dateTimeEnd = @dateTimeEnd, durationSeconds = DATEDIFF(second, @dateTimeStart, @dateTimeEnd)
  1808. WHERE indexDefrag_id = @indexDefrag_id AND dateTimeEnd IS NULL;
  1809. IF @operationFlag = 1 AND @disableNCIX = 1 AND @indexID > 1
  1810. BEGIN
  1811. /* Delete from working table for disabled state control */
  1812. DELETE FROM dbo.tbl_AdaptiveIndexDefrag_IxDisableStatus
  1813. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID;
  1814. END;
  1815. END TRY
  1816. BEGIN CATCH
  1817. /* Update log with error message */
  1818. UPDATE dbo.tbl_AdaptiveIndexDefrag_log
  1819. SET dateTimeEnd = GETDATE(), durationSeconds = -1, errorMessage = 'Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1820. WHERE indexDefrag_id = @indexDefrag_id AND dateTimeEnd IS NULL;
  1821. IF @debugMode = 1
  1822. BEGIN
  1823. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  1824. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1825. --RAISERROR(' An error has occurred executing this command! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 0, 42) WITH NOWAIT;
  1826. END
  1827. END CATCH
  1828. /* Update working table and resume loop */
  1829. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1830. BEGIN
  1831. UPDATE dbo.tbl_AdaptiveIndexDefrag_Working
  1832. SET defragDate = ISNULL(@dateTimeEnd, GETDATE()), printStatus = 1
  1833. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID AND partitionNumber = @partitionNumber;
  1834. END
  1835. ELSE
  1836. BEGIN
  1837. UPDATE dbo.tbl_AdaptiveIndexDefrag_Working
  1838. SET defragDate = ISNULL(@dateTimeEnd, GETDATE()), printStatus = 1
  1839. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID;
  1840. END
  1841. IF @operationFlag = 1
  1842. BEGIN
  1843. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1844. SET updateDate = ISNULL(@dateTimeEnd, GETDATE()), printStatus = 1
  1845. WHERE objectID = @objectID AND dbID = @dbID AND statsName = @indexName;
  1846. END
  1847. /* Just a little breather for the server */
  1848. WAITFOR DELAY @defragDelay;
  1849. END;
  1850. ELSE IF @Exec_Print = 0
  1851. BEGIN
  1852. IF @operationFlag = 0 AND @sqlprecommand IS NOT NULL
  1853. SET @sqlprecommand = NULL
  1854. IF @sqlprecommand IS NULL AND (@sqldisablecommand IS NULL OR @sqldisablecommand = '')
  1855. BEGIN
  1856. SET @debugMessage = ' ' + @sqlcommand;
  1857. END
  1858. ELSE IF @sqlprecommand IS NOT NULL AND (@sqldisablecommand IS NULL OR @sqldisablecommand = '')
  1859. BEGIN
  1860. SET @debugMessage = ' ' + @sqlprecommand + CHAR(10) + ' ' + @sqlcommand;
  1861. END;
  1862. ELSE IF @sqlprecommand IS NULL AND (@sqldisablecommand IS NOT NULL OR LEN(@sqldisablecommand) > 0)
  1863. BEGIN
  1864. SET @debugMessage = ' ' + @sqldisablecommand + CHAR(10) + ' ' + @sqlcommand;
  1865. END;
  1866. ELSE IF @sqlprecommand IS NOT NULL AND (@sqldisablecommand IS NOT NULL OR LEN(@sqldisablecommand) > 0)
  1867. BEGIN
  1868. SET @debugMessage = ' ' + @sqldisablecommand + CHAR(10) + ' ' + @sqlprecommand + CHAR(10) + ' ' + @sqlcommand;
  1869. END;
  1870. /* Print the commands we're executing if specified to do so */
  1871. IF (@debugMode = 1 OR @printCmds = 1) AND @sqlcommand IS NOT NULL
  1872. BEGIN
  1873. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1874. END
  1875. /* Update working table and resume loop */
  1876. IF @partitionCount > 1 AND @dealMaxPartition IS NOT NULL AND @editionCheck = 1
  1877. BEGIN
  1878. UPDATE dbo.tbl_AdaptiveIndexDefrag_Working
  1879. SET printStatus = 1
  1880. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID AND partitionNumber = @partitionNumber;
  1881. END
  1882. ELSE
  1883. BEGIN
  1884. UPDATE dbo.tbl_AdaptiveIndexDefrag_Working
  1885. SET printStatus = 1
  1886. WHERE dbID = @dbID AND objectID = @objectID AND indexID = @indexID;
  1887. END
  1888. IF @operationFlag = 1
  1889. BEGIN
  1890. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1891. SET printStatus = 1
  1892. WHERE objectID = @objectID AND dbID = @dbID AND statsName = @indexName;
  1893. END;
  1894. END;
  1895. IF @operationFlag = 0 AND @updateStats = 1 -- When reorganizing, update stats afterwards
  1896. AND @updateStatsWhere = 0 AND @ixtype NOT IN (5,6,7)
  1897. BEGIN
  1898. IF @debugMode = 1
  1899. RAISERROR(' Updating index related statistics using finer thresholds (if any)...', 0, 42) WITH NOWAIT;
  1900. /* Handling index related statistics */
  1901. IF @debugMode = 1
  1902. RAISERROR(' Selecting a statistic to update...', 0, 42) WITH NOWAIT;
  1903. /* Select the stat */
  1904. BEGIN TRY
  1905. SET @getStatSQL = N'SELECT TOP 1 @statsID_Out = idss.statsID FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss WHERE idss.updateDate IS NULL ' + CASE WHEN @Exec_Print = 0 THEN 'AND idss.printStatus = 0 ' ELSE '' END + ' AND idss.[dbID] = ' + CONVERT(NVARCHAR, @dbID) + ' AND idss.statsName = ''' + @indexName + '''' + ' AND idss.objectID = ' + CONVERT(NVARCHAR, @objectID) + ' AND EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.defragDate IS NOT NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)';
  1906. SET @getStatSQL_Param = N'@statsID_Out int OUTPUT'
  1907. EXECUTE sp_executesql @getStatSQL, @getStatSQL_Param, @statsID_Out = @statsID OUTPUT;
  1908. END TRY
  1909. BEGIN CATCH
  1910. IF @debugMode = 1
  1911. BEGIN
  1912. SET @debugMessage = ' Error ' + CONVERT(VARCHAR(20),ERROR_NUMBER()) + ' has occurred while determining which statistic to update. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS VARCHAR(10)) + ')'
  1913. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1914. END
  1915. END CATCH
  1916. IF @debugMode = 1
  1917. RAISERROR(' Getting information on selected statistic...', 0, 42) WITH NOWAIT;
  1918. /* Get object name and auto update setting */
  1919. SELECT TOP 1 @statsName = statsName, @partitionNumber = partitionNumber, @stats_norecompute = [no_recompute], @stats_isincremental = [is_incremental]
  1920. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  1921. WHERE objectID = @objectID AND statsID = @statsID AND [dbID] = @dbID;
  1922. IF @debugMode = 1
  1923. BEGIN
  1924. SET @debugMessage = ' Determining modification row counter for statistic ' + @statsName + ' on table or view ' + @objectName + ' of DB ' + @dbName + '...';
  1925. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1926. END;
  1927. /* Determine modification row counter to ascertain if update stats is required */
  1928. IF ((@sqlmajorver = 12 AND @sqlbuild >= 5000) OR (@sqlmajorver = 13 AND @sqlbuild >= 4000) OR @sqlmajorver > 13) AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE')
  1929. BEGIN
  1930. IF @debugMode = 1
  1931. RAISERROR(' Using sys.dm_db_incremental_stats_properties DMF...', 0, 42) WITH NOWAIT;
  1932. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_incremental_stats_properties(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ') WHERE partition_number = @partitionNumber_In;'
  1933. END
  1934. ELSE IF ((@sqlmajorver = 12 AND @sqlbuild < 5000) OR (@sqlmajorver = 13 AND @sqlbuild < 4000)) AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE')
  1935. BEGIN
  1936. IF @debugMode = 1
  1937. RAISERROR(' Using sys.dm_db_stats_properties_internal DMF...', 0, 42) WITH NOWAIT;
  1938. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_stats_properties_internal(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ') WHERE partition_number = @partitionNumber_In;'
  1939. END
  1940. ELSE IF ((@sqlmajorver = 10 AND @sqlminorver = 50 AND @sqlbuild >= 4000) OR (@sqlmajorver = 11 AND @sqlbuild >= 3000) OR @sqlmajorver >= 12) AND (@stats_isincremental = 0 OR UPPER(@statsSample) = 'FULLSCAN')
  1941. BEGIN
  1942. IF @debugMode = 1
  1943. RAISERROR(' Using sys.dm_db_stats_properties DMF...', 0, 42) WITH NOWAIT;
  1944. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_stats_properties(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ');'
  1945. END
  1946. ELSE
  1947. BEGIN
  1948. IF @debugMode = 1
  1949. RAISERROR(' Using sys.sysindexes...', 0, 42) WITH NOWAIT;
  1950. SELECT TOP 1 @surrogateStatsID = indexID FROM dbo.tbl_AdaptiveIndexDefrag_Working (NOLOCK) WHERE objectID = @statsobjectID AND indexName = @statsName
  1951. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = SUM(ISNULL(rowmodctr,0)), @rows_Out = ISNULL(rowcnt,0), @rows_sampled_Out = -1 FROM sys.sysindexes WHERE id = ' + CAST(@statsobjectID AS NVARCHAR(10)) + ' AND indid = ' + CAST(@surrogateStatsID AS NVARCHAR(10)) + ' AND rowmodctr > 0;'
  1952. END
  1953. SET @rowmodctrSQL_Param = N'@partitionNumber_In smallint, @rowmodctr_Out bigint OUTPUT, @rows_Out bigint OUTPUT, @rows_sampled_Out bigint OUTPUT'
  1954. BEGIN TRY
  1955. EXECUTE sp_executesql @rowmodctrSQL, @rowmodctrSQL_Param, @partitionNumber_In = @partitionNumber, @rowmodctr_Out = @rowmodctr OUTPUT, @rows_Out = @rows OUTPUT, @rows_sampled_Out = @rows_sampled OUTPUT;
  1956. SET @rowmodctr = (SELECT ISNULL(@rowmodctr, 0));
  1957. END TRY
  1958. BEGIN CATCH
  1959. IF @debugMode = 1
  1960. BEGIN
  1961. SET @debugMessage = ' Error ' + CONVERT(VARCHAR(20),ERROR_NUMBER()) + ' has occurred while determining row modification counter. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS VARCHAR(10)) + ')'
  1962. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1963. END
  1964. END CATCH
  1965. IF @rows IS NOT NULL AND @rows > 0
  1966. SET @record_count = @rows
  1967. IF @debugMode = 1
  1968. BEGIN
  1969. SELECT @debugMessage = ' Found a row modification counter of ' + CONVERT(NVARCHAR(10), @rowmodctr) + ' and ' + CONVERT(NVARCHAR(10), @record_count) + ' rows' + CASE WHEN @stats_isincremental = 1 THEN ' on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  1970. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  1971. END
  1972. /* Because we are reorganizing, we will update statistics if they have changed since last update with same threshold as TF2371.
  1973. Default rules for auto update stats are:
  1974. If the cardinality for a table is greater than 6, but less than or equal to 500, update status every 500 modifications.
  1975. If the cardinality for a table is greater than 500, update statistics when (500 + 20 percent of the table) changes have occurred.
  1976. Reference: http://support.microsoft.com/kb/195565
  1977. */
  1978. IF (@statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.001 AND 100.0 AND @statsMinRows IS NULL AND (@rowmodctr*100)/@record_count >= @statsThreshold)
  1979. OR (@statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.001 AND 100.0 AND @statsMinRows IS NOT NULL AND @record_count >= @statsMinRows AND (@rowmodctr*100)/@record_count >= @statsThreshold)
  1980. OR (@statsThreshold IS NULL AND (
  1981. (@record_count BETWEEN 6 AND 500 AND @rowmodctr >= 500) OR -- like the default
  1982. (@record_count BETWEEN 501 AND 10000 AND (@rowmodctr >= (@record_count*20)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 20 percent or simulate TF 2371
  1983. (@record_count BETWEEN 10001 AND 100000 AND (@rowmodctr >= (@record_count*15)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 15 percent or simulate TF 2371
  1984. (@record_count BETWEEN 100001 AND 1000000 AND (@rowmodctr >= (@record_count*10)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 10 percent or simulate TF 2371
  1985. (@record_count >= 1000001 AND (@rowmodctr >= (@record_count*5)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) -- 500 + 5 percent or simulate TF 2371
  1986. ))
  1987. BEGIN
  1988. SET @sqlcommand2 = N'UPDATE STATISTICS ' + @dbName + N'.'+ @schemaName + N'.' + @objectName + N' (' + @statsName + N')'
  1989. IF UPPER(@statsSample) = 'FULLSCAN' AND (@partitionNumber = 1 OR @partitionNumber = @maxpartitionNumber)
  1990. SET @sqlcommand2 = @sqlcommand2 + N' WITH FULLSCAN'
  1991. ELSE IF UPPER(@statsSample) = 'RESAMPLE'
  1992. SET @sqlcommand2 = @sqlcommand2 + N' WITH RESAMPLE'
  1993. IF @partitionCount > 1 AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE') AND UPPER(@sqlcommand2) LIKE '%WITH%'
  1994. SET @sqlcommand2 = @sqlcommand2 + N' ON PARTITIONS(' + CONVERT(NVARCHAR(10), @partitionNumber) + N');'
  1995. ELSE IF @partitionCount > 1 AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE') AND UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  1996. SET @sqlcommand2 = @sqlcommand2 + N' WITH RESAMPLE ON PARTITIONS(' + CONVERT(NVARCHAR(10), @partitionNumber) + N')'
  1997. IF @stats_norecompute = 1 AND UPPER(@sqlcommand2) LIKE '%WITH%'
  1998. SET @sqlcommand2 = @sqlcommand2 + N' ,NORECOMPUTE'
  1999. ELSE IF @stats_norecompute = 1 AND @sqlcommand2 NOT LIKE '%WITH%'
  2000. SET @sqlcommand2 = @sqlcommand2 + N' WITH NORECOMPUTE'
  2001. /* For list of incremental stats unsupported scenarios check https://msdn.microsoft.com/en-us/library/ms187348.aspx */
  2002. IF @partitionCount > 1 AND @statsIncremental = 1 AND @has_filter = 0
  2003. BEGIN
  2004. IF UPPER(@sqlcommand2) LIKE '%WITH%'
  2005. SET @sqlcommand2 = @sqlcommand2 + N' ,INCREMENTAL = ON'
  2006. ELSE IF UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2007. SET @sqlcommand2 = @sqlcommand2 + N'WITH INCREMENTAL = ON'
  2008. END
  2009. ELSE IF @statsIncremental = 0
  2010. BEGIN
  2011. IF UPPER(@sqlcommand2) LIKE '%WITH%'
  2012. SET @sqlcommand2 = @sqlcommand2 + N', INCREMENTAL = OFF'
  2013. ELSE IF UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2014. SET @sqlcommand2 = @sqlcommand2 + N'WITH INCREMENTAL = OFF'
  2015. END
  2016. SET @sqlcommand2 = @sqlcommand2 + N';'
  2017. END
  2018. ELSE
  2019. BEGIN
  2020. SET @sqlcommand2 = NULL
  2021. END
  2022. /* Are we executing the SQL? If so, do it */
  2023. IF @Exec_Print = 1 AND @sqlcommand2 IS NOT NULL
  2024. BEGIN
  2025. SET @debugMessage = ' ' + @sqlcommand2;
  2026. /* Print the commands we'll be executing, if specified to do so */
  2027. IF (@printCmds = 1 OR @debugMode = 1)
  2028. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2029. /* Get the time for logging purposes */
  2030. SET @dateTimeStart = GETDATE();
  2031. /* Log actions */
  2032. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log (dbID, dbName, objectID, objectName, statsID, statsName, [partitionNumber], [rows], rows_sampled, modification_counter, [no_recompute], dateTimeStart, sqlStatement)
  2033. SELECT @dbID, @dbName, @objectID, @objectName, @statsID, @statsName, @partitionNumber, @rows, @rows_sampled, @rowmodctr, @stats_norecompute, @dateTimeStart, @sqlcommand2;
  2034. SET @statsUpdate_id = SCOPE_IDENTITY();
  2035. /* Wrap execution attempt in a TRY/CATCH and log any errors that occur */
  2036. IF @operationFlag = 0
  2037. BEGIN
  2038. BEGIN TRY
  2039. /* Execute update! */
  2040. EXECUTE sp_executesql @sqlcommand2;
  2041. SET @dateTimeEnd = GETDATE();
  2042. SET @sqlcommand2 = NULL
  2043. /* Update log with completion time */
  2044. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2045. SET dateTimeEnd = @dateTimeEnd, durationSeconds = DATEDIFF(second, @dateTimeStart, @dateTimeEnd)
  2046. WHERE statsUpdate_id = @statsUpdate_id AND partitionNumber = @partitionNumber AND dateTimeEnd IS NULL;
  2047. /* Update working table */
  2048. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2049. SET updateDate = GETDATE(), printStatus = 1
  2050. WHERE dbID = @dbID AND objectID = @objectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2051. END TRY
  2052. BEGIN CATCH
  2053. /* Update log with error message */
  2054. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2055. SET dateTimeEnd = GETDATE(), durationSeconds = -1, errorMessage = 'Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  2056. WHERE statsUpdate_id = @statsUpdate_id AND partitionNumber = @partitionNumber AND dateTimeEnd IS NULL;
  2057. /* Update working table */
  2058. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2059. SET updateDate = GETDATE(), printStatus = 1
  2060. WHERE dbID = @dbID AND objectID = @objectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2061. IF @debugMode = 1
  2062. BEGIN
  2063. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  2064. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2065. --RAISERROR(' An error has occurred executing this command. Please review the tbl_AdaptiveIndexDefrag_Stats_log table for details.', 0, 42) WITH NOWAIT;
  2066. END
  2067. END CATCH
  2068. END
  2069. END
  2070. ELSE IF @Exec_Print = 1 AND @sqlcommand2 IS NULL
  2071. BEGIN
  2072. IF @debugMode = 1
  2073. BEGIN
  2074. SELECT @debugMessage = ' No need to update statistic ' + @statsName + ' on table or view ' + @objectName + ' of DB ' + @dbName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2075. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2076. END
  2077. IF @printCmds = 1 AND @debugMode = 0
  2078. BEGIN
  2079. SELECT @debugMessage = ' -- No need to update statistic ' + @statsName + ' on table or view ' + @objectName + ' of DB ' + @dbName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2080. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2081. END
  2082. /* Update working table */
  2083. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2084. SET updateDate = GETDATE(), printStatus = 1
  2085. WHERE dbID = @dbID AND objectID = @objectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2086. END
  2087. ELSE IF @Exec_Print = 0
  2088. BEGIN
  2089. IF @debugMode = 1 AND @sqlcommand2 IS NULL
  2090. BEGIN
  2091. SET @debugMessage = ' No need to update statistic ' + @statsName + ' on table or view ' + @objectName + ' of DB ' + @dbName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2092. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2093. END
  2094. /* Print the commands we're executing if specified to do so */
  2095. IF (@printCmds = 1 OR @debugMode = 1) AND @sqlcommand2 IS NOT NULL
  2096. BEGIN
  2097. SET @debugMessage = ' ' + @sqlcommand2;
  2098. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2099. END
  2100. IF @printCmds = 1 AND @debugMode = 0 AND @sqlcommand2 IS NULL
  2101. BEGIN
  2102. SET @debugMessage = ' -- No need to update statistic ' + @statsName + ' on table or view ' + @objectName + ' of DB ' + @dbName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2103. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2104. END
  2105. /* Update working table */
  2106. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2107. SET printStatus = 1
  2108. WHERE dbID = @dbID AND objectID = @objectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2109. END
  2110. END
  2111. END;
  2112. /* Handling all the other statistics not covered before*/
  2113. IF @updateStats = 1 -- When reorganizing, update stats afterwards
  2114. AND @updateStatsWhere = 0 -- @updateStatsWhere = 0 then table-wide statistics;
  2115. AND (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss WHERE ((@Exec_Print = 1 AND idss.updateDate IS NULL) OR (@Exec_Print = 0 AND idss.updateDate IS NULL AND idss.printStatus = 0))) > 0 --AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)) > 0 -- If any unhandled statistics remain
  2116. BEGIN
  2117. IF @debugMode = 1
  2118. RAISERROR(' Updating all other unhandled statistics using finer thresholds (if any)...', 0, 42) WITH NOWAIT;
  2119. WHILE (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss WHERE ((@Exec_Print = 1 AND idss.updateDate IS NULL) OR (@Exec_Print = 0 AND idss.updateDate IS NULL AND idss.printStatus = 0))) > 0 --AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)) > 0
  2120. BEGIN
  2121. /* Check to see if we need to exit loop because of our time limit */
  2122. IF ISNULL(@endDateTime, GETDATE()) < GETDATE()
  2123. RAISERROR('Time limit has been exceeded for this maintenance window!', 16, 42) WITH NOWAIT;
  2124. IF @debugMode = 1
  2125. RAISERROR(' Selecting a statistic to update...', 0, 42) WITH NOWAIT;
  2126. /* Select the stat */
  2127. IF @Exec_Print = 1
  2128. BEGIN
  2129. SELECT TOP 1 @statsID = idss.statsID, @dbID = idss.dbID, @statsobjectID = idss.objectID, @dbName = idss.dbName, @statsobjectName = idss.objectName, @statsschemaName = idss.schemaName, @partitionNumber = idss.partitionNumber
  2130. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss
  2131. WHERE idss.updateDate IS NULL --AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)
  2132. END
  2133. ELSE IF @Exec_Print = 0
  2134. BEGIN
  2135. SELECT TOP 1 @statsID = idss.statsID, @dbID = idss.dbID, @statsobjectID = idss.objectID, @dbName = idss.dbName, @statsobjectName = idss.objectName, @statsschemaName = idss.schemaName, @partitionNumber = idss.partitionNumber
  2136. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss
  2137. WHERE idss.updateDate IS NULL AND idss.printStatus = 0 --AND NOT EXISTS (SELECT TOP 1 objectID FROM dbo.tbl_AdaptiveIndexDefrag_Working ids WHERE ids.[dbID] = idss.[dbID] AND ids.objectID = idss.objectID AND idss.statsName = ids.indexName AND idss.updateDate IS NULL AND ids.exclusionMask & POWER(2, DATEPART(weekday, GETDATE())-1) = 0)
  2138. END
  2139. /* Get stat associated table record count */
  2140. BEGIN TRY
  2141. SELECT @getStatSQL = N'USE ' + @dbName + '; SELECT TOP 1 @record_count_Out = p.[rows] FROM [' + DB_NAME() + '].dbo.tbl_AdaptiveIndexDefrag_Stats_Working idss INNER JOIN sys.partitions AS p ON idss.objectID = p.[object_id] AND idss.partitionNumber = p.partition_number WHERE idss.updateDate IS NULL ' + CASE WHEN @Exec_Print = 0 THEN 'AND idss.printStatus = 0 ' ELSE '' END + ' AND idss.statsID = @statsID_In AND idss.dbID = @dbID_In AND idss.objectID = @statsobjectID_In'
  2142. SET @getStatSQL_Param = N'@statsID_In int, @dbID_In int, @statsobjectID_In int, @record_count_Out bigint OUTPUT'
  2143. EXECUTE sp_executesql @getStatSQL, @getStatSQL_Param, @statsID_In = @statsID, @dbID_In = @dbID, @statsobjectID_In = @statsobjectID, @record_count_Out = @record_count OUTPUT;
  2144. END TRY
  2145. BEGIN CATCH
  2146. IF @debugMode = 1
  2147. BEGIN
  2148. SET @debugMessage = ' Error ' + CONVERT(VARCHAR(20),ERROR_NUMBER()) + ' has occurred while getting stat associated table row count. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS VARCHAR(10)) + ')'
  2149. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2150. END
  2151. END CATCH
  2152. IF @debugMode = 1
  2153. RAISERROR(' Getting information on selected statistic...', 0, 42) WITH NOWAIT;
  2154. /* Get object name and auto update setting */
  2155. SELECT TOP 1 @statsName = statsName, @stats_norecompute = [no_recompute], @stats_isincremental = [is_incremental]
  2156. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2157. WHERE objectID = @statsobjectID AND statsID = @statsID AND [dbID] = @dbID AND partitionNumber = @partitionNumber;
  2158. IF @debugMode = 1
  2159. BEGIN
  2160. SET @debugMessage = ' Determining modification row counter for statistic ' + @statsName + ' on table or view ' + @statsobjectName + ' of DB ' + @dbName + '...';
  2161. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2162. END
  2163. /* Determine modification row counter to ascertain if update stats is required */
  2164. IF ((@sqlmajorver = 12 AND @sqlbuild >= 5000) OR (@sqlmajorver = 13 AND @sqlbuild >= 4000) OR @sqlmajorver > 13) AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE')
  2165. BEGIN
  2166. IF @debugMode = 1
  2167. RAISERROR(' Using sys.dm_db_incremental_stats_properties DMF...', 0, 42) WITH NOWAIT;
  2168. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_incremental_stats_properties(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ') WHERE partition_number = @partitionNumber_In;'
  2169. END
  2170. ELSE IF ((@sqlmajorver = 12 AND @sqlbuild < 5000) OR (@sqlmajorver = 13 AND @sqlbuild < 4000)) AND @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE')
  2171. BEGIN
  2172. IF @debugMode = 1
  2173. RAISERROR(' Using sys.dm_db_stats_properties_internal DMF...', 0, 42) WITH NOWAIT;
  2174. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_stats_properties_internal(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ') WHERE partition_number = @partitionNumber_In;'
  2175. END
  2176. ELSE IF ((@sqlmajorver = 10 AND @sqlminorver = 50 AND @sqlbuild >= 4000) OR (@sqlmajorver = 11 AND @sqlbuild >= 3000) OR @sqlmajorver >= 12) AND (@stats_isincremental = 0 OR UPPER(@statsSample) = 'FULLSCAN')
  2177. BEGIN
  2178. IF @debugMode = 1
  2179. RAISERROR(' Using sys.dm_db_stats_properties DMF...', 0, 42) WITH NOWAIT;
  2180. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = ISNULL(modification_counter,0), @rows_Out = ISNULL(rows,0), @rows_sampled_Out = ISNULL(rows_sampled,0) FROM sys.dm_db_stats_properties(' + CAST(@statsobjectID AS NVARCHAR(10)) + ',' + CAST(@statsID AS NVARCHAR(10)) + ');'
  2181. END
  2182. ELSE
  2183. BEGIN
  2184. IF @debugMode = 1
  2185. RAISERROR(' Using sys.sysindexes...', 0, 42) WITH NOWAIT;
  2186. SELECT TOP 1 @surrogateStatsID = indexID FROM dbo.tbl_AdaptiveIndexDefrag_Working (NOLOCK) WHERE objectID = @statsobjectID AND indexName = @statsName
  2187. SELECT @rowmodctrSQL = N'USE ' + @dbName + '; SELECT @rowmodctr_Out = SUM(ISNULL(rowmodctr,0)), @rows_Out = ISNULL(rowcnt,0), @rows_sampled_Out = -1 FROM sys.sysindexes WHERE id = ' + CAST(@statsobjectID AS NVARCHAR(10)) + ' AND indid = ' + CAST(@surrogateStatsID AS NVARCHAR(10)) + ' AND rowmodctr > 0;'
  2188. END
  2189. SET @rowmodctrSQL_Param = N'@partitionNumber_In smallint, @rowmodctr_Out bigint OUTPUT, @rows_Out bigint OUTPUT, @rows_sampled_Out bigint OUTPUT'
  2190. BEGIN TRY
  2191. EXECUTE sp_executesql @rowmodctrSQL, @rowmodctrSQL_Param, @partitionNumber_In = @partitionNumber, @rowmodctr_Out = @rowmodctr OUTPUT, @rows_Out = @rows OUTPUT, @rows_sampled_Out = @rows_sampled OUTPUT;
  2192. SET @rowmodctr = (SELECT ISNULL(@rowmodctr, 0));
  2193. END TRY
  2194. BEGIN CATCH
  2195. IF @debugMode = 1
  2196. BEGIN
  2197. SET @debugMessage = ' Error ' + CONVERT(VARCHAR(20),ERROR_NUMBER()) + ' has occurred while determining row modification counter. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS VARCHAR(10)) + ')'
  2198. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2199. END
  2200. END CATCH
  2201. IF @rows IS NOT NULL AND @rows > 0
  2202. SET @record_count = @rows
  2203. IF @debugMode = 1
  2204. BEGIN
  2205. SELECT @debugMessage = ' Found a row modification counter of ' + CONVERT(NVARCHAR(10), @rowmodctr) + ' and ' + CONVERT(NVARCHAR(10), CASE WHEN @rows IS NOT NULL AND @rows < @record_count THEN @rows ELSE @record_count END) + ' rows' + CASE WHEN @stats_isincremental = 1 THEN ' on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2206. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2207. --select @debugMessage
  2208. END
  2209. /* We will update statistics if they have changed since last update with customized, more finer values, just like when TF2371 is enabled for Auto-Update.
  2210. Default rules for auto update stats are:
  2211. If the cardinality for a table is greater than 6, but less than or equal to 500, update status every 500 modifications.
  2212. If the cardinality for a table is greater than 500, update statistics when (500 + 20 percent of the table) changes have occurred.
  2213. */
  2214. IF (@statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.001 AND 100.0 AND @statsMinRows IS NULL AND (@rowmodctr*100)/@record_count >= @statsThreshold)
  2215. OR (@statsThreshold IS NOT NULL AND @statsThreshold BETWEEN 0.001 AND 100.0 AND @statsMinRows IS NOT NULL AND @record_count >= @statsMinRows AND (@rowmodctr*100)/@record_count >= @statsThreshold)
  2216. OR (@statsThreshold IS NULL AND (
  2217. (@record_count BETWEEN 6 AND 500 AND @rowmodctr >= 500) OR -- like the default
  2218. (@record_count BETWEEN 501 AND 10000 AND (@rowmodctr >= (@record_count*20)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 20 percent or simulate TF 2371
  2219. (@record_count BETWEEN 10001 AND 100000 AND (@rowmodctr >= (@record_count*15)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 15 percent or simulate TF 2371
  2220. (@record_count BETWEEN 100001 AND 1000000 AND (@rowmodctr >= (@record_count*10)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) OR -- 500 + 10 percent or simulate TF 2371
  2221. (@record_count >= 1000001 AND (@rowmodctr >= (@record_count*5)/100 + 500 OR @rowmodctr >= SQRT(@record_count*1000))) -- 500 + 5 percent or simulate TF 2371
  2222. ))
  2223. BEGIN
  2224. SET @sqlcommand2 = N'UPDATE STATISTICS ' + @dbName + N'.' + @statsschemaName + N'.' + @statsobjectName + N' (' + @statsName + N')'
  2225. IF UPPER(@statsSample) = 'FULLSCAN' AND (@partitionNumber = 1 OR @partitionNumber = @maxpartitionNumber)
  2226. SET @sqlcommand2 = @sqlcommand2 + N' WITH FULLSCAN'
  2227. ELSE IF UPPER(@statsSample) = 'RESAMPLE'
  2228. SET @sqlcommand2 = @sqlcommand2 + N' WITH RESAMPLE'
  2229. IF @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE') AND UPPER(@sqlcommand2) LIKE '%WITH%'
  2230. SET @sqlcommand2 = @sqlcommand2 + N' ON PARTITIONS(' + CONVERT(NVARCHAR(10), @partitionNumber) + N');'
  2231. ELSE IF @stats_isincremental = 1 AND (@statsSample IS NULL OR UPPER(@statsSample) = 'RESAMPLE') AND UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2232. SET @sqlcommand2 = @sqlcommand2 + N' WITH RESAMPLE ON PARTITIONS(' + CONVERT(NVARCHAR(10), @partitionNumber) + N')'
  2233. IF @stats_norecompute = 1 AND UPPER(@sqlcommand2) LIKE '%WITH%'
  2234. SET @sqlcommand2 = @sqlcommand2 + N' ,NORECOMPUTE'
  2235. ELSE IF @stats_norecompute = 1 AND UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2236. SET @sqlcommand2 = @sqlcommand2 + N' WITH NORECOMPUTE'
  2237. /* For list of incremental stats unsupported scenarios check https://msdn.microsoft.com/en-us/library/ms187348.aspx */
  2238. IF @partitionCount > 1 AND @statsIncremental = 1 AND @has_filter = 0
  2239. BEGIN
  2240. IF UPPER(@sqlcommand2) LIKE '%WITH%'
  2241. SET @sqlcommand2 = @sqlcommand2 + N' ,INCREMENTAL = ON'
  2242. ELSE IF UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2243. SET @sqlcommand2 = @sqlcommand2 + N'WITH INCREMENTAL = ON'
  2244. END
  2245. ELSE IF @statsIncremental = 0
  2246. BEGIN
  2247. IF UPPER(@sqlcommand2) LIKE '%WITH%'
  2248. SET @sqlcommand2 = @sqlcommand2 + N', INCREMENTAL = OFF'
  2249. ELSE IF UPPER(@sqlcommand2) NOT LIKE '%WITH%'
  2250. SET @sqlcommand2 = @sqlcommand2 + N'WITH INCREMENTAL = OFF'
  2251. END
  2252. SET @sqlcommand2 = @sqlcommand2 + N';'
  2253. END
  2254. ELSE
  2255. BEGIN
  2256. SET @sqlcommand2 = NULL
  2257. END;
  2258. /* Are we executing the SQL? If so, do it */
  2259. IF @Exec_Print = 1 AND @sqlcommand2 IS NOT NULL
  2260. BEGIN
  2261. SET @debugMessage = ' ' + @sqlcommand2;
  2262. /* Print the commands we're executing if specified to do so */
  2263. IF (@printCmds = 1 OR @debugMode = 1)
  2264. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2265. /* Get the time for logging purposes */
  2266. SET @dateTimeStart = GETDATE();
  2267. /* Log actions */
  2268. INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Stats_log (dbID, dbName, objectID, objectName, statsID, statsName, [partitionNumber], [rows], rows_sampled, modification_counter, [no_recompute], dateTimeStart, sqlStatement)
  2269. SELECT @dbID, @dbName, @statsobjectID, @statsobjectName, @statsID, @statsName, @partitionNumber, @rows, @rows_sampled, @rowmodctr, @stats_norecompute, @dateTimeStart, @sqlcommand2;
  2270. SET @statsUpdate_id = SCOPE_IDENTITY();
  2271. /* Wrap execution attempt in a TRY/CATCH and log any errors that occur */
  2272. BEGIN TRY
  2273. /* Execute update! */
  2274. EXECUTE sp_executesql @sqlcommand2;
  2275. SET @dateTimeEnd = GETDATE();
  2276. SET @sqlcommand2 = NULL
  2277. /* Update log with completion time */
  2278. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2279. SET dateTimeEnd = @dateTimeEnd, durationSeconds = DATEDIFF(second, @dateTimeStart, @dateTimeEnd)
  2280. WHERE statsUpdate_id = @statsUpdate_id AND partitionNumber = @partitionNumber AND dateTimeEnd IS NULL;
  2281. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2282. SET updateDate = GETDATE(), printStatus = 1
  2283. WHERE dbID = @dbID AND objectID = @statsobjectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2284. END TRY
  2285. BEGIN CATCH
  2286. /* Update log with error message */
  2287. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2288. SET dateTimeEnd = GETDATE(), durationSeconds = -1, errorMessage = 'Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  2289. WHERE statsUpdate_id = @statsUpdate_id AND partitionNumber = @partitionNumber AND dateTimeEnd IS NULL;
  2290. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2291. SET updateDate = GETDATE(), printStatus = 1
  2292. WHERE dbID = @dbID AND objectID = @statsobjectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2293. IF @debugMode = 1
  2294. BEGIN
  2295. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred executing this command. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  2296. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2297. --RAISERROR(' An error has occurred executing this command. Please review the tbl_AdaptiveIndexDefrag_Stats_log table for details.', 0, 42) WITH NOWAIT;
  2298. END
  2299. END CATCH
  2300. END
  2301. ELSE IF @Exec_Print = 1 AND @sqlcommand2 IS NULL
  2302. BEGIN
  2303. IF @debugMode = 1
  2304. BEGIN
  2305. SET @debugMessage = ' No need to update statistic ' + @statsName + ' on DB ' + @dbName + ' and object ' + @statsobjectName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2306. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2307. END
  2308. IF @printCmds = 1 AND @debugMode = 0
  2309. BEGIN
  2310. SET @debugMessage = ' -- No need to update statistic ' + @statsName + ' on DB ' + @dbName + ' and object ' + @statsobjectName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2311. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2312. END
  2313. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2314. SET updateDate = GETDATE(), printStatus = 1
  2315. WHERE dbID = @dbID AND objectID = @statsobjectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2316. END
  2317. ELSE IF @Exec_Print = 0
  2318. BEGIN
  2319. IF @debugMode = 1 AND @sqlcommand2 IS NULL
  2320. BEGIN
  2321. SET @debugMessage = ' No need to update statistic ' + @statsName + ' on DB ' + @dbName + ' and object ' + @statsobjectName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2322. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2323. END
  2324. /* Print the commands we're executing if specified to do so */
  2325. IF (@printCmds = 1 OR @debugMode = 1) AND @sqlcommand2 IS NOT NULL
  2326. BEGIN
  2327. SET @debugMessage = ' ' + @sqlcommand2;
  2328. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2329. END
  2330. IF @printCmds = 1 AND @debugMode = 0 AND @sqlcommand2 IS NULL
  2331. BEGIN
  2332. SET @debugMessage = ' -- No need to update statistic ' + @statsName + ' on DB ' + @dbName + ' and object ' + @statsobjectName + CASE WHEN @stats_isincremental = 1 THEN ', on partition ' + CONVERT(NVARCHAR(10), @partitionNumber) ELSE '' END + '...';
  2333. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2334. END
  2335. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2336. SET printStatus = 1
  2337. WHERE dbID = @dbID AND objectID = @statsobjectID AND statsID = @statsID AND partitionNumber = @partitionNumber;
  2338. END
  2339. END
  2340. IF (@printCmds = 1 OR @debugMode = 1)
  2341. PRINT ' No remaining statistics to update...';
  2342. END
  2343. ELSE
  2344. BEGIN
  2345. IF (@printCmds = 1 OR @debugMode = 1)
  2346. PRINT ' No remaining statistics to update...';
  2347. END
  2348. /* Output results? */
  2349. IF @outputResults = 1 AND @Exec_Print = 1
  2350. BEGIN
  2351. IF (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate >= @startDateTime) > 0
  2352. OR (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working WHERE updateDate >= @startDateTime) > 0
  2353. BEGIN
  2354. IF @debugMode = 1
  2355. RAISERROR(' Displaying a summary of our actions...', 0, 42) WITH NOWAIT;
  2356. SELECT [dbName], objectName, indexName, partitionNumber, CONVERT(decimal(9,2),fragmentation) AS fragmentation, page_count, fill_factor, range_scan_count, defragDate
  2357. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2358. WHERE defragDate >= @startDateTime
  2359. ORDER BY defragDate;
  2360. SELECT [dbName], [statsName], partitionNumber, [no_recompute], [is_incremental], updateDate
  2361. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2362. WHERE updateDate >= @startDateTime
  2363. ORDER BY updateDate;
  2364. IF @debugMode = 1 AND (SELECT COUNT(*) FROM dbo.tbl_AdaptiveIndexDefrag_log WHERE errorMessage IS NOT NULL AND dateTimeStart >= @startDateTime) > 0
  2365. BEGIN
  2366. RAISERROR('Displaying a summary of all errors...', 0, 42) WITH NOWAIT;
  2367. SELECT dbName, objectName, indexName, partitionNumber, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2368. FROM dbo.tbl_AdaptiveIndexDefrag_log
  2369. WHERE errorMessage IS NOT NULL AND dateTimeStart >= @startDateTime
  2370. ORDER BY dateTimeStart;
  2371. END
  2372. IF @debugMode = 1
  2373. RAISERROR(' Displaying some statistical information about this defragmentation run...', 0, 42) WITH NOWAIT;
  2374. SELECT TOP 10 'Longest time' AS Comment, dbName, objectName, indexName, partitionNumber, dateTimeStart, dateTimeEnd, durationSeconds
  2375. FROM dbo.tbl_AdaptiveIndexDefrag_log
  2376. WHERE dateTimeStart >= @startDateTime
  2377. ORDER BY durationSeconds DESC;
  2378. END
  2379. END;
  2380. END TRY
  2381. BEGIN CATCH
  2382. SET @debugMessage = ' Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ' has occurred. Message: ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')'
  2383. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2384. END CATCH;
  2385. /* Reset printStatus */
  2386. IF @debugMode = 1
  2387. RAISERROR(' Reseting working table statuses.', 0, 42) WITH NOWAIT;
  2388. UPDATE dbo.tbl_AdaptiveIndexDefrag_Working
  2389. SET printStatus = 0;
  2390. UPDATE dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2391. SET printStatus = 0;
  2392. /* Drop all temp tables */
  2393. IF @debugMode = 1
  2394. RAISERROR(' Droping temporary objects', 0, 42) WITH NOWAIT;
  2395. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragDatabaseList'))
  2396. DROP TABLE #tblIndexDefragDatabaseList;
  2397. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragmaxPartitionList'))
  2398. DROP TABLE #tblIndexDefragmaxPartitionList;
  2399. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexDefragScanWorking'))
  2400. DROP TABLE #tblIndexDefragScanWorking;
  2401. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#tblIndexFindInDatabaseList'))
  2402. DROP TABLE #tblIndexFindInDatabaseList;
  2403. IF @debugMode = 1
  2404. RAISERROR('All done!', 0, 42) WITH NOWAIT;
  2405. IF @Exec_Print = 0
  2406. BEGIN
  2407. IF @ignoreDropObj = 0
  2408. BEGIN
  2409. IF (SELECT COUNT([errorMessage]) FROM dbo.vw_LastRun_Log) > 0 AND @ignoreDropObj = 0
  2410. BEGIN
  2411. RAISERROR('Defrag job found execution errors! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 16, 42) WITH NOWAIT;
  2412. RETURN -1
  2413. END
  2414. ELSE
  2415. BEGIN
  2416. RETURN 0
  2417. END
  2418. END
  2419. ELSE
  2420. BEGIN
  2421. IF (SELECT COUNT([errorMessage]) FROM dbo.vw_LastRun_Log WHERE [errorMessage] NOT LIKE 'Table%does not exist%') > 0
  2422. BEGIN
  2423. RAISERROR('Defrag job found execution errors! Please review the tbl_AdaptiveIndexDefrag_log table for details.', 16, 42) WITH NOWAIT;
  2424. RETURN -1
  2425. END
  2426. ELSE
  2427. BEGIN
  2428. RETURN 0
  2429. END
  2430. END
  2431. END
  2432. END
  2433. GO
  2434. --EXEC sys.sp_MS_marksystemobject 'usp_AdaptiveIndexDefrag'
  2435. --GO
  2436. PRINT 'Procedure usp_AdaptiveIndexDefrag created';
  2437. GO
  2438. ------------------------------------------------------------------------------------------------------------------------------
  2439. CREATE VIEW vw_ErrLst30Days
  2440. AS
  2441. SELECT TOP 100 PERCENT dbName, objectName, indexName, partitionNumber, NULL AS statsName, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2442. FROM dbo.tbl_AdaptiveIndexDefrag_log
  2443. WHERE errorMessage IS NOT NULL AND dateTimeStart >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2444. UNION ALL
  2445. SELECT TOP 100 PERCENT dbName, objectName, NULL AS indexName, NULL AS partitionNumber, statsName, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2446. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2447. WHERE errorMessage IS NOT NULL AND dateTimeStart >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2448. ORDER BY dateTimeStart;
  2449. GO
  2450. CREATE VIEW vw_ErrLst24Hrs
  2451. AS
  2452. SELECT TOP 100 PERCENT dbName, objectName, indexName, partitionNumber, NULL AS statsName, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2453. FROM dbo.tbl_AdaptiveIndexDefrag_log
  2454. WHERE errorMessage IS NOT NULL AND dateTimeStart >= DATEADD(hh, -24, GETDATE())
  2455. UNION ALL
  2456. SELECT TOP 100 PERCENT dbName, objectName, NULL AS indexName, partitionNumber, statsName, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2457. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2458. WHERE errorMessage IS NOT NULL AND dateTimeStart >= DATEADD(hh, -24, GETDATE())
  2459. ORDER BY dateTimeStart;
  2460. GO
  2461. CREATE VIEW vw_AvgTimeLst30Days
  2462. AS
  2463. SELECT TOP 100 PERCENT 'Longest time' AS Comment, dbName, objectName, indexName, partitionNumber, AVG(durationSeconds) AS Avg_durationSeconds
  2464. FROM dbo.tbl_AdaptiveIndexDefrag_log
  2465. WHERE dateTimeStart >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2466. GROUP BY dbName, objectName, indexName, partitionNumber
  2467. ORDER BY AVG(durationSeconds) DESC, dbName, objectName, indexName, partitionNumber;
  2468. GO
  2469. CREATE VIEW vw_AvgFragLst30Days
  2470. AS
  2471. SELECT TOP 100 PERCENT 'Most fragmented' AS Comment, dbName, objectName, indexName, partitionNumber, CONVERT(decimal(9,2),AVG(fragmentation)) AS Avg_fragmentation
  2472. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2473. WHERE defragDate >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2474. GROUP BY dbName, objectName, indexName, partitionNumber
  2475. ORDER BY AVG(fragmentation) DESC, dbName, objectName, indexName, partitionNumber;
  2476. GO
  2477. CREATE VIEW vw_AvgSamplingLst30Days
  2478. AS
  2479. SELECT TOP 100 PERCENT 'Avg_Sampling' AS Comment, dbName, objectName, partitionNumber, statsName, CAST((rows_sampled/([rows]*1.00))*100.0 AS DECIMAL(5,2)) AS sampling, dateTimeStart, dateTimeEnd, sqlStatement, errorMessage
  2480. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2481. WHERE errorMessage IS NOT NULL AND dateTimeStart >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2482. ORDER BY dateTimeStart;
  2483. GO
  2484. CREATE VIEW vw_AvgLargestLst30Days
  2485. AS
  2486. SELECT TOP 100 PERCENT 'Largest' AS Comment, dbName, objectName, indexName, partitionNumber, AVG(page_count)*8 AS Avg_size_KB, fill_factor
  2487. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2488. WHERE defragDate >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2489. GROUP BY dbName, objectName, indexName, partitionNumber, fill_factor
  2490. ORDER BY AVG(page_count) DESC, dbName, objectName, indexName, partitionNumber
  2491. GO
  2492. CREATE VIEW vw_AvgMostUsedLst30Days
  2493. AS
  2494. SELECT TOP 100 PERCENT 'Most used' AS Comment, dbName, objectName, indexName, partitionNumber, AVG(range_scan_count) AS Avg_range_scan_count
  2495. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2496. WHERE defragDate >= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -30)
  2497. GROUP BY dbName, objectName, indexName, partitionNumber
  2498. ORDER BY AVG(range_scan_count) DESC;
  2499. GO
  2500. CREATE VIEW vw_LastRun_Log
  2501. AS
  2502. SELECT TOP 100 percent [dbName]
  2503. ,[objectName]
  2504. ,[indexName]
  2505. , NULL AS [statsName]
  2506. ,[partitionNumber]
  2507. ,[fragmentation]
  2508. ,[page_count]
  2509. ,[range_scan_count]
  2510. ,[dateTimeStart]
  2511. ,[dateTimeEnd]
  2512. ,[durationSeconds]
  2513. ,CASE WHEN [sqlStatement] LIKE '%REORGANIZE%' THEN 'Reorg' ELSE 'Rebuild' END AS [Operation]
  2514. ,[errorMessage]
  2515. FROM dbo.tbl_AdaptiveIndexDefrag_log ixlog
  2516. CROSS APPLY (SELECT TOP 1 minIxDate = CASE WHEN defragDate IS NULL THEN CONVERT(DATETIME, CONVERT(NVARCHAR, scanDate, 112))
  2517. ELSE CONVERT(DATETIME, CONVERT(NVARCHAR, defragDate, 112)) END
  2518. FROM [dbo].[tbl_AdaptiveIndexDefrag_Working]
  2519. ORDER BY defragDate ASC, scanDate ASC) AS minDateIxCte
  2520. WHERE dateTimeStart >= minIxDate
  2521. UNION ALL
  2522. SELECT TOP 100 percent [dbName]
  2523. ,[objectName]
  2524. ,NULL AS [indexName]
  2525. ,[statsName]
  2526. ,NULL AS [partitionNumber]
  2527. ,NULL AS [fragmentation]
  2528. ,NULL AS [page_count]
  2529. ,NULL AS [range_scan_count]
  2530. ,[dateTimeStart]
  2531. ,[dateTimeEnd]
  2532. ,[durationSeconds]
  2533. ,'UpdateStats' AS [Operation]
  2534. ,[errorMessage]
  2535. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log statlog
  2536. CROSS APPLY (SELECT TOP 1 minStatDate = CASE WHEN updateDate IS NULL THEN CONVERT(DATETIME, CONVERT(NVARCHAR, scanDate, 112))
  2537. ELSE CONVERT(DATETIME, CONVERT(NVARCHAR, updateDate, 112)) END
  2538. FROM [dbo].[tbl_AdaptiveIndexDefrag_Stats_Working]
  2539. ORDER BY updateDate ASC, scanDate ASC) AS minDateStatCte
  2540. WHERE dateTimeStart >= minStatDate
  2541. ORDER BY dateTimeEnd ASC
  2542. GO
  2543. PRINT 'Reporting views created';
  2544. GO
  2545. ------------------------------------------------------------------------------------------------------------------------------
  2546. CREATE PROCEDURE usp_AdaptiveIndexDefrag_PurgeLogs @daystokeep smallint = 90
  2547. AS
  2548. /*
  2549. usp_AdaptiveIndexDefrag_PurgeLogs.sql - [email protected] (http://blogs.msdn.com/b/blogdoezequiel/)
  2550. Purge log tables to avoid indefinite growth.
  2551. Default is data older than 90 days.
  2552. Change @daystokeep as you deem fit.
  2553. */
  2554. SET NOCOUNT ON;
  2555. SET DATEFORMAT ymd;
  2556. DELETE FROM dbo.tbl_AdaptiveIndexDefrag_log
  2557. WHERE dateTimeStart <= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -@daystokeep);
  2558. DELETE FROM dbo.tbl_AdaptiveIndexDefrag_Stats_log
  2559. WHERE dateTimeStart <= DATEADD(dd, DATEDIFF(dd, 0, GETDATE()), -@daystokeep);
  2560. GO
  2561. --EXEC sys.sp_MS_marksystemobject 'usp_AdaptiveIndexDefrag_PurgeLogs'
  2562. --GO
  2563. PRINT 'Procedure usp_AdaptiveIndexDefrag_PurgeLogs created (Default purge is 90 days old)';
  2564. GO
  2565. ------------------------------------------------------------------------------------------------------------------------------
  2566. CREATE PROCEDURE usp_AdaptiveIndexDefrag_CurrentExecStats @dbname NVARCHAR(255) = NULL
  2567. AS
  2568. /*
  2569. usp_AdaptiveIndexDefrag_CurrentExecStats.sql - [email protected] (http://blogs.msdn.com/b/blogdoezequiel/)
  2570. Allows monitoring of what has been done so far in the defrag loop.
  2571. Use @dbname to monitor a specific database
  2572. Example:
  2573. EXEC usp_AdaptiveIndexDefrag_CurrentExecStats @dbname = 'AdventureWorks2008R2'
  2574. */
  2575. SET NOCOUNT ON;
  2576. IF @dbname IS NULL
  2577. BEGIN
  2578. WITH cte1 ([Database_Name], Total_indexes) AS (SELECT [dbName], COUNT(indexID) AS Total_Indexes FROM dbo.tbl_AdaptiveIndexDefrag_Working GROUP BY [dbName]),
  2579. cte2 ([Database_Name], Defraged_Indexes) AS (SELECT [dbName], COUNT(indexID) AS Total_Indexes FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE defragDate IS NOT NULL OR printStatus = 1 GROUP BY [dbName]),
  2580. cte3 ([Database_Name], Total_statistics) AS (SELECT [dbName], COUNT(statsID) AS Total_statistics FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working GROUP BY [dbName]),
  2581. cte4 ([Database_Name], Updated_statistics) AS (SELECT [dbName], COUNT(statsID) AS Updated_statistics FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working WHERE updateDate IS NOT NULL OR printStatus = 1 GROUP BY [dbName])
  2582. SELECT cte1.[Database_Name], SUM(cte1.Total_indexes) AS Total_indexes, SUM(ISNULL(cte2.Defraged_Indexes, 0)) AS Defraged_Indexes,
  2583. SUM(cte3.Total_statistics) AS Total_statistics, SUM(ISNULL(cte4.Updated_statistics, 0)) AS Updated_statistics
  2584. FROM cte1 INNER JOIN cte3 ON cte1.Database_Name = cte3.Database_Name
  2585. LEFT JOIN cte2 ON cte1.Database_Name = cte2.Database_Name
  2586. LEFT JOIN cte4 ON cte1.Database_Name = cte4.Database_Name
  2587. GROUP BY cte1.[Database_Name];
  2588. SELECT 'Index' AS [Type], 'Done' AS [Result], dbName, objectName, indexName
  2589. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2590. WHERE defragDate IS NOT NULL OR printStatus = 1
  2591. UNION ALL
  2592. SELECT 'Index' AS [Type], 'To do' AS [Result], dbName, objectName, indexName
  2593. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2594. WHERE defragDate IS NULL AND printStatus = 0
  2595. ORDER BY 2, dbName, objectName, indexName;
  2596. SELECT 'Statistic' AS [Type], 'Done' AS [Result], dbName, objectName, statsName
  2597. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2598. WHERE updateDate IS NOT NULL OR printStatus = 1
  2599. UNION ALL
  2600. SELECT 'Statistic' AS [Type], 'To do' AS [Result], dbName, objectName, statsName
  2601. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2602. WHERE updateDate IS NULL AND printStatus = 0
  2603. ORDER BY 2, dbName, objectName, statsName;
  2604. END
  2605. ELSE
  2606. BEGIN
  2607. WITH cte1 ([Database_Name], Total_indexes) AS (SELECT [dbName], COUNT(indexID) AS Total_Indexes FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE [dbName] = QUOTENAME(@dbname) GROUP BY [dbName]),
  2608. cte2 ([Database_Name], Defraged_Indexes) AS (SELECT [dbName], COUNT(indexID) AS Total_Indexes FROM dbo.tbl_AdaptiveIndexDefrag_Working WHERE [dbName] = QUOTENAME(@dbname) AND defragDate IS NOT NULL OR printStatus = 1 GROUP BY [dbName]),
  2609. cte3 ([Database_Name], Total_statistics) AS (SELECT [dbName], COUNT(statsID) AS Total_statistics FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working WHERE [dbName] = QUOTENAME(@dbname) GROUP BY [dbName]),
  2610. cte4 ([Database_Name], Updated_statistics) AS (SELECT [dbName], COUNT(statsID) AS Updated_statistics FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working WHERE [dbName] = QUOTENAME(@dbname) AND updateDate IS NOT NULL OR printStatus = 1 GROUP BY [dbName])
  2611. SELECT cte1.[Database_Name], SUM(cte1.Total_indexes) AS Total_indexes, SUM(ISNULL(cte2.Defraged_Indexes, 0)) AS Defraged_Indexes,
  2612. SUM(cte3.Total_statistics) AS Total_statistics, SUM(ISNULL(cte4.Updated_statistics, 0)) AS Updated_statistics
  2613. FROM cte1 INNER JOIN cte3 ON cte1.Database_Name = cte3.Database_Name
  2614. LEFT JOIN cte2 ON cte1.Database_Name = cte2.Database_Name
  2615. LEFT JOIN cte4 ON cte1.Database_Name = cte4.Database_Name
  2616. GROUP BY cte1.[Database_Name];
  2617. SELECT 'Index' AS [Type], 'Done' AS [Result], dbName, objectName, indexName, partitionNumber
  2618. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2619. WHERE [dbName] = QUOTENAME(@dbname) AND (defragDate IS NOT NULL OR printStatus = 1)
  2620. UNION ALL
  2621. SELECT 'Index' AS [Type], 'To do' AS [Result], dbName, objectName, indexName, partitionNumber
  2622. FROM dbo.tbl_AdaptiveIndexDefrag_Working
  2623. WHERE [dbName] = QUOTENAME(@dbname) AND defragDate IS NULL AND printStatus = 0
  2624. ORDER BY 2, dbName, objectName, indexName;
  2625. SELECT 'Statistic' AS [Type], 'Done' AS [Result], dbName, objectName, statsName
  2626. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2627. WHERE [dbName] = QUOTENAME(@dbname) AND ([updateDate] IS NOT NULL OR printStatus = 1)
  2628. UNION ALL
  2629. SELECT 'Statistic' AS [Type], 'To do' AS [Result], dbName, objectName, statsName
  2630. FROM dbo.tbl_AdaptiveIndexDefrag_Stats_Working
  2631. WHERE [dbName] = QUOTENAME(@dbname) AND [updateDate] IS NULL AND printStatus = 0
  2632. ORDER BY 2, dbName, objectName, statsName;
  2633. END
  2634. GO
  2635. --EXEC sys.sp_MS_marksystemobject 'usp_AdaptiveIndexDefrag_CurrentExecStats'
  2636. --GO
  2637. PRINT 'Procedure usp_AdaptiveIndexDefrag_CurrentExecStats created (Use this to monitor defrag loop progress)';
  2638. GO
  2639. ------------------------------------------------------------------------------------------------------------------------------
  2640. CREATE PROCEDURE usp_AdaptiveIndexDefrag_Exceptions @exceptionMask_DB NVARCHAR(255) = NULL,
  2641. @exceptionMask_days NVARCHAR(27) = NULL,
  2642. @exceptionMask_tables NVARCHAR(500) = NULL,
  2643. @exceptionMask_indexes NVARCHAR(500) = NULL
  2644. AS
  2645. /*
  2646. usp_AdaptiveIndexDefrag_Exceptions.sql - [email protected] (http://blogs.msdn.com/b/blogdoezequiel/)
  2647. To insert info into the Exceptions table, use the following guidelines:
  2648. For @exceptionMask_DB, enter only one database name at a time.
  2649. For @exceptionMask_days, enter weekdays in short form, between commas.
  2650. * NOTE: Keep only the weekdays you DO NOT WANT to ALLOW defrag. *
  2651. Order is not mandatory, but weekday short names are important AS IS ('Sun,Mon,Tue,Wed,Thu,Fri,Sat').
  2652. * NOTE: If you WANT to NEVER allow defrag, set as NULL or leave blank *
  2653. For @exceptionMask_tables (optional) enter table names separated by commas ('table_name_1, table_name_2, table_name_3').
  2654. For @exceptionMask_indexes (optional) enter index names separated by commas ('index_name_1, index_name_2, index_name_3').
  2655. If you want to exclude all indexes in a given table, enter its name but don't add index names.
  2656. Example:
  2657. EXEC usp_AdaptiveIndexDefrag_Exceptions @exceptionMask_DB = 'AdventureWorks2008R2',
  2658. @exceptionMask_days = 'Mon,Wed',
  2659. @exceptionMask_tables = 'Employee',
  2660. @exceptionMask_indexes = 'AK_Employee_LoginID'
  2661. */
  2662. SET NOCOUNT ON;
  2663. IF @exceptionMask_DB IS NULL OR QUOTENAME(@exceptionMask_DB) NOT IN (SELECT QUOTENAME(name) FROM master.sys.sysdatabases)
  2664. RAISERROR('Syntax error. Please input a valid database name.', 15, 42) WITH NOWAIT;
  2665. IF @exceptionMask_days IS NOT NULL AND
  2666. (@exceptionMask_days NOT LIKE '___' AND
  2667. @exceptionMask_days NOT LIKE '___,___' AND
  2668. @exceptionMask_days NOT LIKE '___,___,___' AND
  2669. @exceptionMask_days NOT LIKE '___,___,___,___' AND
  2670. @exceptionMask_days NOT LIKE '___,___,___,___,___' AND
  2671. @exceptionMask_days NOT LIKE '___,___,___,___,___,___' AND
  2672. @exceptionMask_days NOT LIKE '___,___,___,___,___,___,___')
  2673. RAISERROR('Syntax error. Please input weekdays in short form, between commas, or leave NULL to always exclude.', 15, 42) WITH NOWAIT;
  2674. IF @exceptionMask_days LIKE '[___,___,___,___,___,___,___]'
  2675. RAISERROR('Warning. You chose to permanently exclude a table and/or index from being defragmented.', 0, 42) WITH NOWAIT;
  2676. IF @exceptionMask_tables IS NOT NULL AND @exceptionMask_tables LIKE '%.%'
  2677. RAISERROR('Syntax error. Please do not input schema with table name(s).', 15, 42) WITH NOWAIT;
  2678. DECLARE @debugMessage NVARCHAR(4000), @sqlcmd NVARCHAR(4000), @sqlmajorver int
  2679. /* Find sql server version */
  2680. SELECT @sqlmajorver = CONVERT(int, (@@microsoftversion / 0x1000000) & 0xff);
  2681. BEGIN TRY
  2682. --Always exclude from defrag?
  2683. IF @exceptionMask_days IS NULL OR @exceptionMask_days = ''
  2684. BEGIN
  2685. SET @exceptionMask_days = 127
  2686. END
  2687. ELSE
  2688. BEGIN
  2689. -- 1=Sunday, 2=Monday, 4=Tuesday, 8=Wednesday, 16=Thursday, 32=Friday, 64=Saturday, 127=AllWeek
  2690. SET @exceptionMask_days = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@exceptionMask_days,',','+'),'Sun',1),'Mon',2),'Tue',4),'Wed',8),'Thu',16),'Fri',32),'Sat',64);
  2691. END
  2692. --Just get everything as it should be
  2693. SET @exceptionMask_tables = CHAR(39) + REPLACE(REPLACE(@exceptionMask_tables, ' ', ''),',', CHAR(39) + ',' + CHAR(39)) + CHAR(39)
  2694. SET @exceptionMask_indexes = CHAR(39) + REPLACE(REPLACE(@exceptionMask_indexes, ' ', ''),',', CHAR(39) + ',' + CHAR(39)) + CHAR(39)
  2695. --Get the exceptions insert command
  2696. IF @sqlmajorver > 9
  2697. BEGIN
  2698. SELECT @sqlcmd = 'MERGE dbo.tbl_AdaptiveIndexDefrag_Exceptions AS target
  2699. USING (SELECT ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ' AS dbID, si.[object_id] AS objectID, si.index_id AS indexID,
  2700. ''' + @exceptionMask_DB + ''' AS dbName, OBJECT_NAME(si.[object_id], ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ') AS objectName, si.[name] AS indexName,
  2701. ' + CONVERT(NVARCHAR,@exceptionMask_days) + ' AS exclusionMask
  2702. FROM ' + QUOTENAME(@exceptionMask_DB) + '.sys.indexes si
  2703. INNER JOIN ' + QUOTENAME(@exceptionMask_DB) + '.sys.objects so ON si.object_id = so.object_id
  2704. WHERE so.is_ms_shipped = 0 AND si.index_id > 0 AND si.is_hypothetical = 0
  2705. AND si.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + @exceptionMask_DB + '].sys.internal_tables AS sit)' -- Exclude Heaps, Internal and Hypothetical objects
  2706. + CASE WHEN @exceptionMask_tables IS NOT NULL THEN ' AND OBJECT_NAME(si.[object_id], ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ') IN (' + @exceptionMask_tables + ')' ELSE '' END
  2707. + CASE WHEN @exceptionMask_indexes IS NOT NULL THEN ' AND si.[name] IN (' + @exceptionMask_indexes + ')' ELSE '' END
  2708. + ') AS source
  2709. ON (target.[dbID] = source.[dbID] AND target.objectID = source.objectID AND target.indexID = source.indexID)
  2710. WHEN MATCHED THEN
  2711. UPDATE SET exclusionMask = source.exclusionMask
  2712. WHEN NOT MATCHED THEN
  2713. INSERT (dbID, objectID, indexID, dbName, objectName, indexName, exclusionMask)
  2714. VALUES (source.dbID, source.objectID, source.indexID, source.dbName, source.objectName, source.indexName, source.exclusionMask);';
  2715. END
  2716. ELSE
  2717. BEGIN
  2718. SELECT @sqlcmd = 'DELETE FROM dbo.tbl_AdaptiveIndexDefrag_Exceptions
  2719. WHERE dbID = ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB))
  2720. + CASE WHEN @exceptionMask_tables IS NOT NULL THEN ' AND [objectName] IN (' + @exceptionMask_tables + ')' ELSE '' END
  2721. + CASE WHEN @exceptionMask_indexes IS NOT NULL THEN ' AND [indexName] IN (' + @exceptionMask_indexes + ');' ELSE ';' END +
  2722. 'INSERT INTO dbo.tbl_AdaptiveIndexDefrag_Exceptions
  2723. SELECT ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ' AS dbID, si.[object_id] AS objectID, si.index_id AS indexID,
  2724. ''' + @exceptionMask_DB + ''' AS dbName, OBJECT_NAME(si.[object_id], ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ') AS objectName, si.[name] AS indexName,
  2725. ' + CONVERT(NVARCHAR,@exceptionMask_days) + ' AS exclusionMask
  2726. FROM ' + QUOTENAME(@exceptionMask_DB) + '.sys.indexes si
  2727. INNER JOIN ' + QUOTENAME(@exceptionMask_DB) + '.sys.objects so ON si.object_id = so.object_id
  2728. WHERE so.is_ms_shipped = 0 AND si.index_id > 0 AND si.is_hypothetical = 0
  2729. AND si.[object_id] NOT IN (SELECT sit.[object_id] FROM [' + @exceptionMask_DB + '].sys.internal_tables AS sit)' -- Exclude Heaps, Internal and Hypothetical objects
  2730. + CASE WHEN @exceptionMask_tables IS NOT NULL THEN ' AND OBJECT_NAME(si.[object_id], ' + CONVERT(NVARCHAR,DB_ID(@exceptionMask_DB)) + ') IN (' + @exceptionMask_tables + ')' ELSE '' END
  2731. + CASE WHEN @exceptionMask_indexes IS NOT NULL THEN ' AND si.[name] IN (' + @exceptionMask_indexes + ')' ELSE '' END;
  2732. END;
  2733. EXEC sp_executesql @sqlcmd;
  2734. END TRY
  2735. BEGIN CATCH
  2736. SET @debugMessage = 'Error ' + CONVERT(NVARCHAR(20),ERROR_NUMBER()) + ': ' + ERROR_MESSAGE() + ' (Line Number: ' + CAST(ERROR_LINE() AS NVARCHAR(10)) + ')';
  2737. RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
  2738. END CATCH;
  2739. GO
  2740. --EXEC sys.sp_MS_marksystemobject 'usp_AdaptiveIndexDefrag_Exceptions'
  2741. --GO
  2742. PRINT 'Procedure usp_AdaptiveIndexDefrag_Exceptions created (If the defrag should not be daily, use this to set on which days to disallow it. It can be on entire DBs, tables and/or indexes)';
  2743. PRINT 'All done!'
  2744. GO