Converting text to Datetime in specific format - sql-server

I need an expression which will convert a text (VARCHAR) column to a DATETIME if, and only if, it matches dd/MM/yyyy, d/MM/yyyy, dd/M/yyyy or d/M/yyyy. If it doesn't match then I want a NULL.
I have this...
CASE ISDATE([DateField])
WHEN 1 THEN CONVERT(DATETIME,[DateField],103)
ELSE NULL
END
However this fails for '15/04/76' for example - with a "Conversion failed when converting datetime from character string" error - whereas I would want it to return NULL
Example output
'1/6/1976' -> 1976-06-01
'01/06/1976' -> 1976-06-01
'13/06/2001' -> 2001-06-13
'06/13/2001' -> NULL
'13/06/76' -> NULL
Is there a way of forcing ISDATE to validate a given format?
The documentation seems to suggest so...
ISDATE is deterministic only if used with the CONVERT function, the
CONVERT style parameter is specified and style is not equal to 0, 100,
9, or 109.
But ISDATE only takes one argument, so how do I "use it with CONVERT function" if I am not doing so already?

You could do a nested case statement here. The first could check to see if you have a 10 character string 2 for day, 2 for month, 4 for year and 2 for separators = 10 characters.
SET DATEFORMAT DMY;
Case When DateField Like '%/%/[0-9][0-9][0-9][0-9]'
Then Case When IsDate(DateField) = 1
Then CONVERT(DATETIME,[DateField],103)
End
End
Revised: I changed the code to use a like search which forces there to be a /YYYY at the end of the string, and then does an IsDate check to allow for a single day and/or month.

Well, first off, why on earth are you storing datetime values in a varchar column? This is a cardinal sin for a variety of reasons, not the least of which is that you get no validation whatsoever that the data is (or is convertible to) a datetime. You should also consider validating the input, even if you leave the column as varchar, so you don't have such a wide variety of potential formats that you want to consider valid.
So here is one way, borrowing a bit from #G Mastros:
DECLARE #f TABLE(i INT, d VARCHAR(32));
INSERT #f VALUES
(1,'15/04/76'),
(2,'15/04/1976'),
(3,'1/3/1976'),
(4,'1/3/76'),
(5,'15/3/1976'),
(6,'22/22/22'),
(7,'Yesterday');
SET DATEFORMAT DMY;
SELECT i, d, d2 = CASE WHEN ISDATE(d) = 1
AND d LIKE '%/[0-9][0-9][0-9][0-9]'
THEN CONVERT(DATETIME, d, 103) END
FROM #f;
Results:
i d d2
- ---------- -----------------------
1 15/04/76 NULL
2 15/04/1976 1976-04-15 00:00:00.000
3 1/3/1976 1976-03-01 00:00:00.000
4 1/3/76 NULL
5 15/3/1976 1976-03-15 00:00:00.000
6 22/22/22 NULL
7 Yesterday NULL
PS this will be a great case for TRY_CONVERT in SQL Server 2012. It does exactly what you're asking - it tries to convert to the specified data type; if it can't, it returns NULL.

Thanks for the responses folks.
I've done it like this...
CASE ISDATE([DateField]) WHEN 1 THEN
CASE WHEN SUBSTRING([DateField],LEN([DateField])-4,1) = '/' THEN
CASE WHEN CHARINDEX('/',[DateField],LEN([DateField])-3)=0 THEN
CONVERT(datetime, [DateField] , 103)
END
END
END
which is pretty nasty business so would still appreciate something neater!
But this doesn't work either - it still errors on mm/dd/yyyy format dates!
Scrap that last comment - it does seem to work now? Probably something to do with SET DATEFORMAT

Related

Convert Excel formulae (logic) to SQL Server

