4-4-5 Accounting Periods are the Bane of my Existence - sql

We use 4-4-5 Accounting Periods. In case you aren't familiar with this, you can find info at Wikipedia.
To accomplish this, I created a date table with all the days for the next 10 years. I used the script (albeit modified) found here.
The creation script for my table is:
USE [RedFridayDates];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE TABLE [tblRedFridayAllDates] (
[DATE_ID] int NOT NULL,
[DATE] datetime NOT NULL,
[YEAR] smallint NOT NULL,
[MONTH] tinyint NOT NULL,
[DAY_OF_WEEK] tinyint NOT NULL,
[REDFRIDAY] bit NULL,
[Period] tinyint NULL,
[ForecastSales] money NULL)
ON [PRIMARY];
GO
The Period is my 4-4-5 "month".
I typically link my queries to it with the following template I created:
SELECT RED.[YEAR] as [Year],
RED.PERIOD as [RF Period],
FROM TableName
INNER JOIN REDFRIDAYDATES..TBLREDFRIDAYALLDATES RED
ON RED.date =
CAST (FLOOR (CAST (TableName.Date AS FLOAT)) AS DATETIME)
Most of my database usage is SQL 2000 so my dates are all datetime fields.
Is there a more efficient way to accomplish this? Is this the best Query template I could use? What about other ways to convert Date to Date time? Are there faster ways to do so?

I have used extracting the date part of the datetime into a persisted computed field (maybe indexed) and do all joins and searches on the computed field (date only). In effect you'd have the 2k8 new DATE type in pre-2k8, at the expense of 8 bytes (or even 4 bytes if you're willing to store it as int). The advantages is SARGability of datetimes by only date part.

Related

Datetime column cannot be pesisted

I have this SQL statement to create a table that stores the JSON string data and the event time found in that JSON string.
CREATE TABLE [dbo].[EventLog]
(
[EventID] INT NOT NULL IDENTITY(1,1),
[EventTime] AS CAST(JSON_VALUE(RawEvent, '$.EventTime') AS DATETIME ) PERSISTED,
[RawEvent] NVARCHAR(MAX) NOT NULL
)
However I get the following error below when I run this, I assume SQL Server does not know if the value fits DATETIME? is there a way to get this column defined?
Msg 4936, Level 16, State 1, Line 26
Computed column 'EventTime' in table 'Event' cannot be persisted because the column is non-deterministic.
You can use CONVERT with dates and have deterministic behavior as long as you specify certain date styles. As per the docs here, with the most common JavaScript date formats (since you are converting from JSON), you can use style 126 or 127, which are ISO8601 and ISO8601 with time zone. Your table, then, could be specified like this:
CREATE TABLE [dbo].[EventLog]
(
[EventID] INT NOT NULL IDENTITY(1,1),
[EventTime] AS CONVERT(DATETIME, JSON_VALUE(RawEvent, '$.EventTime'), 126) PERSISTED,
[RawEvent] NVARCHAR(MAX) NOT NULL
)
Alas, this is explained in the documentation:
CAST Deterministic unless used with datetime, smalldatetime, or sql_variant.
You may be able to parse the date and reconstruct the value using datefromparts() or datetimefromparts().

'Summing' a date field in SQL - any ideas?

