CONVERT Date INT to DATE in SQL - sql

How can I convert a date integer to a date type? (20200531 into 5/31/2020)
My current table has a datadate formatted as YYYYMMDD (20200531, 20200430, etc.)
The Datatype for the datadate is an int according the Toad Data Point software I'm using. I believe it's using ORACLE sql database.
As a result, when querying this data, I have to type in the where clause as below..
where datadate = '20200531'
My goal is to convert this integer datadate into a date format (5/31/2020) so I can apply the datadate to the where clause.
like..
WHERE datadate = dateadd(DD, -1, CAST(getdate() as date))

(Read below for my answer for if it's an int column)
Assuming it's a textual string:
Assuming that datadate is a string (character, text, etc) column and not a date/datetime/datetime2/datetimeoffset column, then use the CONVERT function with style: 23. The 23 value corresponds to ISO 8601 because the values are in yyyy-MM-dd-order, even though they're missing dashes.
This page has a reference of style numbers: https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15
SELECT
*
FROM
(
SELECT
myTable.*
CONVERT( date, datadate, 23 ) AS valueAsDate
FROM
myTable
) AS q
WHERE
q.valueAsDate = DATEADD( dd, -1, GETDATE() )
Assuming it's an actual int column:
The quick-and-dirty way is to convert the int to varchar and then use the same code as above as if it were a textual field - but don't do this because it's slow:
SELECT
*
FROM
(
SELECT
myTable.*,
CONVERT( char(8), datadate ) AS valueAsChar,
CONVERT( date, CONVERT( char(8), datadate ), 23 ) AS valueAsDate
FROM
myTable
) AS q
WHERE
q.valueAsDate = DATEADD( dd, -1, GETDATE() )
Assuming it's an actual int column (better answer):
We'll need to use DATEFROMPARTS and extract each component using Base-10 arithmetic (fun)!
If we have an integer representing a formatted date (the horror) such as 20200531 then:
We can get the day by performing MOD 31 (e.g. 19950707 MOD 31 == 7)
We can get the month by first dividing by 100 to remove the day part, and then MOD 12: (e.g. 20200531 / 100 == 202005, 202005 MOD 12 == 5)
We can get the year by dividing by 10,000, (e.g. 20200531 / 10000 == 2020).
Btw:
SQL Server uses % for the Modulo operator instead of MOD.
Integer division causes truncation rather than producing decimal or floating-point values (e.g. 5 / 2 == 2 and not 2.5).
Like so:
SELECT
q2.*
FROM
(
SELECT
q.*,
DATEFROMPARTS( q.[Year], q.MonthOfYear, q.DayOfMonth ) AS valueAsDate
FROM
(
SELECT
myTable.*,
( datadate % 31 ) AS DayOfMonth,
( ( datadate / 100 ) % 12 ) AS MonthOfYear,
( datadate / 10000 ) AS [Year]
FROM
myTable
) AS q
) AS q2
WHERE
q2.valueAsDate = DATEADD( dd, -1, GETDATE() )
Obviously, having two nested subqueries is a pain to work with (SQL has terrible ergonomics, I don't understand how or why SQL doesn't allow expressions in a SELECT clause to be used by other expressions in the same query - it's really bad ergonomics...) - but we can convert this to a scalar UDF (and SQL Server will inline scalar UDFs so there's no performance impact).
This function has a TRY/CATCH block in it because of the possibility that you process an invalid value like 20209900 (which isn't a real date as there isn't a 99th month with a 0th day in 2020). In this event the function returns NULL.
CREATE FUNCTION dbo.convertHorribleIntegerDate( #value int ) RETURNS date AS
BEGIN
DECLARE #dayOfMonth int = #value % 31;
DECLARE #monthOfYear int = ( #value / 100 ) % 100;
DECLARE #year int = #value / 10000;
BEGIN TRY
RETURN DATEFROMPARTS( #dayOfMonth, #monthOfYear, #year );
END TRY;
BEGIN CATCH
RETURN NULL;
END CATCH;
END
Which we can use in a query like so:
SELECT
myTable.*,
dbo.convertHorribleIntegerDate( datadate ) AS valueAsDate
FROM
myTable
As SELECT cannot share expression results with other expressions in the same query, you'll still need to use an outer query to work with valueAsDate (or repeat the dbo.convertHorribleIntegerDate function call):
SELECT
*
FROM
(
SELECT
myTable.*,
dbo.convertHorribleIntegerDate( datadate ) AS valueAsDate
FROM
myTable
) AS q
WHERE
q.valueAsDate = DATEADD( dd, -1, GETDATE() )

This answers assumes that you are running Oracle, as suggested in your question.
How can I convert a date integer to a date type? (20200531 into 5/31/2020)
In Oracle, you use to_date() to convert a string to a number. If you are giving it a number, it implicitly converts it to a string before converting it. So in both cases, you would do:
to_date(datadate, 'yyyymmdd')
My goal is to convert this integer datadate into a date format (5/31/2020) so I can apply the datadate to the where clause.
Generally, you want to avoid applying a function on a column in a where predicate: it is not efficient, because the database needs to apply the function on the entire column before it is able to filter. If you want to filter on dateadd as of yesterday, then I would recommend computing yesterday's date and putting it in the same format as the column that is filtered, so you can do a direct match against the existing column values.
If your column is a string:
where datadatea = to_char(sysdate - 1, 'yyyymmdd')
If it's a number:
where datadatea = to_number(to_char(sysdate - 1, 'yyyymmdd'))

Related

How to calculate average in date column

I don't know how to calculate the average age of a column of type date in SQL Server.
You can use datediff() and aggregation. Assuming that your date column is called dt in table mytable, and that you want the average age in years over the whole table, then you would do:
select avg(datediff(year, dt, getdate())) avg_age
from mytable
You can change the first argument to datediff() (which is called the date part), to any other supported value depending on what you actually mean by age; for example datediff(day, dt, getdate()) gives you the difference in days.
First, lets calculate the age in years correctly. See the comments in the code with the understanding that DATEDIFF does NOT calculate age. It only calculates the number of temporal boundaries that it crosses.
--===== Local obviously named variables defined and assigned
DECLARE #StartDT DATETIME = '2019-12-31 23:59:59.997'
,#EndDT DATETIME = '2020-01-01 00:00:00.000'
;
--===== Show the difference in milliseconds between the two date/times
-- Because of the rounding that DATETIME does on 3.3ms resolution, this will return 4ms,
-- which certainly does NOT depict an age of 1 year.
SELECT DATEDIFF(ms,#StartDT,#EndDT)
;
--===== This solution will mistakenly return an age of 1 year for the dates given,
-- which are only about 4ms apart according the SELECT above.
SELECT IncorrectAgeInYears = DATEDIFF(YEAR, #StartDT, #EndDT)
;
--===== This calulates the age in years correctly in T-SQL.
-- If the anniversary data has not yet occurred, 1 year is substracted.
SELECT CorrectAgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Now, lets turn that correct calculation into a Table Valued Function that returns a single scalar value producing a really high speed "Inline Scalar Function".
CREATE FUNCTION [dbo].[AgeInYears]
(
#StartDT DATETIME, --Date of birth or date of manufacture or start date.
#EndDT DATETIME --Usually, GETDATE() or CURRENT_TIMESTAMP but
--can be any date source like a column that has an end date.
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT AgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Then, to Dale's point, let's create a test table and populate it. This one is a little overkill for this problem but it's also useful for a lot of different examples. Don't let the million rows scare you... this runs in just over 2 seconds on my laptop including the Clustered Index creation.
--===== Create and populate a large test table on-the-fly.
-- "SomeInt" has a range of 1 to 50,000 numbers
-- "SomeLetters2" has a range of "AA" to "ZZ"
-- "SomeDecimal has a range of 10.00 to 100.00 numbers
-- "SomeDate" has a range of >=01/01/2000 & <01/01/2020 whole dates
-- "SomeDateTime" has a range of >=01/01/2000 & <01/01/2020 Date/Times
-- "SomeRand" contains the value of RAND just to show it can be done without a loop.
-- "SomeHex9" contains 9 hex digits from NEWID()
-- "SomeFluff" is a fixed width CHAR column just to give the table a little bulk.
SELECT TOP 1000000
SomeInt = ABS(CHECKSUM(NEWID())%50000) + 1
,SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
+ CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
,SomeDecimal = CAST(RAND(CHECKSUM(NEWID())) * 90 + 10 AS DECIMAL(9,2))
,SomeDate = DATEADD(dd, ABS(CHECKSUM(NEWID())%DATEDIFF(dd,'2000','2020')), '2000')
,SomeDateTime = DATEADD(dd, DATEDIFF(dd,0,'2000'), RAND(CHECKSUM(NEWID())) * DATEDIFF(dd,'2000','2020'))
,SomeRand = RAND(CHECKSUM(NEWID())) --CHECKSUM produces an INT and is MUCH faster than conversion to VARBINARY.
,SomeHex9 = RIGHT(NEWID(),9)
,SomeFluff = CONVERT(CHAR(170),'170 CHARACTERS RESERVED') --Just to add a little bulk to the table.
INTO dbo.JBMTest
FROM sys.all_columns ac1 --Cross Join forms up to a 16 million rows
CROSS JOIN sys.all_columns ac2 --Pseudo Cursor
;
GO
--===== Add a non-unique Clustered Index to SomeDateTime for this demo.
CREATE CLUSTERED INDEX IXC_Test ON dbo.JBMTest (SomeDateTime ASC)
;
Now, lets find the average age of those million represented by the SomeDateTime column.
SELECT AvgAgeInYears = AVG(age.AgeInYears )
,RowsCounted = COUNT(*)
FROM dbo.JBMTest tst
CROSS APPLY dbo.AgeInYears(SomeDateTime,GETDATE()) age
;
Results:

How to convert round number to data and time format

Two Column in table tblpress
Date Time
20160307 120949
20160307 133427
Need to be select below the format:
07-03-2016 12:09:49
07-03-2016 13:34 27
or
03-March-2016 12:09: 49 PM
03-March-2016 01:34: 27 PM
You can try below
select format(cast([Date] as date),'dd-MMMM-yyyy') as [Date],
TIMEFROMPARTS(LEFT([Time],2), SUBSTRING([Time],3,2), RIGHT([Time],2), 0,0) as [Time]
I think CAST/CONVERT will help you:
SELECT
CAST('20160307' AS date),
CAST(STUFF(STUFF('120949',3,0,':'),6,0,':') AS time)
And convert for out:
SELECT
CONVERT(varchar(20),NormalDate,105) OutDate, -- Italian style
CONVERT(varchar(20),NormalTime,108) OutTime -- hh:mi:ss
FROM
(
SELECT
CAST([Date] AS date) NormalDate,
CAST(STUFF(STUFF([Time],3,0,':'),6,0,':') AS time) NormalTime
FROM YourTable
) q
CAST and CONVERT (Transact-SQL)
And you can use FORMAT (Transact-SQL)
SELECT
FORMAT(GETDATE(),'dd-MM-yyyy'),
FORMAT(GETDATE(),'HH:mm:ss')
Best way to do it is to create a function :
create FUNCTION [dbo].[udfGetDateTimeFromInteger]
(
#intDate int,
#intTime int
)
RETURNS datetime
AS BEGIN
-- Declare the return variable here
DECLARE #DT_datetime datetime = NULL,
#str_date varchar(11),
#str_time varchar(8)
if(#intDate is not null and #intDate > 0)
begin
select #str_date = CAST( cast(#intDate as varchar(8)) AS date)
if #intTime=0
select #str_time ='000000'
else
select #str_time = right('0'+CONVERT(varchar(11),#intTime),6)
select #str_time =
SUBSTRING(#str_time,1,2)+':'+SUBSTRING(#str_time,3,2)+':'+SUBSTRING(#str_time,5,2)
select #DT_datetime = CAST(#str_date+' '+#str_time as datetime)
end
-- Return the result of the function
RETURN #DT_datetime
END
and then call it in select like :
declare #next_run_date int, #next_run_time int
select #next_run_date = 20160307
select #next_run_time = 130949
SELECT #next_run_date inputdate,
#next_run_time inputtime,
dbo.udfGetDateTimeFromInteger(#next_run_date, #next_run_time) outputdatetime
Output will be like :
inputdate inputtime outputdatetime
20160307 130949 2016-03-07 13:09:49.000
You said those are numbers, right? You can use datetimefromparts (or datetime2fromparts). ie:
select
datetimefromparts(
[date]/10000,
[date]%10000/100,
[date]%100,
[time]/10000,
[time]%10000/100,
[time]%100,0)
from tblpress;
DB Fiddle demo
Note that naming fields like that and also storing date and time like that is a bad idea.
I later noticed it was char fields:
select
cast([date] as datetime) +
cast(stuff(stuff([time],5,0,':'),3,0,':') as datetime)
from tblpress;

Subtract substring days from date

I'm trying to come up with a Select statement that would return the DATE column minus the number of days in the DAYS column.
This is the query I tried:
SELECT
DATEADD(DAY, -+(REPLACE(ISNULL(DAYS,0), 'DAYS', '')), DATE)
FROM
T
It throws an error
Operand data type varchar is invalid for minus operator
dateadd(day, -1*convert(int, coalesce(replace([DAYS], ' days', ''),'0')), [DATE])
Something like
DECLARE
#Days VARCHAR(25) = '23 DAYS',
#Date DATE = '2018-10-23';
SELECT DATEADD(Day, - CAST(REPLACE(#Days , 'DAYS', '') AS INT), #Date )
If you are using SQL Server 2012+
SELECT DATEADD(Day, - ISNULL(TRY_CAST(REPLACE(#Days , 'DAYS', '') AS INT), 0), #Date )
I think you need to get the number, cast it to integer as negative and use dateadd like below:
SELECT dateadd(DAY,cast('-'+substring([DAYS], 1,charindex(' ',[DAYS])) as int),[DATE])
FROM T
You can use this:
Use CHARINDEX to get the number of days and convert to int
and then minus it from date column
select DATEADD(DAY,-CAST(SUBSTRING(days,1,CHARINDEX(' ',days)) AS INT),DATE),* from #table
The DATEADD function expects an integer for its second parameter argument.
By REPLACING 'DAYS' with an empty string, you are still left with a varchar containing a number and the blank space that was between that number and the word "DAYS".
RTRIM this result and CAST it as an Integer, and you should be good to go.
Oh, and you also need to put ,DATE inside the DATEADD()'s parenthesis.

Use part of VARCHAR column as date in where clause

I have a table where the varchar column CREATED_BY has the data in the format
USER - dd/MM/yyyy hh:mm.
I'm trying to do data migration and need to get records where the created date is greater than a certain date, but the format of the column makes this difficult, so
SELECT * FROM TABLE_NAME WHERE -- last part of CREATED_BY > SOMEDATE
Well, since the dd/MM/yyyy hh:mm format has a fixed length, you can use the RIGHT function to extract the data part from your string:
SELECT *
FROM TABLE_NAME
WHERE CONVERT(datetime, RIGHT(CREATED_BY, 16), 103) > somedate
You need to extract the date from the string:
WHERE cast(reverse ( substring ( reverse ( #string ) , 1 , 16 ) ) as datetime) > somedate

Converting YYYYMM format to YYYY-MM-DD in SQL Server

I need to perform a query on a large table that has a datetime column that is indexed.
We need to query the data for a range from a month (at a minimum) to multiple months.
This query would be executed from Cognos TM1 and the input would be a period like YYYYMM. My question is - how to convert the YYYYMM input to a format that can be used to query that table (with the index being used).
Let's say if the input is
From Date: '201312'
To Date: '201312'
then, we need convert the same to 'between 01-12-2013 and 31-12-2013' in the query
Since we need this to be hooked up in Cognos TM1, so would not be able to write a procedure or declare variables (TM1 somehow does not like it).
Thanks in advance for your reply.
I would do something like this:
create procedure dbo.getDataForMonth
#yyyymm char(6) = null
as
--
-- use the current year/month if the year or month is invalid was omitted
--
set #yyyymm = case coalesce(#yyyymm,'')
when '' then convert(char(6),current_timestamp,112)
else #yyyymm
end
--
-- this should throw an exception if the date is invalid
--
declare #dtFrom date = convert(date,#yyyymm+'01') -- 1st of specified month
declare #dtThru date = dateadd(month,1,#dtFrom) -- 1st of next month
--
-- your Big Ugly Query Here
--
select *
from dbo.some_table t
where t.date_of_record >= #dtFrom
and t.date_of_record < #dtThru
--
-- That's about all there is to it.
--
return 0
go
Suppose you are getting this value of YYYYMM in a varchar variable #datefrom .
You can do something like
DECLARE #DateFrom VARCHAR(6) = '201201';
-- Append '01' to any passed string and it will get all
-- records starting from that month in that year
DECLARE #Date VARCHAR(8) = #DateFrom + '01'
-- in your query do something like
SELECT * FROM TableName WHERE DateTimeColumn >= #Date
Passing Datetime in a ansi-standard format i.e YYYYMMDD is a sargable expression and allows sql server to take advantage of indexes defined on that datetime column.
here is an article written by Rob Farley about SARGable functions in SQL Server.
Try this...
declare #startdate date,#endate date
select #startdate =convert(date,left('201312',4)+'-'+right('201312',2)+'-01')
select #endate= DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #startdate) + 1, 0))
select convert(date,#startdate,102) startdate,convert(date,#endate,102) endate
In the datasource of your TM1 Turbo Integrator process, you can use parameters in the SQL query. E.g. you could take this SQL query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = 'Green'
AND Col2 < 30
In TM1, to parameterise this, you would create two parameters e.g. P1 and P2 and put them in the query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = '?P1?'
AND Col2 < ?P2?