Compare two datetime fields in the select statement - TSQL - sql

I have this sql select query:
declare #godina as integer = 2020
select
vtRokSi.KodSI,
vtRokSi.rKod,
vtRokSi.rDatum,
vtRokSi.rDatumZavr,
CONVERT(datetime, vtRokSi.rDatum) as docni
-- if CONVERT(datetime, vtRokSi.rDatum) > vtRokSi.rDatumZavr
-- select 'docni' as docni
-- else if CONVERT(datetime, vtRokSi.rDatum) <= vtRokSi.rDatumZavr
-- select 'ne docni' as docni
-- else if vtRokSi.rDatumZavr
-- select 'nema vneseno'
from GP.dbo.VT_ROK_SI as vtRokSi
where vtRokSi.GodGP = #godina
where you can see, that I am trying to compare two columns.
vtRokSi.rDatum is of type date and vtRokSi.rDatumZavr is of type datetime. I am converting vtRokSi.rDatum to datetime.
I want to print several values in the column 'docni', for example,
the string 'nema vneseno' if vtRokSi.rDatumZavr is null,
'docni' if vtRokSi.rDatum is bigger then vtRokSi.rDatumZavr,
'ne docni' if vtRokSi.rDatum is smaller or equal to vtRokSi.rDatumZavr.
How can I do that in the same select statement?
Or will I need something more powerful, i.e. separate the command into several other?

You can use a CASE expression.
...
CASE
WHEN vtroksi.rdatum > vtroksi.rdatumzavr THEN
'docni'
WHEN vtroksi.rdatum <= vtroksi.rdatumzavr THEN
'ne docni'
ELSE
'nema vneseno'
END docni
...

Related

Cast to datetime only if its a date value

I have a table with DateFrom column
it's an nvarchar col in the col I have data like that
01
02
10/10/2020
04
some strings and some DateTime values
I need to cast it to DateTime but only if it's a date if not then pull out the value like it is can is this possible?
thanks ...
a Simple Try cast should do the job here
DECLARE #Table TABLE (Val NVARCHAR(20))
INSERT INTO #Table
VALUES('1'),('2'),('10/10/2020'),('04')
SELECT
*,
TRY_CAST(Val AS DATE)
FROM #Table
Results
Something like this should work. It uses a CASE statement to check whether or not the value is a valid date and if so casts it to DATETIME, then converts it to VARCHAR (so dates and other values can be returned as the same column).
You can find other datetime to string styles here if you need them formatted differently:
https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15
DECLARE #TempTable TABLE (Dt NVARCHAR(30))
INSERT INTO #TempTable
VALUES('1'),('2'),('10/10/2020'),('04'),('Jun 27 2021 12:22AM'),('Oct 12 2021 8:31PM')
SELECT
*,
CASE WHEN ISDATE(Dt) = 1 THEN Convert(VARCHAR, CAST(Dt AS DATETIME), 120) ELSE Dt END
FROM #TempTable
You can easily check this with the case statement.
Postgres example
select case when '12/12/2021' ~ '\d{2}\/\d{2}\/\d{4}' then 'this is the date' ELSE 'this is a string' end;
->this is the date
select case when 'some text' ~ '\d{2}\/\d{2}\/\d{4}' then 'this is the date' ELSE 'this is a string' end;
->this is a string
so you can use it like this
select case when fild_01 ~ '\d{2}\/\d{2}\/\d{4}' then TO_DATE(fild_01,'DD/MM/YYYY')::text ELSE fild_01 end from test_table;
as you can see, case construction can't generalize two different types of data, so I had to convert them to the same type of text
Use the TRY_CONVERT function, with style 107
SELECT TRY_CONVERT(datetime, 'Jun 27 2021 12:22AM', 107)
Result
2021-06-27 00:22:00.000
This returns a null if the value is not a date
You can choose according to your needs.
When you want to set default value:
SELECT IIF(ISDATE(Val) = 1 , Val,'YOUR_DEFAULT_VALUE')
Whent default value not matter (It will be NULL):
SELECT TRY_CAST(NULL AS DATE)

CONVERT Date INT to DATE in 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'))

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:

Invalid Column Name When Setting New Variable Equal to Expression

I need to calculate a percentage based off of two columns in my query and store that number in a new variable. When I set the variable equal to the expression I get an "Invalid column name" error.
SELECT
Count(Case When HMC_Place_Position is null Then 0 end) as Parts,
COUNT(*) AS Total_Parts, /*Total Parts */
COUNT(CASE
WHEN Outfeed_Place_Time IS NOT NULL THEN 1
END) AS Total_Good_Parts /*Total Good Parts */
FROM PartData_GKN05_C
WHERE Infeed_Pick_Time >= DATEADD(day,-7, GETDATE())
ALTER TABLE PartData_GKN05_C Add Total_Good_Parts int
DECLARE #Total_Good_Percent AS float = ((Total_Good_Parts / Total_Parts)*100)
I believe you are looking to get the percentage of "good" parts from the total count of part recordss in PartData_GKN05_C.
So using your definition of " good parts " (Outfeed_Place_Time IS NOT NULL), the query below counts those and then divides by the count of all the part records in the table.
Since "count()" returns an integer value, we have to cast one of the "count()" as float BEFORE we divide them so that SQL does not return a zero (Since an "int / int" does not return decimals while a float does).
We only have to convert one of the "Count()" because SQL will implicitly convert the other one but feel free to convert both to float explicitly if you'd like.
Here is the code :
DECLARE
#Total_Good_Percent float
SELECT
#Total_Good_Percent = (
COUNT(
CASE
WHEN Outfeed_Place_Time IS NOT NULL
THEN 1
END
)
/convert(float,count(*))
)*100
FROM
PartData_GKN05_C
WHERE
Infeed_Pick_Time >= DATEADD(day, -7, GETDATE());
select
#Total_Good_Percent
try this code:
DECLARE #Total_Good_Parts INT, #Total_Parts INT;
SELECT
--Count(Case When HMC_Place_Position is null Then 0 end) as Parts,
#Total_Parts = COUNT(*),
#Total_Good_Parts = COUNT(CASE
WHEN Outfeed_Place_Time IS NOT NULL
THEN 1
END)
FROM PartData_GKN05_C
WHERE Infeed_Pick_Time >= DATEADD(day, -7, GETDATE());
ALTER TABLE PartData_GKN05_C
ADD Total_Good_Parts INT;
DECLARE #Total_Good_Percent AS FLOAT= ((#Total_Good_Parts / #Total_Parts) * 100.00);

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?