I'm looking for help on converting an Excel formula to SQL Server.
=If(AND(N3="A", R3>O3),
R3,If(AND(N3="P",S3>O3),S3,If(N3="D","",If(OR(Q3="P",Q3="A")*AND(P3>TODAY(),P3>O3),P3,O3))))
SQL formula I tried ....Colum N & Q consists of varchar and other fields are datetime in SQL Server. In below SQL statement, I have replaced and (BOLD) with OR condition. When I use "AND"(bold) am getting right data in few cases if I use (OR), am getting right data in few other cases. Here is database structure with insert statements.
https://www.db-fiddle.com/f/iHYxufV2NuyXwHeBM832NS/4
create table dbo.test (id int, N varchar(10), O datetime, P datetime, Q varchar(10), R datetime, S datetime)
select case when N='A' and R>O THEN R
when N='P' and S>O then S
when N='D' then ''
when (Q='P' or Q='Á') **and** p>getdate() and P>O then P else O end data
from test
output for above fiddler =
id-data
1-2020-11-20 00:00:00
2-2021-02-15 00:00:00
3-2021-04-11 00:00:00
4-2021-04-16 00:00:00
5-2021-04-30 00:00:00
The problem is * is a multiplication operator, but both sides of the expression are boolean rather than numeric. I think what's going on is Excel is converting the boolean true/false values to 1 and 0 for the multiplication operation.
If this is correct, then AND is the correct operator and almost everything else in the translation is correct.
There is one other mistake. when N='D' then '' is wrong, because the other result values all appear to be DateTime columns. You can't mix strings and dates. Instead, you need when N='D' then NULL.
CASE WHEN N = 'A' AND R > O THEN R
WHEN N = 'P' AND S > O THEN S
WHEN N = 'D' THEN NULL
WHEN Q IN ('P', 'A') AND P > current_timestamp AND P > O THEN P
ELSE O END
If you really need an empty string, you can convert the result and coalesce to empty string at a different level, but don't do it inside the CASE expression.
It's also worth noting the DateTime/String mismatch could entirely explain the strange results. If you have a sample somewhere for testing with the columns represented as Varchar values instead of Date or DateTime, then the comparisons could be wrong, throwing off the results. For example, O comes before S in the third row of sample data if they are compared as strings instead of dates.
If this is your verbatim code, you have an accented A in this line:
when (Q='P' or Q='Á') and p>getdate() and P>O then P else O end data
A and Á are not equivalent, so that may be short circuiting your OR and failing to return values for any Q = 'A' values that aren't handled further up in the logic.
Other than that your logic looks equivalent. The use of OR(...)*AND(...) is odd but does produce a 1/0 value, and your conversion into SQL has the correct boolean operators to match that logic.

Error converting data type varchar to numeric when converting substring to decimal

I have a table that includes a VARCHAR column populated with values like
Employee is not entitled to SSP because their average weekly earnings are below LEL.Average Earnings: £44.13750
I am trying to extract the value after the £ sign so I have tried:
SELECT
ClientId
, Max(PayPeriod) As PeriodNo
, CASE WHEN MAX(Comment) like '%£%' THEN
CAST(MAX(right(Comment, charindex('£', reverse(Comment))-1) )AS DECIMAL(5,2))
ELSE 0 END AS AvgEarnings2
FROM
t_PayrollSSPEmployeeComment
WHERE
comment like '%LEL%' AND comment like '%£%'
GROUP BY
ClientID
I only want to retrieve the value so I can cast it to decimal like 110.25
But I am getting an error
Error converting data type varchar to numeric.
I can get the value with or without the £ sign, but I need to cast it to the right number of decimal places for a report.
What am I doing wrong here?
Can you try this, SUBSTRING function can be used to extract number after character £.
SELECT
ClientId
,Max(PayPeriod) As PeriodNo
,CASE WHEN MAX(Comment) like '%£%' THEN
CAST(SUBSTRING(MAX(Comment),2,LEN(MAX(Comment))) AS DECIMAL(18,2))
ELSE 0 END AS AvgEarnings2
FROM t_PayrollSSPEmployeeComment
WHERE comment like '%LEL%' AND comment like '%£%'
GROUP BY ClientID
If you are using SQL Server 2012 or higher, use Try_Convert function:
Try_Convert (Decimal(5, 2), MAX(right(Comment, charindex('£', reverse(Comment))-1) ))
and see, wherever your error code does not convert the expression to decimal type, instead of error, the result will be null and check the problem with that value.

SQL Server: substring leading zero counting is off

