/* Regel: SQLI-AX-008 – Kontinuerlig Dynamics AX-nummerserie er ikke ryddet op Alvorlighed: Lav (Low) Hvad og hvorfor En kontinuerlig serie må ikke have huller. Numre reserveret af afbrudte sessioner bliver liggende som optagede, til serien ryddes op; uden oprydning opstår huller og fejl, og serien bliver en flaskehals, fordi hver tildeling låser rækken i NUMBERSEQUENCETABLE, til transaktionen er færdig. Tærskler 7 dage (fast, AxNumberSequenceContinuousRule.MinimumDays), eller 2 × seriens eget CLEANINTERVAL (timer), hvis længere. Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxNumberSequenceContinuousRule) For kontinuerlige (CONTINUOUS = 1), ikke spærrede, ikke manuelle serier, der er taget i brug (NEXTREC > LOWEST): fund, når seneste oprydning er ældre end tærsklen eller aldrig er sket (1900-01-01). Seneste oprydning er LATESTCLEANDATETIME (UTC) i AX 2009+, og LATESTCLEANDATE + LATESTCLEANTIME (lokal tid, sekunder efter midnat) i Axapta 3.0. Mangler kolonnen, gives intet fund. Højst 25 fund pr. database. Collector: src/SQLInsight.Collectors/DynamicsAx/AxSequenceCollector.cs – AxSequenceCollector.CollectAsync (hver time). SQL: src/SQLInsight.Core/DynamicsAx/AxSchemaAdapter.cs – NumberSequences. Rettigheder: Valgfri AX-læseadgang, kun SELECT (db/permissions/grant-dynamics-ax-read.sql): Axapta 3.0: GRANT SELECT ON [bmssa].[NUMBERSEQUENCETABLE] TO []; AX 4.0/2009/2012/F&O: GRANT SELECT ON [dbo].[NUMBERSEQUENCETABLE] TO []; Mangler den, er reglen "ikke målbar" med den nøjagtige GRANT for databasen. Versioner: SQL Server 2005 og nyere (ikke 2000). Axapta 3.0: verificeret mod en kundes skema. AX 4.0, AX 2009, AX 2012 og F&O: baseret på Microsofts dokumenterede skema – ikke verificeret mod kundedata. Læsning: Kun SELECT med TOP, READ COMMITTED med SET LOCK_TIMEOUT 2000 (SNAPSHOT, når databasen tillader det), aldrig NOLOCK, i AX-databasen via [db].sys.sp_executesql. Kun i databaser genkendt som AX og valgt til overvågning. */ -- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden. -- Variant: Axapta 3.0 (skema bmssa): nummerserier (AxSchemaAdapter.NumberSequences) DECLARE @Database sysname; SET @Database = N'AXDB'; -- <== ret til AX-databasen DECLARE @Schema sysname; SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo DECLARE @Sql nvarchar(max); SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [bmssa].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [bmssa].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.'); DECLARE @Procedure nvarchar(300); SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO -- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) DECLARE @Database sysname; SET @Database = N'AXDB'; -- <== ret til AX-databasen DECLARE @Schema sysname; SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo DECLARE @Sql nvarchar(max); SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATE], n.[LATESTCLEANTIME], CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); DECLARE @Procedure nvarchar(300); SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO -- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) DECLARE @Database sysname; SET @Database = N'AXDB'; -- <== ret til AX-databasen DECLARE @Schema sysname; SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo DECLARE @Sql nvarchar(max); SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], n.[DATAAREAID], n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCE] = n.[NUMBERSEQUENCE] AND r.[DATAAREAID] = n.[DATAAREAID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); DECLARE @Procedure nvarchar(300); SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO -- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: nummerserier (AxSchemaAdapter.NumberSequences) DECLARE @Database sysname; SET @Database = N'AXDB'; -- <== ret til AX-databasen DECLARE @Schema sysname; SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo DECLARE @Sql nvarchar(max); SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (5000) CAST(n.[RECID] AS bigint), n.[NUMBERSEQUENCE], NULLIF(sc.[DATAAREA], N''''), n.[TXT], CAST(n.[LOWEST] AS bigint), CAST(n.[HIGHEST] AS bigint), CAST(n.[NEXTREC] AS bigint), n.[FORMAT], ISNULL(n.[BLOCKED], 0), ISNULL(n.[CONTINUOUS], 0), ISNULL(n.[CYCLIC], 0), ISNULL(n.[MANUAL], 0), CASE WHEN EXISTS (SELECT 1 FROM [dbo].[NUMBERSEQUENCEREFERENCE] r WHERE r.[NUMBERSEQUENCEID] = n.[RECID]) THEN 1 ELSE 0 END, n.[LATESTCLEANDATETIME], CAST(NULL AS int), CAST(n.[CLEANINTERVAL] AS float), DATEDIFF(minute, GETDATE(), GETUTCDATE()) FROM [dbo].[NUMBERSEQUENCETABLE] n LEFT JOIN [dbo].[NUMBERSEQUENCESCOPE] sc ON sc.[RECID] = n.[NUMBERSEQUENCESCOPE] ORDER BY (CAST(n.[NEXTREC] AS float) - n.[LOWEST]) / NULLIF(CAST(n.[HIGHEST] AS float) - n.[LOWEST], 0) DESC'; SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.'); DECLARE @Procedure nvarchar(300); SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql'; EXEC @Procedure @Sql; SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO -- <<< slut på genereret SQL