Convert Number to Date in SQL Server with Exception in Month-End - sql

I have an INT column in my table. This column stores the date in special format. I'm trying to convert that column to DATE type.
For example, we keep '2016-03-14' as 20160314.
The exception is, for the last of each month, we do not store day.
So for '2016-03-31' we store 201603 and I have to consider if the number is less than 999999 or not to find is the number represents a month-end or other days in month.
So far, I have 2 queries to achieve this task:
Query 1:
This is all mathematics formula.
declare #k int = 20160321
--declare #k int = 201603
select
IIF(#k < 999999
, EOMONTH(DATEFROMPARTS(#k /100, #k % 100, 1), 0)
, DATEFROMPARTS(#k /10000, (#k / 100) % 100, #k % 100)
)
Query 2:
This one is using string manipulation.
declare #k int = 20160321
--declare #k int = 201603
select
IIF(#k < 999999
, EOMONTH(cast(LEFT(#k, 4) + '-' + RIGHT(#k, 2) + '-01' as date), 0)
, cast(LEFT(#k, 4) + '-' + RIGHT(LEFT(#k, 6), 2) + '-' + RIGHT(#k, 2) as date )
) AS DateColumn
I need to do the conversion formulas in WHERE clause. Something like:
SELECT K, Dt, Name -- and more fields
FROM tbl
WHERE IIF(K < 999999
, EOMONTH(DATEFROMPARTS(K /100, K % 100, 1), 0)
, DATEFROMPARTS(K /10000, (K / 100) % 100, K % 100)
) < GetDate()
And performance is important
Question: Is there a better way to do this? Possibly a ways that SQL Server can use the clustered index I have on K column.

I would expect that Query 1 would perform better, but you'd have to test it to be sure. I have no idea what kind of performance datefromparts() and datetimefromparts() have. They're relatively new, so it wouldn't shock me if they were magically terrible for no good reason. You're comparing performance of string manipulation and type casting vs the performance of arithmetic and type casting. My guess is it's mostly a wash, but that arithmetic is probably faster.
The options that strike me for a solution performance-wise are: a) Add a datetime column to your table. b) Add a computed column to the table. If you make a PERSISTED column, you could even create an index on it. c) Create a view (an indexed view if you can jump through the requirement hoops). d) Create a new table with the datetime field and update it.
Both (a) and (d) duplicate data, so they're not as great of solutions as they first appear.
I always have found computed columns to be a little gross, but they work well enough. If you create a view, you'll have to INNER JOIN it back to use it, but in most systems JOINS are very fast.
I'd probably look at creating a persisted computed column or a view. The best solution, of course, is to not store dates as integers in the first place.
You might try this query:
SELECT CASE
WHEN #K < 999999 THEN EOMONTH(TRY_CONVERT(date, CAST(#K * 100 + 1 AS VARCHAR(10))))
ELSE TRY_CONVERT(date, CAST(#K AS VARCHAR(10)))
END AS K_Date
The reason this might work is that YYYYMMDD is one of the ISO date formats. I'd try TRY_CONVERT() before CONVERT() because the query engine can decide to evaluate all the CONVERT without looking at the CASE conditional and throw errors.

Related

Convert date to number data type

I am trying to convert date to number like below, not sure which function works better.
Database used is SQL Server.
Table details
create table test
(
id varchar(255),
call_date varchar(255)
);
insert into test('26203', '14-Aug-2020');
I need output as 4405726203 -- its concatenation of date (14-Aug-2014) + id (26203)
This is too long for a comment.
SQL Server allows you to convert a datetime to a float. That would be:
select cast(dte as float)
from (values (convert(datetime, '14-Aug-2020'))) v(dte)
However, the corresponding floating point value is 44055 (https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d142a64db0872e7572eb4fbd6d5d5fe7). It is a bit of mystery what your intention is.
You could subtract 2, but that seems arbitrary. You could calculate the number of days since 1899-12-30. But that also seems arbitrary.
In any case, once you figure out how to convert the date to the number you want, just use concat() to combine the values.
I have found the solution:
convert(varchar,CAST(CONVERT(datetime,call_date) as bigint)) + id
Under the hood, a SQL Server DateTime is a tuple of 2 32-bit integers:
The first integer is a count of days since since the epoch, which for SQL Server is 1 January 1900
The second integer is a count of milliseconds since start of day (00:00:00.000). Except that the count ticks up in 3- or 4-milliscond increments. Microsoft only knows why that decision was made.
You can get the count of days since the epoch with
convert( int, convert( date, t.call_date ) )
[wrap that in convert(varchar, ... ) to turn it into a string]
Looks like your id is already a varchar, so you can say:
select compound_key = convert(varchar,
convert(int,
convert(date,
call_date
)
)
)
+ t.id
from test t
I would suggest padding both fields with leading zeros to a fixed length so as to avoid possible collisions (assuming you're trying to generate a key here). Signed 32-bit integer overflows a 2.1 billion-ish, so 9 digits for each field is sufficient.
This works
select concat(datediff(d, 0, cast(call_date as date)), id)
from
(values ('26203','14-Aug-2020')) v(id, call_date);
Results
4405526203

SQL Server: convert and merge separate date and time columns (both integers)

I am working on a query, where I have to fill a table's column ([Result_DateTime]) with datetime values.
The datetime based on two columns, both integer. One contains the date and the other is the time, as it is.
As you can see from the picture, it is a bit difficult to merge and convert these values to an actual datetime, because of the way they are stored. Mainly the time value causing problems.
I concluded how to convert the date column:
CONVERT(DATETIME, LEFT(20200131, 8))
but then I got stuck - what to do with the time and how to merge the two into one datetime effectively?
Using function STUFF looks nasty...
Could you help me out please? I am using SQL Server 2014
Below is one method to do it:
SELECT CAST(Convert(DATE, LEFT(DATEUPDT, 8)) AS VARCHAR(10)) +' '+CAST (TIMEUPDT/100 AS VARCHAR(4)) + ':' + CAST(TIMEUPDT%(100 * (TIMEUPDT/100)) AS VARCHAR(10))+':00'
FROM TEST_TABLE_TIME;
I think I found one solution. What I tried is to avoid using varchar conversions because of how the time column's zeros are cut off. However, I am not convinced that this is the most effective way to do so:
DECLARE #DateInt int = 20200131
DECLARE #TimeInt int = 345 -- 03:45:00
SELECT CONVERT(DATETIME, LEFT(#DateInt, 8)) +
CAST(DATEADD(second, FLOOR(#TimeInt / 100) * 3600 + FLOOR(#TimeInt / 1) % 100 * 60, 0) as datetime)
I was testing it with various time values, it is working.

How do I convert a 5 or 6 digit decimal to a date in sql

I've got a column that shows the date as a decimal such as 101118 for 10-11-18 and 90118 for 09-01-18. I am trying to create a simple report that would give me all reservations yesterday.
So for example
Select playerid, datereservationmade
from dbo.lms
normally there is very simple and I would just do
Select playerid, datereservationmade
from dbo.lms
where datereservationmade >= dateadd(day,datediff(day,1,GETDATE()),0)
AND datereservationmade < dateadd(day,datediff(day,0,GETDATE()),0)
That does not work in this case because the datereservationmade field is a decimal and if its a month 1-9 it leaves off the 0 and makes it a 5 digit decimal then if its 10-12 it is a 6 digit decimal.
Someone please help me figure out how to convert this!
If at all possible, you really should fix your schema so that dates are actually being stored as dates.
If you need to work with the decimal data type, you can use something like the following...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
BEGIN DROP TABLE #TestData; END;
CREATE TABLE #TestData (
decimal_date DECIMAL(6, 0) NOT NULL
);
INSERT #TestData (decimal_date) VALUES (101118), (90118), (101718);
--==============================================
SELECT
td.decimal_date,
dd.date_date
FROM
#TestData td
CROSS APPLY ( VALUES (RIGHT('0' + CONVERT(VARCHAR(6), td.decimal_date), 6)) ) cd (char_date)
CROSS APPLY ( VALUES (CONVERT(DATE, STUFF(STUFF(cd.char_date, 5, 0, '/'), 3, 0, '/'), 1)) ) dd (date_date)
WHERE
dd.date_date = CONVERT(DATE, DATEADD(DAY, -1, GETDATE()));
Convert the decimal to varchar(6) by adding a zero in front and getting the RIGHT 6 characters.
Then convert the string to a date from its parts, which are substrings in your varchar(6). This is made easier in SQL Server 2012 with the DATEFROMPARTS function.
Using the DATEFROMPARTS, as Tab Alleman suggested, you might get something like this:
-- Example of the arithmetic
SELECT 101118 / 10000 AS Month, (101118 % 10000) / 100 AS Day, (101118 % 100) AS Year
-- Using the math in DATEFROMPARTS
SELECT DATEFROMPARTS((101118 % 100) + 2000, 101118 / 10000, (101118 % 10000) / 100 )
However, I'm skeptical that you've provided all the correct information. What happens on January first? Your decimal value won't start with zero (as you stated). Will your day always pad with zero? If not, then 1119 won't produce the same result as 10119. If, however, your day does start with zero, then the equation above should work fine.

Is there a way to persist dateparts Week and DayOfWeek in computed column?

To speed and simplify aggregation of data queries for a dashboard, I have added these additional date columns as computed columns derived from a date field when the record is inserted.
These 3 are created from (CONVERT([varchar],[DateCreated], (112))) or a substring of such. Persisting works. No problem.
YearUTC
YearMonthUTC
YearMonthDayUTC
However, with these 2, I receive the error
Cannot be persisted because the column is non-deterministic.
WEEK(DATEPART(WEEK, [DateCreated]))
DAYOFWEEK(DATEPART(WEEKDAY, [DateCreated]))
If cannot persist, I'm afraid the cost would be too great with large table (500K rows). Is there a work-around?
EIDT: Sorry, I was not using GetDate() in my real code. I should have changed that to a standard field, [DateCreated]. I updated my code above. Even with a standard date field that is known when the record is created, I get the same error.
Ok, I found a solution to the Week datepart. Use (datepart, ISO_WEEK, [datefield]) instead of (datepart, week, [datefield]) and you can persist the value. One more reason to hate on the nuances of time however, “The numbering systems of different countries/regions might not comply with the ISO standard.”
https://learn.microsoft.com/en-us/sql/t-sql/functions/datepart-transact-sql?view=sql-server-2017
The solution I went with for the DayOfWeek was to create an after update trigger as suggested by #Jeroen_Mostert
Now that you have the week part sorted, you can also create a (much simpler than it would be for the week) function for the weekday bit:
-- Monday is always 1 and so on
create function WeekdayAbsolute(#d datetime)
returns int
with schemabinding, returns null on null input
as
begin
declare #b int;
-- The zero date (i.e. cast(0 as datetime)) was Monday. Therefore...
set #b = 1 + ((datediff(d, #d - #d, #d)) % 7);
if #b > 7 set #b = #b - 7;
return #b;
end;
and use it in the column expression.

SQL date format from float (without a '1')

I have been given some data where there is a 'float' column that contains a date with a 1 in the front of it. I.e the value 1171030 represents 30th October 2017.
I am stuck in attempting to convert it to a normal date, when attempting to remove the one a dot appears (i.e. .171030).
Would really appreciate some guidance.
Many thanks
Yuck! Let's try datefromparts():
select datefromparts((val - 100000) / 10000, floor(val / 100) % 100, val % 100)
Or, here is another approach:
select convert(date, right(convert(varchar(255), cast(val as int)), 6))
SQL Server is actually pretty flexible in the date formats it understands.
I'm assuming this was probably produced by a non-Y2K compliant method such as JavaScript getYear() which simply returns current year minus 1900. It's not clear to me whether this data is coming from a flat file or it is already imported into a database table.
If you treat this value as a character string:
case length(val)
when 7 then cast('20' + right(val, 6) as date)
when 6 then cast('19' + val as date)
end
If you truly have a float (or int) value:
cast(cast(cast(19000000 + #val as int) as char(8)) as date)
In this case you'll need to convert through a character value rather than going straight to date. Be careful with string conversions from float to char. If you prefer shorter then you may be able to get away with this one:
cast(str(19000000 + #val, 8) as date);
You might get errors by assuming a length of 8 but that's probably a good way to catch and prevent problems.