Skip to content

Commit 0051b22

Browse files
BalinTomskclaude
andauthored
Make fn_read_fish_edit_list an inline TVF (wfFishViewer habitat timeout) (#93)
The multi-statement version built all 1,063 fish into a table variable with 10 UPDATEs before wfFishViewer.LoadHabitat's WHERE fish_id applied; the fish page hit the 30 s command timeout 24 times on prod since 2026-10-27. Same columns in the same order; one fish 1,841 ms -> 2 ms locally. Tests: unit_test@FishEditList.sql (3; TEST 1 failed on the old function). LIVE on prod 2026-11-15 (1,063 rows identical before/after). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ce52bab commit 0051b22

3 files changed

Lines changed: 133 additions & 65 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
Split out of `AGENTS.md` for readability. Newest entries first.
44

5+
- 2026-11-15: **`dbo.fn_read_fish_edit_list` is now an inline TVF.** The multi-statement version built every
6+
fish (1,063) into a table variable with 10 UPDATEs before `wfFishViewer.LoadHabitat`'s `WHERE fish_id =` applied;
7+
the fish page hit the 30 s command timeout 24 times on prod since 2026-10-27 (plus 4 in `LoadUnEditedFish`). Same
8+
columns in the same order; one fish 1,841 ms -> 2 ms locally. Tests: `unit_test@FishEditList.sql` (3; TEST 1 failed
9+
on the old function). **LIVE on prod 2026-11-15** (all 1,063 rows identical before/after, Smallmouth Bass page
10+
renders its habitat); local envionx applied; CTRL queued in `ctrlLogBag/pending` (CTRL unreachable).
11+
512
- 2026-11-14: **Map shapes by bounding box (envfish-droid P0-10, docapi 1.33.0).** New `dbo.fn_shapes_bbox_geojson(west,
613
south, east, north, zoom, limit)`: the `Lake_Shape` shapes crossing a box as GeoJSON, each `Reduce`d to one 256-px
714
pixel at the zoom and left out when under two pixels long, longest first, `truncated` past `limit` (default 500,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
SET QUOTED_IDENTIFIER ON
2+
GO
3+
/*
4+
Unit tests for dbo.fn_read_fish_edit_list -- the habitat row of a fish species.
5+
Callers: FishTracker.Resources.wfFishViewer.LoadHabitat (one fish, WHERE fish_id = @id)
6+
FishTracker.Editor.TFishEditor.LoadUnEditedFish (every fish).
7+
It was a multi-statement TVF that built the row for EVERY fish into a table variable before
8+
the viewer's WHERE applied; wfFishViewer.LoadHabitat hit the 30 s timeout 24 times on prod.
9+
10+
TEST 1 - the function is inline, so a fish_id filter reaches the base tables
11+
TEST 2 - base-rule (-1/-1) intervals and flags are returned, seasonal rules are ignored
12+
TEST 3 - a fish with no base rule still returns its row, habitat columns NULL
13+
*/
14+
BEGIN TRAN Test1
15+
DECLARE @tStart datetime2 = SYSUTCDATETIME(), @ElapsedMs int;
16+
DECLARE @type char(2) = (SELECT type FROM sys.objects WHERE object_id = OBJECT_ID('dbo.fn_read_fish_edit_list'));
17+
SET @ElapsedMs = DATEDIFF(millisecond, @tStart, SYSUTCDATETIME());
18+
IF ISNULL(@type, '') <> 'IF'
19+
RAISERROR ('TEST 1 FAIL [%dms]: fn_read_fish_edit_list is not an inline TVF', 16, -1, @ElapsedMs)
20+
ELSE
21+
PRINT 'TEST 1 PASS [' + CAST(@ElapsedMs AS varchar) + 'ms]: fn_read_fish_edit_list is inline';
22+
ROLLBACK TRAN Test1
23+
GO
24+
25+
BEGIN TRAN Test2
26+
DECLARE @tStart datetime2, @ElapsedMs int, @ok bit = 0;
27+
BEGIN TRY SET NOCOUNT ON;
28+
DECLARE @fam uniqueidentifier = NEWID(), @fish uniqueidentifier = NEWID();
29+
INSERT INTO dbo.fish_family (Family_id, Family_name, fid, created) VALUES (@fam, N'__TEST_FAMILY__', -999, GETUTCDATE());
30+
INSERT INTO dbo.fish (fish_id, fish_name, fish_latin, family_Id, water_type, created, stamp)
31+
VALUES (@fish, N'__TEST_FISH__', N'Testus habitatus', @fam, 3, GETUTCDATE(), GETUTCDATE());
32+
-- TR_ins_Fish created the base (-1/-1) rule and a seasonal 4..6 one
33+
DECLARE @ed uniqueidentifier = NEWID();
34+
UPDATE dbo.fish_Rule SET habitat = 5, feedsOver = 6, locked = 1, editor = @ed
35+
WHERE fish_Id = @fish AND periodStart = -1 AND periodEnd = -1;
36+
DECLARE @base uniqueidentifier = (SELECT id FROM dbo.fish_Rule WHERE fish_Id = @fish AND periodStart = -1 AND periodEnd = -1);
37+
DECLARE @seas uniqueidentifier = (SELECT TOP 1 id FROM dbo.fish_Rule WHERE fish_Id = @fish AND periodStart > 0);
38+
INSERT INTO dbo.real_interval (ri_parent_id, ri_type, ri_min, ri_low, ri_avg, ri_high, ri_max, ri_stamp) VALUES
39+
(@base, 17, 1, 10, 15, 20, 30, SYSUTCDATETIME()), -- temperature
40+
(@base, 9, 5, 6.5, 7, 8.5, 9, SYSUTCDATETIME()), -- pH
41+
(@base, 3, 2, NULL, NULL, NULL, 12, SYSUTCDATETIME()), -- depth
42+
(@base, 73, 40, NULL, NULL, NULL, 180, SYSUTCDATETIME()), -- hardness
43+
(@seas, 17, 99, 99, 99, 99, 99, SYSUTCDATETIME()); -- seasonal: must be ignored
44+
45+
SET @tStart = SYSUTCDATETIME();
46+
SELECT @ok = CASE WHEN tmL = 10 AND tmH = 20 AND tmLD = 1 AND tmHD = 30 AND phL = 6.5 AND phH = 8.5
47+
AND depthMin = 2 AND depthMax = 12 AND HardnessL = 40 AND HardnessH = 180
48+
AND habitat = 5 AND feedsOver = 6 AND locked = 1 AND editor = CAST(@ed AS varchar(36))
49+
AND water_type = 3 AND oxL IS NULL THEN 1 ELSE 0 END
50+
FROM dbo.fn_read_fish_edit_list() WHERE fish_id = @fish;
51+
IF @@ROWCOUNT <> 1 SET @ok = 0;
52+
SET @ElapsedMs = DATEDIFF(millisecond, @tStart, SYSUTCDATETIME());
53+
END TRY
54+
BEGIN CATCH
55+
SET @ok = 0; PRINT 'TEST 2 ERROR: ' + ERROR_MESSAGE();
56+
END CATCH
57+
IF @ok = 1
58+
PRINT 'TEST 2 PASS [' + CAST(@ElapsedMs AS varchar) + 'ms]: base-rule habitat values returned';
59+
ELSE
60+
RAISERROR ('TEST 2 FAIL [%dms]: base-rule habitat values wrong', 16, -1, @ElapsedMs)
61+
ROLLBACK TRAN Test2
62+
GO
63+
64+
BEGIN TRAN Test3
65+
DECLARE @tStart datetime2, @ElapsedMs int, @ok bit = 0;
66+
BEGIN TRY SET NOCOUNT ON;
67+
DECLARE @fam uniqueidentifier = NEWID(), @fish uniqueidentifier = NEWID();
68+
INSERT INTO dbo.fish_family (Family_id, Family_name, fid, created) VALUES (@fam, N'__TEST_FAMILY__', -999, GETUTCDATE());
69+
INSERT INTO dbo.fish (fish_id, fish_name, fish_latin, family_Id, created, stamp)
70+
VALUES (@fish, N'__TEST_FISH__', N'Testus nullus', @fam, GETUTCDATE(), GETUTCDATE());
71+
DELETE FROM dbo.fish_Rule WHERE fish_Id = @fish; -- drop the rules TR_ins_Fish created
72+
73+
SET @tStart = SYSUTCDATETIME();
74+
SELECT @ok = CASE WHEN fish_name = '__TEST_FISH__' AND habitat IS NULL AND tmL IS NULL
75+
AND depthMin IS NULL THEN 1 ELSE 0 END
76+
FROM dbo.fn_read_fish_edit_list() WHERE fish_id = @fish;
77+
IF @@ROWCOUNT <> 1 SET @ok = 0;
78+
SET @ElapsedMs = DATEDIFF(millisecond, @tStart, SYSUTCDATETIME());
79+
END TRY
80+
BEGIN CATCH
81+
SET @ok = 0; PRINT 'TEST 3 ERROR: ' + ERROR_MESSAGE();
82+
END CATCH
83+
IF @ok = 1
84+
PRINT 'TEST 3 PASS [' + CAST(@ElapsedMs AS varchar) + 'ms]: fish without a rule returns NULL habitat';
85+
ELSE
86+
RAISERROR ('TEST 3 FAIL [%dms]: fish without a rule lost or wrong', 16, -1, @ElapsedMs)
87+
ROLLBACK TRAN Test3
88+
GO

‎mssql/script02_Funct.sql‎

Lines changed: 38 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,78 +2794,51 @@ SELECT v.fish_id, v.fish_name FROM dbo.fish v
27942794
) AND z.fish_max_length < 65
27952795
GO
27962796
---------------------------------------------------------------------------------------------------------------------------------------------
2797-
IF EXISTS (SELECT * FROM sysobjects WHERE NAME = 'fn_read_fish_edit_list' AND xtype = 'TF')
2797+
IF OBJECT_ID('dbo.fn_read_fish_edit_list') IS NOT NULL
27982798
DROP function dbo.fn_read_fish_edit_list
27992799
GO
2800-
-- used in FishTracker.TFishEditor.LoadUnEditedFish()
2800+
-- Habitat row of every fish species: the base (-1/-1) fish_Rule and its real_interval values.
2801+
-- Called from FishTracker.Editor.TFishEditor.LoadUnEditedFish (every fish)
2802+
-- and FishTracker.Resources.wfFishViewer.LoadHabitat (WHERE fish_id = @fish_id).
2803+
-- Inline on purpose: the multi-statement version built every fish into a table variable before the
2804+
-- viewer's filter applied, and wfFishViewer.LoadHabitat hit the 30 s command timeout on prod.
2805+
-- real_interval PK is (ri_parent_id, ri_type) and UK_fish_Rule allows one base rule per fish, so
2806+
-- each join below matches at most one row.
28012807
-- 1 - sport, 2 - Coarse, 4 - commersial, 8 - invading
28022808
-- select * from dbo.fn_read_fish_edit_list() where fish_id = '6b45fea3-5cbe-4982-89af-c241eb5c6a36' ORDER BY fish_name ASC
28032809
CREATE FUNCTION [dbo].[fn_read_fish_edit_list]()
2804-
RETURNS @TBL TABLE ( fish_id uniqueidentifier, fish_name varchar(32), fish_latin varchar(64), synonims varchar(255)
2805-
, food_Type int, water_type int, feedsOver int, habitat int
2806-
, tuLD float, tuL float, tuC float, tuH float, tuHD float
2807-
, tmLD float, tmL float, tmC float, tmH float, tmHD float
2808-
, oxLD float, oxL float, oxC float, oxH float, oxHD float
2809-
, phLD float, phL float, phC float, phH float, phHD float
2810-
, veL float, veH float
2811-
, depthMin float, depthMax float
2812-
, saltL float, saltH float
2813-
, NitrateH float, NitrateL float, PhosphateH float, PhosphateL float
2814-
, HardnessL float, HardnessH float
2815-
, periodStart int, periodEnd int, editor varchar(128), locked bit
2816-
, fish_Type int, fish_ability int, react_color int, home_range float, distribution_area nvarchar(500), stamp datetime )
2810+
RETURNS TABLE
28172811
WITH SCHEMABINDING
28182812
AS
2819-
begin
2820-
INSERT INTO @TBL ( fish_id, fish_name, fish_latin, synonims, food_Type
2821-
, water_type, fish_Type, fish_ability, react_color, home_range, distribution_area, stamp )
2822-
SELECT fish_id, fish_name, fish_latin, alt_Name, food_Type, water_type, fish_Type
2823-
, fish_ability, react_color, fish_home_range, fish_distribution_area, stamp FROM dbo.fish;
2824-
2825-
update t SET t.depthMin = n.ri_min, t.depthMax = n.ri_max
2826-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2827-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 3
2828-
2829-
update t SET t.veL = n.ri_min, t.veH = n.ri_max
2830-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2831-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 41
2832-
2833-
update t SET t.saltL = n.ri_min, t.saltH = n.ri_max
2834-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2835-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 49
2836-
2837-
update t SET t.PhosphateL = n.ri_min, t.PhosphateH = n.ri_max
2838-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2839-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 57
2840-
2841-
update t SET t.NitrateL = n.ri_min, t.NitrateH = n.ri_max
2842-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2843-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 65
2844-
2845-
update t SET t.HardnessL = n.ri_min, t.HardnessH = n.ri_max
2846-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2847-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 73
2848-
2849-
update t SET t.oxLD=n.ri_min, t.oxL=n.ri_low, t.oxC=n.ri_avg, t.oxH=n.ri_high, t.oxHD=n.ri_max
2850-
from dbo.real_interval n RIGHT JOIN dbo.fish_Rule c ON c.id = n.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2851-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 33
2852-
2853-
update t SET t.phLD=ph.ri_min, t.phL=ph.ri_low, t.phC=ph.ri_avg, t.phH=ph.ri_high, t.phHD=ph.ri_max
2854-
from dbo.real_interval ph RIGHT JOIN dbo.fish_Rule c ON c.id = ph.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2855-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 9
2856-
2857-
update t SET t.tmLD=tm.ri_min, t.tmL=tm.ri_low, t.tmC=tm.ri_avg, t.tmH=tm.ri_high, t.tmHD=tm.ri_max
2858-
from dbo.real_interval tm RIGHT JOIN dbo.fish_Rule c ON c.id = tm.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2859-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 17
2860-
2861-
update t SET t.tuLD=tu.ri_min, t.tuL=tu.ri_low, t.tuC=tu.ri_avg, t.tuH=tu.ri_high, t.tuHD=tu.ri_max
2862-
from dbo.real_interval tu RIGHT JOIN dbo.fish_Rule c ON c.id = tu.ri_parent_id RIGHT JOIN @tbl t on t.fish_id=c.fish_id
2863-
WHERE c.periodStart=-1 AND c.periodEnd=-1 AND ri_type = 25
2864-
2865-
update t SET t.locked = c.locked, t.feedsOver=c.feedsOver, t.habitat=c.habitat, t.editor = c.editor
2866-
FROM @TBL t JOIN dbo.fish_Rule c ON t.fish_id=c.fish_id WHERE -1 = c.periodStart AND -1 = c.periodEnd
2867-
return
2868-
end
2813+
RETURN
2814+
SELECT f.fish_id, CAST(f.fish_name AS varchar(32)) AS fish_name, CAST(f.fish_latin AS varchar(64)) AS fish_latin
2815+
, CAST(f.alt_Name AS varchar(255)) AS synonims
2816+
, f.food_Type, f.water_type, r.feedsOver, r.habitat
2817+
, tu.ri_min AS tuLD, tu.ri_low AS tuL, tu.ri_avg AS tuC, tu.ri_high AS tuH, tu.ri_max AS tuHD
2818+
, tm.ri_min AS tmLD, tm.ri_low AS tmL, tm.ri_avg AS tmC, tm.ri_high AS tmH, tm.ri_max AS tmHD
2819+
, ox.ri_min AS oxLD, ox.ri_low AS oxL, ox.ri_avg AS oxC, ox.ri_high AS oxH, ox.ri_max AS oxHD
2820+
, ph.ri_min AS phLD, ph.ri_low AS phL, ph.ri_avg AS phC, ph.ri_high AS phH, ph.ri_max AS phHD
2821+
, ve.ri_min AS veL, ve.ri_max AS veH
2822+
, dp.ri_min AS depthMin, dp.ri_max AS depthMax
2823+
, sa.ri_min AS saltL, sa.ri_max AS saltH
2824+
, ni.ri_max AS NitrateH, ni.ri_min AS NitrateL, po.ri_max AS PhosphateH, po.ri_min AS PhosphateL
2825+
, hd.ri_min AS HardnessL, hd.ri_max AS HardnessH
2826+
, CAST(NULL AS int) AS periodStart, CAST(NULL AS int) AS periodEnd
2827+
, CAST(r.editor AS varchar(128)) AS editor, r.locked
2828+
, f.fish_Type, f.fish_ability, f.react_color, f.fish_home_range AS home_range
2829+
, CAST(f.fish_distribution_area AS nvarchar(500)) AS distribution_area, f.stamp
2830+
FROM dbo.fish f
2831+
LEFT JOIN dbo.fish_Rule r ON r.fish_Id = f.fish_id AND r.periodStart = -1 AND r.periodEnd = -1
2832+
LEFT JOIN dbo.real_interval dp ON dp.ri_parent_id = r.id AND dp.ri_type = 3
2833+
LEFT JOIN dbo.real_interval ph ON ph.ri_parent_id = r.id AND ph.ri_type = 9
2834+
LEFT JOIN dbo.real_interval tm ON tm.ri_parent_id = r.id AND tm.ri_type = 17
2835+
LEFT JOIN dbo.real_interval tu ON tu.ri_parent_id = r.id AND tu.ri_type = 25
2836+
LEFT JOIN dbo.real_interval ox ON ox.ri_parent_id = r.id AND ox.ri_type = 33
2837+
LEFT JOIN dbo.real_interval ve ON ve.ri_parent_id = r.id AND ve.ri_type = 41
2838+
LEFT JOIN dbo.real_interval sa ON sa.ri_parent_id = r.id AND sa.ri_type = 49
2839+
LEFT JOIN dbo.real_interval po ON po.ri_parent_id = r.id AND po.ri_type = 57
2840+
LEFT JOIN dbo.real_interval ni ON ni.ri_parent_id = r.id AND ni.ri_type = 65
2841+
LEFT JOIN dbo.real_interval hd ON hd.ri_parent_id = r.id AND hd.ri_type = 73
28692842
GO
28702843
--------------------------------------------------------------------------------------------------------------------------------------------------
28712844
IF EXISTS (SELECT * FROM sysobjects WHERE NAME = 'fn_edit_fish_general' AND xtype = 'IF')

0 commit comments

Comments
 (0)