Tag Archives: MS SQL

Handling a datetime in NAV with super accuracy

So, as I have promised – some neater code for exact datetime handling in NAV.

The same concept, as described in the earlier post. I am going to use text string to store formatted and sortable datetime value. The only difference – I’m employing a power of .NET to do so. .NET enables me to utilise culture, and most importantly CultureInfo.InvariantCulture. Why would I do so? Simply because initial root of issue was the fact that I was in need to accept by NAV a very accurate datetime from external sources. I can hardly imagine a situation when I would need exact datetime in NAV. Dealing with data from other app which are much more accurate in terms of time handling comparing to NAV perhaps is only case when we need to deal with milliseconds.

So, then the goal is to store data within NAV SQL database with high level of accuracy and same time to allow external system to read and write this data.

And this is simple: two new functions – FormatDT  and EvaluateDT – and this is pretty much it.

The first one, FormatDT simply accept datetime value. I am assuming my datetime come from webservice, therefore always UTC. So I just apply to it CultureInfo.InvariantCulture and convert it to string. Therefore, it saved in SQL DB in UTC, but as string. Same as NAV handle dates (yes, dates are always UTC in NAV SQL DB and only formatted to local time on UI). So then in DB we have an UTC date string formatted as sortable datetime. Like this one: ‘2017-08-31T19:58:29.2780000Z’

FormatDT(DateTimeString : DateTime) : Text
DotNetDateTime := DateTimeString;
EXIT(DotNetDateTime.ToString('o', CultureInfo.InvariantCulture));

The next task then is to evaluate it back to datetime to present it on my SOAP service.

The advantage of .NET here is the fact that TryParse method will inherit a parent culture. So, on SOAP service it will always be UTC, as NAV SOAP services have invariant culture.

On NAV UI (windows client, web client, phone, tablet, etc..) this function will inherit the client culture, which is in turn inherited from local machine. Pretty, isn’t it?

EvaluateDT(DateTimeText : Text) : DateTime
DotNetDateTime := DotNetDateTime.DateTime(0);
DotNetDateTime.TryParse(DateTimeText, DotNetDateTime);
EXIT(DotNetDateTime);

So conclusion then:

  1. Datetime stored as text string allowing us a microseconds accuracy, micro, not even milliseconds and stored in SQL DB in UTC, same way as standard NAV would store dates/times in UTC.
  2. Datetime evaluated for UI or SOAP service same way as standard NAV would do.

Job done.

A test objects here:

 

Some notes on MS SQL timestamps in Dynamics NAV 2016.

My data have a timestamp. A datetime datatype. And it is vital to me as business process dictates so some logic decisions must be made based on record datetime.  What I have noticed is that all my datetimes ends with 0 or 3 or 7 milliseconds. Like 01/01/2017 20:54:253. Or 01/01/2017 20:54:250. Or 01/01/2017 20:54:257. But never 01/01/2017 20:54:254.

Quite strange, as I have assumed the tick is a millisecond. Which is true in NAV – the smallest tick is a millisecond (1/1000 second) indeed. So why then always 0 3 or 7 in the database? I quickly created a nav code like this:

OnRun()
MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyTable."Create Date" := MyDatetime + i;
  MyTable.INSERT;
END;

What would you expect of this code? I was expecting 1000 records created with one tick datetime difference. I’m using CLEAR(MyTable); as my table has autoincrement primary key, so MyTable.INIT would not work as INIT does not clearing the primary key value, so INSERT will fail then as we let SQL server to decide primary key.

And indeed, I had 1000 records. And all of them ends with 0, 3 or 7. Mystery? There must be no mystery in IT. So I amended the code then to dump data to simple text file same time as I am inserting to table.

MyFile.TEXTMODE := TRUE;
MyFile.WRITEMODE := TRUE;
MyFile.CREATE('c:\temp\test.txt');
MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyTable."Create Date" := MyDatetime + i;
  MyTable.INSERT;
  MyFile.WRITE(FORMAT(MyTable."Create Date", 0, '<Day,2>/<Month,2>/<Year4> <Hours24,2>:<Minutes,2>:<Seconds,2><Second dec.><Comma,.>'));
END;
MyFile.CLOSE;

And results were truly surprising.

Text File SQL table
30/08/2017 11:58:29.271 30/08/2017 11:58:29.270
30/08/2017 11:58:29.272 30/08/2017 11:58:29.273
30/08/2017 11:58:29.273 30/08/2017 11:58:29.273
30/08/2017 11:58:29.274 30/08/2017 11:58:29.273
30/08/2017 11:58:29.275 30/08/2017 11:58:29.277
30/08/2017 11:58:29.276 30/08/2017 11:58:29.277
30/08/2017 11:58:29.277 30/08/2017 11:58:29.277
30/08/2017 11:58:29.278 30/08/2017 11:58:29.277
30/08/2017 11:58:29.279 30/08/2017 11:58:29.280
30/08/2017 11:58:29.280 30/08/2017 11:58:29.280

 

As you can see – in the file it is one tick 1/1000 of second, as expected. But in SQL the tick is about 1/300 second and rounded to nearest. Thank you, MS, for your infinite wisdom!

I can prove the point – https://stackoverflow.com/questions/715432/why-is-sql-server-losing-a-millisecond

Of course, there is a SQL datatype datetime2 with precision of 100 nanoseconds. But it is not available in NAV.

So, then conclusion: never use NAV datetime if you want one tick precision. It is not here. Yes, NAV can handle 1/1000 of a second, but SQL cannot!

To prove the point, I have added two new fields to my table: Create Date and Create Time, with NAV datatypes Date and Time respectively. And slightly amended the code

MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyDatetime += 1;
  MyTable."Create DateTime" := MyDatetime;
  MyTable."Create Date" := DT2DATE(MyDatetime);
  MyTable."Create Time" := DT2TIME(MyDatetime);
  MyTable.INSERT;
END;

I somehow was under impression that on SQL end NAV Time datatype handled differently. But it is not. On SQL end NAV Time = SQL datatype DateTime. Which is rounded to 1/300 of a second.

So, there is no way in NAV 2016, 2017 or any other version of NAV to record a time with precision of one tick, I mean 1/1000 second. No way.

Well, there is. A text string of length 23 and some stupid weird code to format datetime received from external sources (where it is high-precision) to string, and back to datetime when our external source read nav data via webservice. I know, it is ugly. But I cannot see better way.

DatetimeToString(MyDatetime : DateTime) : Text
IF MyDatetime = 0DT THEN
  MyDatetime := CREATEDATETIME(01011753D, 000000T);
String := FORMAT(MyDatetime, 0, '<Day,2>/<Month,2>/<Year4> <Hours24,2>:<Minutes,2>:<Seconds,2><Second dec.><Comma,.>');
IF STRLEN(String) = 19 THEN
  String := String + '.';
IF STRLEN(String) < 23 THEN
  String := PADSTR(String, 23, '0');
EXIT(String);
StringToDatetime(MyDatetimeAsString : Text) : DateTime
EVALUATE(Day, COPYSTR(MyDatetimeAsString, 1, 2));
EVALUATE(Month, COPYSTR(MyDatetimeAsString, 4, 2));
EVALUATE(Year, COPYSTR(MyDatetimeAsString, 7, 4));
EVALUATE(MyTime, COPYSTR(MyDatetimeAsString, 12, 12));
MyDate := DMY2DATE(Day, Month, Year);
MyDatetime := CREATEDATETIME(MyDate, MyTime);
EXIT(MyDatetime);

And there is another issue – the datetime stored in NAV always stored in UTC and then formatted to local time on NAV UI. On webservice it is UTC though. So, then all this handling needs to be implemented in your code when you do format datetime to string and vice versa, or to be precise, when you display this data in NAV.

I will cover this in separate post though.

UPDATE: I actually see a better way of managing this. Still using strings, but utilizing the power of .NET – so cleaner and faster code then. New post will follow on this.

Dynamics NAV and SQL Linked tables

While fiddling with my dozens of linked tables I’ve came across an interesting issue.

I’ve got a table with simple primary key (field 1). 1. This table is connected to SQL View, and the View is pointing to SQL Table. When I run some code against data in my table I’ve identified that there is number of records with same primary key! Well, it is not something you can see in NAV world.

I did some investigation on this, and found, that my SQL table (which wasn’t created by me, but provided by third-party organisation) has no primary key on it, therefore it is fine to have duplicate values in any (or even all fields) for some (or even all) records.

It seems Dynamics NAV Linked tables can’t handle this situation properly. Well, when you define any table in NAV it has to have a primary key, and you will expect it to be unique.  But not for a linked tables. Linked tables inherits the schema defined in the SQL side, so it may have non-unique values in your primary key field.

I’ve tried to reproduce the issue – and have done it quite successfully, please have a look to pictures below:

  1. NAV table structure and Primary Key
    SQL1
  2. SQL table Definition
    SQL2
  3. Data I’ve put to SQL table
    SQL3
  4. Data I can see in NAV system
    SQL4

So, what we can do to resolve this? In ideal world we should always have same primary key in SQL table and NAV linked table. In real life it may be difficult (you may have no right to alter tables provided by third-party organisations) – so if you unable to alter SQL table – then you have to handle this on NAV side by code… Of course, I would prefer to keep same keys on both sides, in Dynamics NAV and in SQL.

Convert SQL Script to Dynamics NAV table Text Object

Got a task – need to create dozens of SQL-NAV linked tables, and some of them have hundreds of fields. Boring and stupid work 🙂 So I’ve  made  a tool to convert SQL scripts to Dynamics NAV NAV text objects.

You can download stuff here. Provided with no warranty, free to distribute and amend (retaining a copyright).

Dynamics NAV and SQL Linked servers

Often there is requirement to get data from external sources. We all know about dataports (now xmlports), webservices, and even FILE.READ function. But sometimes there is more convenient way to get data from an external SQL server.
Since old times we do have ‘LinkedObject’ property on tables. What is it? It is just a property, which allow you to link a NAV table to SQL view in the same database. The field definitions in the view and NAV table should be the same, and NAV table name must be exactly the same as view name (but without company name). Ok, this give us a convenient way to access data on same server. What about external servers?
Quite simple: since sql 2005 we can link SQL server to another SQL server (and not only to MS SQL!).
So when we link sql server A and sql server B then we can access data from server B within server A. So we can create a vien on server A, which will read data from server B and then create an NAV linked table, attached to this view…
How do we do this?
On the server A run the following query:

USE [master]
GO
EXEC master.dbo.sp_addlinkedserver
@server = N’B’,
@srvproduct=N’SQL Server’ ;
GO

Then sort out security

EXEC master.dbo.sp_addlinkedsrvlogin
@rmtsrvname = N’B’,
@locallogin = NULL ,
@useself = N’True’ ;
GO

Then test connection

SELECT name FROM [A].master.sys.databases ;
GO

Then we can do something like this:

SELECT local.name AS Logins, linked.name AS LinkLogins
FROM master.sys.server_principals AS local
LEFT JOIN [B].master.sys.server_principals AS linked
ON local.name = linked.name ;
GO

If this worked – you are able to read server B data from server A.