I'm creating an application that is essentially an integrity check between two databases - one is MSSQL and one is an old provider Btrieve. As part of the requirements all columns for every table need to be compared to ensure the data matches. Currently we loop through each table, get the basic count of the table in both DBs, and then delve into the columns. For numeric fields we do a simple SUM, and for text fields we sum up the length of the column for every row. If these match in both DBs, it's a good indicator the data has migrated across correctly.
This all works fine, but I need to develop something similar for datetime fields. Obviously we can't really SUM these fields, so I'm wondering if anyone has ideas on the best way to approach this. I was thinking maybe the seconds since a certain date but the number will be huge.
Any other ideas? Thanks!
The most straightforward answer to me would be to convert the date or datetime fields to integers with the same format. YYYYMMDD or YYYYMMDDHHmmss work just fine as long as your formats use leading zeroes. In SQL Server, you can do something like:
SELECT SUM(CAST(REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR(20),DateTimeColumn,120),' ',''),':',''),'-','') AS BIGINT)) .....
Alternately, you can convert them to either the number of days from a given date ('1970-01-01'), or the number of seconds from a given date ('1970-01-01 00:00:00') if you use time.
SELECT SUM(DATEDIFF(DAY,'19700101',DateColumn)) ....
I'm not familiar enough with Btrieve to know what kinds of functions are available for formatting dates, however.
Using "Except" in SQL on the lines of Numeric fields you can compare the date counts in both the tables. For the Old source you may generate the select statement using excel or in the native database and bring to the SQL Server. For demonstration purpose I have used two tables and showing Except example below.
IF EXISTS (SELECT * FROM sys.objects
WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[DateCompareOld]') AND
TYPE IN (N'U'))
DROP TABLE [dbo].[DateCompareOld]
GO
CREATE TABLE dbo.DateCompareOld
(
AsOf DATETIME
)
INSERT INTO DateCompareOld
SELECT '01/01/2016' UNION ALL
SELECT '01/01/2016' UNION ALL
SELECT '01/01/2016' UNION ALL
SELECT '01/02/2016' UNION ALL
SELECT '01/02/2016' UNION ALL
SELECT '01/02/2016'
IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[DateCompareNew]') AND TYPE IN (N'U'))
DROP TABLE [dbo].[DateCompareNew]
GO
CREATE TABLE dbo.DateCompareNew
(
AsOf DATETIME
)
INSERT INTO DateCompareNew
SELECT '01/01/2016' UNION ALL
SELECT '01/01/2016' UNION ALL
SELECT '01/01/2016' UNION ALL
SELECT '01/02/2016' UNION ALL
SELECT '01/02/2016' UNION ALL
SELECT '01/02/2016'
SELECT AsOf,COUNT(*) AsOfCount
FROM DateCompareOld
GROUP BY AsOf
Except
SELECT AsOf,COUNT(*) AsOfCount
FROM DateCompareNew
GROUP BY AsOf
Unless the date range used by rows in the database is extreme (like dates of astronomical stars being born and dying), it should be just as valid to convert the dates to an integer. This can be done any of several ways and is slightly database-specific, but converting 2016-01-04 to 20,160,104 is going to work fine.
Even SQL Server allows ORD(date_field) like expressions to obtain the internal representation. But this can also be done in a portable, system-agnostic means like
datediff(day, 'January 1, 1901', date_field)
if keeping track of days is sufficient, or
datediff(second, 'January 1, 1901', date_field)
if keeping track of seconds is needed.
Maybe it is not much help, maybe is something:
declare #d1 datetime; set #d1 = '2016-01-05 12:09'
declare #d2 datetime; set #d2 = '1970-04-05 07:09'
declare #d3 datetime; set #d3 = '1999-12-12 23:05'
declare #d4 datetime; set #d4 = '1999-12-12 23:06'
declare #i1 bigint
declare #i2 bigint
declare #i3 bigint
declare #i4 bigint
select #i1 = convert( bigint, convert( timestamp, #d1 ) )
select #i2 = convert( bigint, convert( timestamp, #d2 ) )
select #i3 = convert( bigint, convert( timestamp, #d3 ) )
select #i4 = convert( bigint, convert( timestamp, #d4 ) )
select #i1
select #i2
select #i3
select #i4
select #i1 ^ #i2 ^ #i3 ^ #i4
I think you could do something like this on the SQL Server side to find the center ("average") value of the column. Then use that value on the Btrieve side to avoid overflow issues where I'm guessing you're more constrained.
-- January 1, 2000 value pulled out of the air as a stab in the dark
select
dataadd(
second,
avg(cast(datediff(datediff(second, '20000101', <data>) as bigint)),
'20000101'
) /* find the center */
I wouldn't be surprised if you had to resort to a floating point type with Btrieve or partition your scans into smaller ranges to avoid intermediate sums that get too big. And you might want to use a cursor and randomize the ordering of the rows so you don't hit them in a sorted order that causes an overflow. At this point I'm just speculating since I haven't seen any of the data and my knowledge of Btrieve is so ancient and minimal to begin with.
I also have a feeling that some of this effort is all about satisfying some uneasiness on the part of non-technical stakeholders. I'm sure you could come up with checksums and hashes that would work better but this summing concept is the one they can grasp and will set their minds at ease on top of being somewhat easier to implement quickly.

How to compare smalldatetime in stored procedure

I'm writing stored procedure to compare dates but it's not working properly. How can I make it so it compares only the dates but not the time? What I'm trying to do is compare the times and if the Id is null than insert a new entry with the same name but new time. I'm keeping multiple entries with same name but different test time.
ALTER PROCEDURE [dbo].[UL_TestData]
(
#Name varchar(30),
#Test_Time smalldatetime,
#ID INT output
)
AS
Declare #UpdateTime smalldatetime
SELECT #ID=ID FROM Info_User WHERE Name=#Name AND UpdateTime= #Test_Time
IF(#ID IS NULL)
BEGIN
INSERT INTO Info_User (Name, UpdateTime) VALUES (#Name, #UpdateTime)
END
there are a lot of solutions to this depending on what type of DBMS, however here is one:
SELECT #ID=ID FROM Info_User WHERE Name=#Name AND floor(cast(#UpdateTime as float))= floor(cast(#Test_Time as float))
this works because smalldatetime's date is stored a whole numbers, where the time is stored as decimals.
I would cast the dates to a plain date which makes this solution independent of implementation details
select #ID=ID
from info_user
where Name = #Name
and cast (UpdateTime as Date) = Cast(#TestTime as Date)
However, I would either add the date part of the UpdateTime as an additional (calculated) column or split the information into a date and a time part. This makes it much easier to query entries by the plain date.
As a rule of thumb: The type of columns (in general: the table layout) greatly depends on the type of query you usually run against your data.
Edit: As attila pointed out, the date datatype only exists in version 2008 and up

how to take specific string with getdate function in sql

i have one crm application. i want to store conversions date and specific time at which time that thread created that to show. how ever i have also one [datetime] column but time is not correct. event though this time part should be default from sql. how ever i want this time part is accurate and how i implement this. is that way to store acuurate time part from sql.
is that way to store time part from getdate function of sql.
for more reference i put table
Inquiry_id varchar(50)
Inquiry_subject varchar(50)
Service_id numeric(18, 0)
Priority_id numeric(18, 0)
User_id varchar(50)
Status_id numeric(18, 0)
body varchar(1024)
Email_Address varchar(50)
IsDisplay bit
IsRead bit
IsReplied bit
TimeStamp datetime
Activity_start_time datetime
Activity_expire_time datetime
please help me..
To display your DateTime columns as Date and separate Time columns, do this in a SELECT statement (or in a view)
SELECT
CONVERT(VarChar(20), Activity_Start_Time, 108) AS ActivityStartTime,
CONVERT(VarChar(30), Activity_Start_Time, 101) AS ActivityStartDateUS
FROM YourExampleTableAbove
This will produce a result like
ActivityStartTime ActivityStartDateUS
----------------- -------------------
08:10:27 4/23/2013
15:58:40 4/29/2013
For more options, read about the Convert() function for TSQL http://msdn.microsoft.com/en-us/library/ms187928.aspx

How to enter a Date into a table in TSQL? (Error converting data type varchar to datetime)

I want to enter 30/10/1988 as the date to a DOB column in a table using a procedure
alter procedure addCustomer
#userName varchar(50),
#userNIC varchar(50),
#userPassword varchar(100),
#userDOB datetime,
#userTypeID int,
#userEmail varchar(50),
#userTelephone int,
#userAddress char(100),
#userCityID int,
#status int output
as
declare #userID int
declare #eid int
declare #tid int
declare #aid int
execute getLastRaw 'userID','tblUserParent', #userID output
insert into tblUserParent values (#userID, #userName, #userNIC, #userPassword, #userDOB, #userTypeID)
execute getLastRaw 'addressID','tblAddress', #aid output
insert into tblAddress values (#aid, #userAddress, #userID, #userCityID)
execute getLastRaw 'emailID','tblEmail', #eid output
insert into tblEmail values (#eid, #userEmail, #userID)
execute getLastRaw 'telephoneID','tblTelephoneNO', #tid output
insert into tblTelephoneNO values (#tid, #userTelephone , #userID)
insert into tblUserCustomer values (#userID, #eid , #tid, #aid)
...but it gives an error when i enter like this '30/10/1988'
Msg 8114, Level 16, State 5, Procedure addCustomer, Line 0 Error converting data type varchar to datetime.
...but when I enter like only the 30/10/1988
Incorrect syntax near '/'
How do I fix this?
If you would truly like to avoid the possibility of ambiguous dates based, then you should always enter it in one of the two unambiguous date formats Answer has already been selected and it's valid but I'm a believer in spreading the knowledge ;)
As noticed by #cloud and my post representing a younger, and less wise me with a link only answer, I'll pop the contents of the archive of Jamie Thompson's answer for unambiguous date formats in TSQL
tl;dr;
yyyy-MM-ddTHH24:mi:ss
yyyyMMdd HH24:mi:ss
One of the most commonly used data types in SQL Server is [datetime]
which unfortunately has some vagaries around how values get casted. A
typical method for defining a [datetime] literal is to write it as a
character string and then cast it appropriately. The cast syntax looks
something like this: DECLARE #dt NVARCHAR(19) = '2009-12-08 18:00:00';
SELECT CAST(#dt AS datetime);
Unfortunately in SQL Server 2005 the result of the cast operation may
be dependent on your current language setting. You can discover your
current language setting by executing: SELECT ##LANGUAGE To
demonstrate how your language setting can influence the results of a
cast take a look at the following code: ALTER DATABASE tempdb
SET COMPATIBILITY_LEVEL = 90 ; --Behave like SQL Server 2005
USE tempdb
GO
DECLARE #t TABLE (
dateString NVARCHAR(19)
);
INSERT #t (dateString)
VALUES ('2009-12-08 18:00:00') --'yyyy-MM-dd hh24:mi:ss'
, ('2009-12-08T18:00:00') --'yyyy-MM-ddThh24:mi:ss'
, ('20091208 18:00:00') --'yyyyMMdd hh24:mi:ss'
SET LANGUAGE french;
SELECT 'french' AS lang
, DATENAME(MONTH,q.[dt]) AS mnth
, q.[dt]
FROM (
SELECT CAST(dateString AS DATETIME) AS dt
FROM #t
)q;
SET LANGUAGE us_english;
SELECT 'us_english' AS lang
, DATENAME(MONTH,q.[dt]) AS mnth
, q.[dt]
FROM (
SELECT CAST(dateString AS DATETIME) AS dt
FROM #t
)q; We are taking the value which can be described in words as “6pm on 8th December 2009”, defining it in three different ways, then
seeing how the ##LANGUAGE setting can affect the results. Here are
those results: french language datetime Notice how the interpretation
of the month can change depending on ##LANGUAGE. If
##LANGUAGE=’french’ then the string '2009-12-08 18:00:00' is
interpreted as 12th August 2009 (‘août’ is French for August for those
that don’t know) whereas if ##LANGUAGE=’us_english’ it is interpreted
as 8th December 2009. Clearly this is a problem because the results of
our queries have a dependency on a server-level or connection-level
setting and that is NOT a good thing. Hence I recommend that you only
define [datetime] literals in one of the two unambiguous date formats:
yyyy-MM-ddTHH24:mi:ss yyyyMMdd HH24:mi:ss That was going to be the end
of this blog post but then I found out that this behaviour changed
slightly in SQL Server 2008. Take the following code (see if you can
figure out what the results will be before I tell you): ALTER
DATABASE tempdb
SET COMPATIBILITY_LEVEL = 100 ; --Behave like SQL Server 2008
GO
USE tempdb
GO
SET LANGUAGE french;
DECLARE #dt NCHAR(10) = '2009-12-08 18:00:00'; --Ambiguous date
format
SELECT CAST(#dt AS datetime) AS [ExplicitCast]
, DATENAME(MONTH,#dt) AS [MonthFromImplicitCast]
, DATENAME(MONTH,CAST(#dt AS datetime)) AS
[MonthFromExplicitCast]; Here we are doing three different things with
our nchar literal: explicitly cast it as a [datetime] extract the
month name from the char literal using the DATENAME function (which
results in an under-the-covers implicit cast) extract the month name
from the char literal using the DATENAME function after it has been
explicitly casted as a [datetime] Note that the compatibility level is
set to SQL Server 2008 and ##LANGUAGE=’french’. Here are the results:
image (Were you correct?) Let’s take a look at what is happening here.
The behaviour when we are explicitly casting as [datetime] hasn’t
changed, our nchar literal is still getting interpreted as 12th August
rather than 8th December when ##LANGUAGE=’french’. The
[MonthFromExplicitCast] field is interesting though, it seems as
though the implicit cast has resulted in the desired value of 8th
December. Why is that? To get the answer we can turn to BOL’s
description of the DATENAME function syntax: image The implicit cast
is not casting to [datetime] at all, it is actually casting to [date]
which is a new datatype in SQL Server 2008. The new date-related
datatypes in SQL Server 2008 (i.e. [date], [datetime2], [time],
[datetimeoffset]) disregard ##LANGUAGE and hence we get behaviour that
is more predictable and, frankly, better. These new behaviours for SQL
Server 2008 were unknown to me when I began this blog post so I have
learnt something in the course of authoring it, I hope it has helped
you too. No doubt someone somewhere is going to get nastily burnt by
this at some point, make sure that it isn’t you by always using
unambiguous date formats: yyyy-MM-ddTHH24:mi:ss yyyyMMdd HH24:mi:ss
regardless of which version you are on!
The following works in both SQL Server and MySql without ambiguity: yyyy-mm-dd, like so:
INSERT INTO TableName(DateColumn) VALUES ('1988-10-30');
...as an added benefit there's no question of whether it's a US or European style date on days like the fourth of March...
See if there is a culture setting that you can change to allow you to use dd/mm/yyyy. I believe it is expecting mm/dd/yyyy.
A potentially easy way around the problem is to use a date format with no ambiguity between mm/dd/yyyy and dd/mm/yyyy such as dd-mmm-yyyy, eg: 30-OCT-1988