Author: Henrik Langbak and Kim Ginnerup, Bording Data
Meet the Pattern
A side effect is reduced and simplified code, increased performance and a more scalable solution that is almost independent of the amount of records in the table.
Know the Pattern
· The SQL Server is reading to many records
· Too much data sent over the network.
(If the SQL Server and the NAV Service tier is on different machines.)
· The NAV Service Tier receives and throw away data.
Ending Date Problem
Ending Date creates a dependency between two records. Changing a Starting Date, requires you to update the previous record. Changing the Ending Date requires you to update the next record.
If you add a record in between you will have to update both the before and the after record.
Use the Pattern
1. Create the view
You will need to create a view for every company in the database.
CREATEVIEW[dbo].[CRONUS$PriceView]
AS
SELECT[Code],[Starting Date],[Price]
FROMdbo.[CRONUS$Price]ASA
WHERE[Starting Date]=
(SELECTMAX([Starting Date])
FROMdbo.[CRONUS$Price]ASB
WHEREB.[Code]=A.[Code]AND
B.[Starting Date]<=GETDATE())
2. Create the Table object
3. Implement the code
4. Create a deployment codeunit
The codeunit needs to Create or Alter the views for all companies.
To see an example of how to talk to SQL Server using .NET see Waldo’s blog here:
http://dynamicsuser.net/blogs/waldo/archive/2011/07/19/net-interop-calling-stored-procedures-on-sql-server-example-1.aspx
5. Deployment
1. Delete the table objects referencing the views
2. Deploy and run the deployment codeunit
3. Deploy the new table objects that reference the views
General precaution
· If you add columns, you need to add them to the view first and then add them to the Table Object.
· If you want to remove columns from the view, you need to delete the Table Object, then change the view and last recreate the Table Object without the new columns.
Code example that accomplish the same but without using the pattern
Price.SETCURRENTKEY(Code,"Starting Date");
Price.SETFILTER("Starting Date",’..%1’, TODAY
IF Price.FINDSET THEN BEGIN
REPEAT
Price.SETRANGE(Code, Price.Code);
Price.FINDLAST;
Price.SETRANGE(Code);
PriceTemp := Price;
PriceTemp.INSERT;
UNTIL Price.NEXT = 0;
END;
// PriceTemp will contain the Prices
Comparison
· The table has a more complex key.
This will require setting and clearing more filters
· You need to read from more than one table.
Say you need to apply discount from a separate table.
This may give several lines in PriceTemp.
· If the Code field is controlled by a Type field.
The Code field reference keys in different tables
The NAV Example will require an unknown number of SQL calls and thereby an unknown number of trips to the server. The number of SQL calls is dependent on the number of distinct Code values.
The NAV example will require SQL Server to read all data older than or equal to TODAY, but only return one row per Code. Over time, as old data piles up in the system, the NAV code will perform slower because the SQL statements will be slower.
The Pattern makes a scalable solution with a predictable performance. The performance will not deteriorate at the same rate as the NAV code example.
NAV Usages
Ideas for improvement
Query Object should be able to handle sub-selects and Unions. A simple solution could be to allow the NAV developer to specify the actual Select statement inside the query Object in clear text. Opening up for writing your own queries and map the projection to the Query-defined fields will make the query Object very versatile and remove the pressure from Microsoft trying to create all the different permutations that a select statement can have. Microsoft and others have all tried to create wizards that can create SQL select statement. They all end up having a clear text option.