I'm trying to make 3 separate fields from a date and now I ran into this problem. When the (DDMMYYYY European style) date is like 04032017, the code:
SELECT SUBSTRING(CAST(04032017 AS VARCHAR(38)), 2, 2) AS Month
returns 03 (perfect).
But when the code is like (the first zero is now a 1!) :
SELECT SUBSTRING(CAST(18022017 AS VARCHAR(38)), 2, 2) AS Month
the result is 80 because SUBSTRING now is counting from the (1) first position and in the first example it took 4 as the starting point.
Obviously I need to have 1 code for all occurrences but I just don't get it right.
Some help would be appreciated!
Regards, J
This should work for you:
select SUBSTRING(right('00000000' + CAST(04032017 AS varchar(38)),8), 3, 2) as Month
You might require to parse this as varchar and then convert into valid date and use month() function to get clear approach
declare #date nvarchar(10)='18022017'
select Month(cast(CONCAT(SUBSTRING(#date,3,2),'/',SUBSTRING(#date,1,2),'/',SUBSTRING(#date,5,4)) as date))
Ok, just solved it. Just remove the cast (why did I use that in the first place?) and put single quotes around the date:
select SUBSTRING('04032017'), 2, 2) as Month
This works just fine!

Calculated column in DAX to show current BusinessArea

I have a table in my SSAS-model with SCD-type2 functionality.
CustNr StartDate EndDate BusinessArea
123 2014-12-01 2015-01-01 Norway
123 2015-01-01 - Sweden
I need a calc-column(DAX) which shows the current BusinessArea(based on customer number). How do i approach it? I've heard about the "Earlier" function but i am new to DAX and cannot get my head around it.
The desired output would be like this:
CustNr StartDate EndDate BusinessArea CurrentBA
123 2014-12-01 2015-01-01 Norway Sweden
123 2015-01-01 - Sweden Sweden
All answers are welcome! Cheers!
LOOKUPVALUE()
(edit: note original left for continuity - correct measure below in edit section)
CurrentBusinessArea =
LOOKUPVALUE(
DimCustomer[BusinessArea] // Lookup column - will return value
// matching search criteria below.
,DimCustomer[CustNr] // Search column 1.
,DimCustomer[CustNr] // Value to match to search column 1 -
// this is evaluated in row context.
,DimCustomer[EndDate] // Search column 2.
,"-" // Literal value to match for search
// column 2.
)
I doubt that your [EndDate] is actually a text field, so I also doubt that the literal value for [EndDate] for the row that represents the current business area is actually "-". If it's blank, use the BLANK() function rather than a literal "-".
Edit based on comment, corrected measure definition with some discussion:
CurrentBusinessArea =
LOOKUPVALUE(
DimCustomer[BusinessArea]
,DimCustomer[CustNr]
,DimCustomer[CustNr]
,DimCustomer[EndDate]
,DATE(BLANK(),BLANK(),BLANK())
)
Normally in DAX you can test directly for equality to BLANK(). It tends not to act similarly to NULL in SQL. In fact, you can create a column to test this. If you do any of these, they return true for the row with a blank [EndDate]:
=DimCustomer[EndDate] = BLANK()
=ISBLANK(DimCustomer[EndDate])
=DimCustomer[EndDate] = 0 //implicit conversion 0 = blank
For some reason there is an issue in the conversion from Date/Time to BLANK(). The construction above, using the DATE() function fed with all BLANK()s works for me. I had assumed that LOOKUPVALUE() would work with a literal blank (fun fact, if data type is Integer, LOOKUPVALUE() works with a BLANK()). Apologies on that.

Overcoming SQL's lack of short-circuit of AND conditions

I'm processing an audit log, and want to ignore entries where a NULL value is changed to zero (or remains NULL). The Old and New values are held in NVARCHAR fields regardless of the type of the fields being logged. In order to CAST a new value to decimal, to determine if it's zero, I need to restrict to cases where ISNUMERIC of the field returns 1.
I've got it to work with this strange bit of SQL - but feel sure there must be a better way.
WHERE MessageDesc LIKE 'poitem%'
AND NOT(OldValue IS NULL AND 0.0 =
CASE
WHEN ISNUMERIC(NewValue) = 1 THEN CAST(NewValue AS DECIMAL(18,4))
WHEN NewValue IS NULL THEN 0.0
ELSE 1.0
END)
Any suggestions?
SQL Server 2012 added a Try_Convert function, which returns NULL if the value cannot be casted as the given type. http://technet.microsoft.com/en-us/library/hh230993.aspx
WHERE NOT (OldValue is Null AND
(NewValue is null OR try_convert(float, NewValue) = 0.0)
)
If using a version prior to 2012, check out Damien_The_Unbeliever's answer here: Check if a varchar is a number (TSQL) ...based on Aaron's comment this will not work in all cases.
Since you are using SQL 2008, then it appears a combination of isnumeric and a modified version of Damien's answer from the link above will work. Your current solution in your question would have problems with values like '.', '-', currency symbols ($, etc.), and scientific notation like '1e4'.
Try this for SQL 2008 (here is SQLFiddle with test cases: http://sqlfiddle.com/#!3/fc838/3 ): Note: this solution will not convert text values to numeric if the text has commas (ex: 1,000) or accounting notation with parens (ex: using "(1)" to represent "-1"), because SQL Server will throw an error when trying to cast to decimal.
WHERE t.OldValue is null
AND
(
t.NewValue is null
OR
0.0 =
case
when isnumeric(t.NewValue) = 1
--Contains only characters that are numeric, negative sign, or decimal place.
--This is b/c isnumeric will return true for currency symbols, scientific notation, or '.'
and not (t.NewValue like '%[^0-9.\-\+]%' escape '\')
--Not other single char values where isnumeric returns true.
and t.NewValue not in ( '.', '-', '+')
then cast(t.NewValue as decimal(18,4))
else null --can't convert to a decimal type
end
)
Avoid ISNUMERIC() since it is problematic with '.'.
-- Dot comes up as valid numeric
select
ISNUMERIC('.') as test1,
ISNUMERIC('1A') as test2,
ISNUMERIC('1') as test3,
ISNUMERIC('A1') as test4
-- Results window (text)
test1 test2 test3 test4
----------- ----------- ----------- -----------
1 0 1 0
Use COALESCE() instead.
WHERE MessageDesc LIKE 'poitem%'
AND
NOT (OldValue IS NULL AND
CAST(COALESCE(NewValue, '0') AS DECIMAL(18,4)) = 0)

Resources