Spaces:
Paused
Paused
File size: 12,187 Bytes
ec286d9 cac96f2 ec286d9 cac96f2 ec286d9 cac96f2 ec286d9 cac96f2 dd08f57 cac96f2 ec286d9 cac96f2 ec286d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | -- ============================================================================
-- DATABASE CHANGES - Consolidated Script
-- Date: 30 November 2025
-- Purpose: Performance optimization for AquaBarrier project management API
-- ============================================================================
-- ============================================================================
-- PART 1: ADD TIMESTAMP COLUMNS TO PROJECTS TABLE
-- ============================================================================
-- Purpose: Support dashboard metrics and track record lifecycle
-- Impact: Enables "New Projects" filtering in dashboard
-- ============================================================================
-- Add CreatedDate column (if not exists)
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Projects' AND COLUMN_NAME = 'CreatedDate')
BEGIN
ALTER TABLE [dbo].[Projects] ADD [CreatedDate] [datetime2](7) NULL DEFAULT (getutcdate());
PRINT 'Added CreatedDate column to Projects table';
END
ELSE
PRINT 'CreatedDate already exists in Projects table';
GO
-- Add ModifiedDate column (if not exists)
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Projects' AND COLUMN_NAME = 'ModifiedDate')
BEGIN
ALTER TABLE [dbo].[Projects] ADD [ModifiedDate] [datetime2](7) NULL DEFAULT (getutcdate());
PRINT 'Added ModifiedDate column to Projects table';
END
ELSE
PRINT 'ModifiedDate already exists in Projects table';
GO
-- Backfill CreatedDate with current date for existing records (if NULL)
UPDATE [dbo].[Projects]
SET [CreatedDate] = GETUTCDATE()
WHERE [CreatedDate] IS NULL;
PRINT 'Backfilled CreatedDate for ' + CAST(@@ROWCOUNT AS varchar) + ' existing Projects';
GO
-- ============================================================================
-- PART 2: CREATE PERFORMANCE INDEXES
-- ============================================================================
-- Purpose: Optimize query performance for project and customer endpoints
-- Impact: Reduces query execution time from 5+ seconds to sub-second
-- ============================================================================
-- Index for dashboard metrics filtering by CreatedDate
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Projects_CreatedDate' AND object_id = OBJECT_ID('dbo.Projects'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Projects_CreatedDate] ON [dbo].[Projects]
(
[CreatedDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Projects_CreatedDate';
END
ELSE
PRINT 'Index IX_Projects_CreatedDate already exists';
GO
-- Index for Bidders ProjectNo lookups (if not exists)
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_ProjectNo' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_ProjectNo] ON [dbo].[Bidders]
(
[ProjNo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Bidders_ProjectNo';
END
ELSE
PRINT 'Index IX_Bidders_ProjectNo already exists';
GO
-- Index for Bidders Id (keyset pagination)
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_Id' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_Id] ON [dbo].[Bidders]
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Bidders_Id';
END
ELSE
PRINT 'Index IX_Bidders_Id already exists';
GO
-- Index for Bidders CustId lookups
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_CustId' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_CustId] ON [dbo].[Bidders]
(
[CustId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Bidders_CustId';
END
ELSE
PRINT 'Index IX_Bidders_CustId already exists';
GO
-- Index for Customers CustomerID lookups
IF EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Customers_CustomerID' AND object_id = OBJECT_ID('dbo.Customers'))
BEGIN
DROP INDEX [IX_Customers_CustomerID] ON [dbo].[Customers];
PRINT 'Dropped existing IX_Customers_CustomerID to recreate with INCLUDE(CompanyName)';
END
GO
CREATE NONCLUSTERED INDEX [IX_Customers_CustomerID] ON [dbo].[Customers]
(
[CustomerID] ASC
)
INCLUDE ([CompanyName])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = ON, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Customers_CustomerID with INCLUDE(CompanyName)';
GO
-- Composite index to optimize keyset pagination and project bidders fetch
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_ProjNo_Id' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_ProjNo_Id] ON [dbo].[Bidders]
(
[ProjNo] ASC,
[Id] ASC
)
INCLUDE ([CustId])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = ON, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Bidders_ProjNo_Id';
END
ELSE
PRINT 'Index IX_Bidders_ProjNo_Id already exists';
GO
-- Composite index to support ORDER BY [Primary] DESC, Id for offset pagination
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_ProjNo_Primary_Id' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_ProjNo_Primary_Id] ON [dbo].[Bidders]
(
[ProjNo] ASC,
[Primary] DESC,
[Id] ASC
)
INCLUDE (
[CustId], [Quote], [DateLastContact], [DateFollowup],
[CustType], [EmailAddress], [ReplacementCost], [Enabled]
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = ON, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Created index IX_Bidders_ProjNo_Primary_Id';
END
ELSE
PRINT 'Index IX_Bidders_ProjNo_Primary_Id already exists';
GO
-- ============================================================================
-- PART 3: OPTIONAL - MIGRATE Bidders.CustId FROM nvarchar(15) TO int
-- ============================================================================
-- Purpose: Eliminate CAST() in JOIN clause for better index usage
-- Impact: Removes type conversion overhead, allows index seek on Customers
-- WARNING: This is a breaking schema change - test thoroughly before applying
-- Status: OPTIONAL - Only apply if type mismatch is causing performance issues
-- ============================================================================
/*
-- Uncomment this section to perform the migration
-- Step 1: Add new int column
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId_New')
BEGIN
ALTER TABLE [dbo].[Bidders] ADD [CustId_New] [int] NULL;
PRINT 'Added CustId_New column';
END
GO
-- Step 2: Migrate data
UPDATE [dbo].[Bidders]
SET [CustId_New] = CAST([CustId] AS int)
WHERE ISNUMERIC([CustId]) = 1
AND [CustId_New] IS NULL;
PRINT 'Migrated ' + CAST(@@ROWCOUNT AS varchar) + ' rows to CustId_New';
GO
-- Step 3: Drop index on old column
IF EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_CustId' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
DROP INDEX [IX_Bidders_CustId] ON [dbo].[Bidders];
PRINT 'Dropped index IX_Bidders_CustId';
END
GO
-- Step 4: Drop old nvarchar column
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId' AND DATA_TYPE = 'nvarchar')
BEGIN
ALTER TABLE [dbo].[Bidders] DROP COLUMN [CustId];
PRINT 'Dropped old nvarchar CustId column';
END
GO
-- Step 5: Rename new column
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId_New')
AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId')
BEGIN
EXEC sp_rename 'dbo.Bidders.CustId_New', 'CustId', 'COLUMN';
PRINT 'Renamed CustId_New to CustId';
END
GO
-- Step 6: Set NOT NULL constraint
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId'
AND DATA_TYPE = 'int' AND IS_NULLABLE = 'YES')
BEGIN
IF NOT EXISTS (SELECT 1 FROM [dbo].[Bidders] WHERE [CustId] IS NULL)
BEGIN
ALTER TABLE [dbo].[Bidders] ALTER COLUMN [CustId] [int] NOT NULL;
PRINT 'Set CustId to NOT NULL';
END
ELSE
BEGIN
PRINT 'WARNING: NULL values exist in CustId, cannot set NOT NULL';
END
END
GO
-- Step 7: Recreate index on new int column
IF NOT EXISTS (SELECT 1 FROM sys.indexes
WHERE name = 'IX_Bidders_CustId' AND object_id = OBJECT_ID('dbo.Bidders'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Bidders_CustId] ON [dbo].[Bidders]
(
[CustId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];
PRINT 'Recreated index IX_Bidders_CustId on int column';
END
GO
*/
-- ============================================================================
-- VERIFICATION QUERIES
-- ============================================================================
PRINT '';
PRINT '============================================================================';
PRINT 'VERIFICATION RESULTS';
PRINT '============================================================================';
-- Verify Projects timestamp columns
SELECT 'Projects Timestamp Columns' AS CheckType;
SELECT
COLUMN_NAME,
DATA_TYPE,
IS_NULLABLE,
COLUMN_DEFAULT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Projects'
AND COLUMN_NAME IN ('CreatedDate', 'ModifiedDate')
ORDER BY COLUMN_NAME;
-- Verify all indexes exist
SELECT 'Performance Indexes' AS CheckType;
SELECT
t.name AS TableName,
i.name AS IndexName,
c.name AS ColumnName,
ty.name AS DataType
FROM sys.indexes i
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
JOIN sys.tables t ON i.object_id = t.object_id
WHERE i.name IN (
'IX_Projects_CreatedDate',
'IX_Bidders_ProjectNo',
'IX_Bidders_Id',
'IX_Bidders_CustId',
'IX_Customers_CustomerID'
)
ORDER BY t.name, i.name;
-- Verify Bidders.CustId data type
SELECT 'Bidders.CustId Data Type' AS CheckType;
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Bidders' AND COLUMN_NAME = 'CustId';
PRINT '';
PRINT '============================================================================';
PRINT 'DATABASE CHANGES COMPLETE';
PRINT '============================================================================';
GO
|