Case when only executes ELSE statement - sql-server

I have never seen this happen before, but when I SELECT a CASE WHEN statement in SQL Server, it executes the ELSE statement. How is this possible? Is it because it cannot handle different kind of value types in the same column?
SELECT CASE WHEN len(birthDate)=4 THEN birthDate
WHEN birthDate = '' THEN ''
ELSE CONVERT(datetime, birthDate)
END AS [birthDateConverted]
,[birthDate]
FROM BirthDayTable
The result looks like this:
birthDateConverted birthDate
1951-01-01 00:00:00.000 1951
1936-06-19 00:00:00.000 June 19, 1936
1948-03-11 00:00:00.000 March 11, 1948
NULL
I want to have the following:
birthDateConverted birthDate
1951 1951
1936-06-19 June 19, 1936
1948-03-11 March 11, 1948
NULL
And I also do not understand why I get NULL when I specify ''. But this part is not as important as the first part, as I would like to have only the year when only a year is specified.

Try this first and then implement the logic accordingly in your query.
Replace #birthDate variable with the input string that you desire.
There is one more check that you can perform using ISDATE() function which checks if the entered string is a valid date and you can change your logic accordingly if required.
More information about isdate() function can be found here
DECLARE #birthDate varchar(20)=''
SET #birthDate='March 11, 1948'
SELECT CASE WHEN len(#birthDate)=4 THEN CONVERT(varchar, #birthDate)
WHEN CONVERT(varchar,#birthDate) = '' THEN ''
ELSE CONVERT(varchar,CONVERT(date, #birthDate))
END AS [birthDateConverted]

NULL is not equal to '' so, you can do instead :
SELECT (CASE WHEN len(birthDate) = 4
THEN birthDate
WHEN birthDate IS NULL
THEN ''
ELSE CONVERT(datetime, birthDate)
END) AS [birthDateConverted],
[birthDate]
FROM BirthDayTable;
However, '' will implicit convert into datetime.

You are trying to store multiple types in one column which is not possible in SQL. So when the query executes on SQL Server it will type all the values on first read type.
In your case, your second conditions always fail due to comparing a null value with an empty string, in SQL you can achieve this through ISNULL(birthDate,'') = '' so when the value is null or empty it always returns true.
So you can't set an empty string or date string with DateTime value your requirement is invalid.
I think you are looking for this it will format DateTime values as string
SELECT (CASE WHEN ISDATE(birthDate) = 1
THEN CONVERT(varchar, CONVERT(datetime,birthDate), 110)
ELSE birthDate
END) AS [birthDateConverted], [birthDate]
FROM BirthDayTable;

I have tried the following, which works for my case. Hopefully someone else can benefit from it too
SELECT CASE WHEN len(birthDate)>4
THEN CONCAT(YEAR(birthDate), '-', RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(MONTH, (birthDate))),2), '-',
RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(DAY, (birthDate))),2))
WHEN len(birthDate)=4
THEN CONVERT(nvarchar(255), birthDate)
ELSE ''
END AS [birthDateConverted]
,[birthDate]
FROM BirthDayTable;

A CASE will only return 1 datatype.
In your SQL that would return a DATETIME because of that convert.
And since '1951' is then implicitly casted to a DATETIME, it returns '1951-01-01 00:00:00.000'.
So let the CASE return a VARCHAR.
If you're using MS SQL Server 2012 or higher, then you could use TRY_CONVERT to verify if the varchar can be converted to a date or datetime.
Because a TRY_CONVERT will return NULL when the conversion attempt fails.
Example snippet:
select *,
(case
when birthDate like '[0-9][0-9][0-9][0-9]' then birthDate
when birthDate like '%[0-9]%' and try_convert(date, birthDate) is not null
then convert(varchar(10), convert(date, birthDate), 20)
else ''
end) as birthDateConverted
from (values
(1,'1951'),
(2,'June 19, 1936'),
(3,'March 11, 1948'),
(4,''),
(5,null),
(6,'ABCD')
) t(id, birthDate);
Returns:
id birthDate birthDateConverted
-- -------------- ------------------
1 1951 1951
2 June 19, 1936 1936-06-19
3 March 11, 1948 1948-03-11
4
5 NULL
6 ABCD

Related

In SQL Server how to have a paymentdate greater than the month and year ignoring the day part?

In Oracle:
select
name,
case
when PaymentDate is not null
and PaymentDate <= to_date('202001','yyyyMM')
then 'Paid'
else NotPaid
where
empId in(%s)) ;
I need the SQL Server equivalent of the above query. I just need to check the month and year part of the date with paymentdate's month and year. Please advice.
Need the to_date('202001','yyyyMM') equivalent in SQL Server.
Here's how to get the beginning of the month. You can apply it to both PaymentDate and Date, then do the comparison
SELECT DATEADD(month, DATEDIFF(month, 0, PaymentDate), 0) AS StartOfMonth
Oracle always return 1st day of the month when you convert to_date. So in this case it is 1st Jan 2020.
Generally SQL server have default date format at YYYY-MM-DD. If you want to match date as per SQL server then you need to manipulate the input string to as per date format
SELECT CAST((LEFT('202001',4) + '-' + RIGHT('202001',2) + '-01') as Date)
As per your query
select
name,
case
when PaymentDate is not null
and PaymentDate <= CAST((LEFT('202001',4) + '-' + RIGHT('202001',2) + '-01') as Date)
then 'Paid'
else 'NotPaid'
where
empId in(%s)) ;
In Oracle, to_date('202001','yyyyMM') produces a date value that represents January 1st, 2020.
You can get the same result in SQL Server with an expression such as cast('2020-01-01' as date) or cast('20200101' as date).
If your input ('202001') is coming from outside the query and you cannot modify its value, you can concatenate '01' at the end: cast(concat(#myparam, '01') as date).
The rest of your query has syntax errors:
missing quotes around 'Not Paid'
missing end keyword at the end of the case expression
missing from clause
superfluous closing parenthese at the end of the query
It is also worth noting that the condition in the case expression can be simplified by removing the superfluous is not null predicate:
select
name,
case when PaymentDate <= cast('2020-01-01' as date)
then 'Paid'
else NotPaid
end
from mytable
where empId in (%s);
select * from payment_table where month(payment_date) > 2 and year(payment_date) >= 2020;

SQL server v17 : how to convert smalldatetime value to datetime

I used following query to insert the data :
CASE(isdate([Date]))
WHEN 1 THEN [date]
WHEN 0 THEN cast(substring([Date],0,5) - 2 as smalldatetime)
ELSE [date] END
Now, datetime column has date in smalldatetime format. How to convert smalldatetime formatted date to datetime format whereas column type is datetime.
Example: For Numeric date 41298 it resulted into 1911-04-21 00:00:00 but actual expected result was 2013-01-26 00:00:00.000
I think you are looking for something like this:
(CASE WHEN ISNUMERIC([Date]) = 1
THEN DATEADD(day, CAST([Date] as int), '1899-12-31')
WHEN iSDATE([Date]) = 1
THEN CAST([date] as smalldatetime)
END)
The value 41298 looks like an Excel formatted date. These start counting from the last day of 1899.
I have tried your given value and it convert to expected result please have a look;
SELECT CAST(CAST(41298 AS smalldatetime) AS DATE) AS Date
SELECT CAST(41298 AS smalldatetime) AS Date
Output:
Another option is DateAdd
Select DateAdd(DAY,41298,'1900-01-01')
Returns
2013-01-26 00:00:00.000

Check if date falls within Month and Year range

If a user selects a range such as:
Start: November 2016
End: September 2017,
I want to include all results that fall within the range of 2016-11-01 to 2017-09-30.
I tried concatenating together the year, month, and day, however the issue comes that not all months have the same last day. While I know all months start on day 01, a month's end day can be 28, 29, 30, or 31.
Is there a way to do this without constructing the date? SqlServer 2008 doesn't have the EOMONTH function, and I feel like anything more complex than that is not the right solution. I would like to avoid this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
It really seems to me that the easiest and best answer is to go from the first of the beginning month to the first of the month after the ending month, and make the second comparison not inclusive.
In other words, instead of this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
simply this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol < '2017' + '-' + '10' + '-01'
There is a faster way to do so :
DECLARE #minDate DATE
DECLARE #maxDate DATE
SET #minDate = XXXXX
SET #maxDate = YYYYY
-- Get the first day of the month minDate.
SET #minDate = CONVERT(datetime,CONVERT(varchar(6),#minDate,112)+'01',112)
-- Get the last day of the month minDate.
SET #maxDate = CONVERT(datetime,CONVERT(varchar(6),#maxDate,112)+'01',112)
SET #maxDate = DATEADD(day, -1, DATEADD(month, 1, #maxDate))
SELECT * FROM myTABLE WHERE DateCol >= #minDate AND DateCol <= #maxDate
Or :
SELECT * FROM myTABLE
WHERE DateCol >= CONVERT(datetime,CONVERT(varchar(6),XXXXX,112)+'01',112)
AND DateCol <= DATEADD(day, -1, DATEADD(month, 1, CONVERT(datetime,CONVERT(varchar(6),YYYYY,112)+'01',112)))
Use syntax like CONVERT(datetime,'20170930',112) or CONVERT(datetime,'09-30-2017',110) for XXXXX and YYYYY rather than '2017-09-30' that use SQL Server implicit convertion from char to datetime (rely on the server configuration : can be hazardous!!!)).
Using this syntax is faster because #minDate and #maxDate do not need any evaluation. So that indexes can be used directly...
Otherwise a scalar function that will simulate the eomonth() behaviour could be usefull...
You could use following select statement to get last date of any month (and any year) by passing a field or date to it:
DECLARE #dtDate DATE
SET #dtDate = '09/25/2016'
SELECT CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#dtDate)+1,0)) AS DATE) AS LastDay_AnyMonth
Please provide some example data and desired result and I will update my answer further.
Here you go:
DECLARE #YourTable TABLE (YourData DATE);
INSERT INTO #YourTable VALUES
('2016-11-01'),
('2016-09-05'),
('2017-03-03'),
('2017-11-11'),
('2017-12-14'),
('2017-09-30');
WITH CTE AS (
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2016 AND MONTH (YourData) >= 11
)
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2017 AND MONTH (YourData) <= 9
UNION ALL
SELECT YourData
FROM CTE;
There is no need to know the end of the month (28 or 30 or 31).
For 2008, you can simply convert the string
Example
Select Date1=convert(date,'November 2016')
,Date2=dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
Returns
Date1 Date2
2016-11-01 2017-09-30
So the WHERE would be somthing like this
...
Where DateCol between convert(date,'November 2016')
and dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
A useful construct for performing date-based operations is to make use of a Date Dimension table. You are creating a lookup table that is populated with a lot of information about dates over a large span of time. You can then query the table based on the information that you do have. The table is small enough so that it does not impose significant performance concerns.
In your particular case, you have the month and year. You would plug that into the date dimension table to get the first of the month from the beginning month and the last of the month from the ending month. You now have a time range to search over without any complex logic or calculations on the fly.
Aaron Bertrand explains it in depth here: https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/

SQL Date-Time Conversion with nested converts experiences random conversion errors

Looking for assistance with a strange issue if anyone has ideas:
I have a SQL that statement works most of the time in a T-SQL script but crashes occasionally. I have identified the data that a crash occurs on and cannot identify any difference between data rows that work.
The goal of this code is to add the time to an already existing datetime value that has 00:00:00 as the time from the second time column (as outlined below). My goal is to combine both columns into YYYY-MM-DD HH:MM:SS format, but I had to convert them to char first to trim off the orignal 00:00:00.
Columns
LogDate - contains date only in DateTime format (YYYY-MM-DD HH:MM:SS)
LogTime - contains the time of the action and is in varchar format (HH:MM)
SQL Conversion
SELECT CONVERT(DATETIME, CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108))
FROM TestTable
WHERE EventSerial = '100001'
However, if I change the EventSerial in the above statement to a different row, such as '100002', the statement works.
The data for each row is below:
EventSerial 100001's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:04
EventSerial 100002's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:48
Running with data set 1 fails, running with data set 2 produces output. Also, running the code without the final datetime conversion works, or if I run the code with the string manually it works (as outlined below:)
SELECT CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108)
FROM TestTable
WHERE EventSerial = '100001'
SELECT CONVERT(DATETIME, '20150402 10:48')
SELECT CONVERT(DATETIME, '20150402 10:04')
Any suggestions, I'm sure its something silly that I'm missing (and I probably took the long way around the issue anyway. The desired output would be 2015-04-02 10:04:00
First, datetime has no format. (why?)
Second, you don't need to convert the datetime value to char to add hours and minutes, just use DateAdd:
SELECT DATEADD(Minute,
CAST(RIGHT(LogTime, 2) as int),
DATEADD(Hour,
CAST(LEFT(LogTime, 2) as int),
LogDate
)
)
FROM TestTable
WHERE EventSerial = '100001'
Also, note that convert does not hold a style for yyyymmdd hh:mm
Note: code was written directly here, there might be some mistakes.
I'm not sure why you're getting the error... possibly there are some unseen characters in your varchar time field... like a tab or something maybe? Try this query:
SELECT ascii(substring(LogTime,1,1)) Char1,
ascii(substring(LogTime,2,1)) Char2,
ascii(substring(LogTime,3,1)) Char3,
ascii(substring(LogTime,4,1)) Char4,
ascii(substring(LogTime,5,1)) Char5
FROM TestTable
WHERE EventSerial = '100001'
It should show these results:
Char1 Char2 Char3 Char4 Char5
----------- ----------- ----------- ----------- -----------
49 48 58 48 52
(1 row(s) affected)
This would be a bit more efficient:
select dateadd(minute, datediff(minute,0, LogTime), LogDate)
FROM TestTable
But this assumes that your date field always has 00:00:00 time information. If you want to be sure that is stripped out as well you could use:
select dateadd(minute, datediff(minute,0, LogTime), dateadd(day, datediff(day, 0, Logdate),0))
FROM TestTable

Convert Date Stored as NUMERIC to DATETIME

I am currently working on a query that needs to calculate the difference in days between two different dates. I've had issues with our DATE columns before, because they are all being stored as numeric columns which is a complete pain.
I tried using CONVERT as I had done in the past to try and get the different pieces of the DATETIME string built, but I am not having any luck.
The commented line --convert(datetime,) is where I am having the issue. Basically, I need to convert PO_DATE and LINE_DOCK_DATE to a format that is usable, so I can calculate the difference between the two in days.
USE BWDW
GO
SELECT
[ITEM_NO]
,[ITEM_DESC]
,[HEADER_DUE_DATE]
,[BWDW].[dbo].[DS_tblDimWhs].WHS_SHORT_NAME AS 'Warehouse'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_NO] AS 'PO NUMBER'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_DATE] AS 'Start'
,[BWDW].[dbo].[DS_tblFactPODtl].[PO_STATUS] AS 'Status'
,[BWDW].[dbo].[DS_tblFactPODtl].[LINE_DOCK_DATE] AS 'End'
--,(SELECT CONVERT(DATETIME, CONVERT(CHAR(8), [BWDW].[dbo].[DS_tblFactPODtl].[PO_DATE])) FROM dbo.DS_tblFactPODtl)
FROM [BWDW].[dbo].[DS_tblFactPODtl]
INNER JOIN [BWDW].[dbo].[DS_tblDimWhs] ON [BWDW].[dbo].[DS_tblFactPODtl].WAREHOUSE = [BWDW].[dbo].[DS_tblDimWhs].WAREHOUSE
INNER JOIN [BWDW].[dbo].[DS_tblFactPO] ON [BWDW].[dbo].[DS_tblFactPODtl].PO_NO = [BWDW]. [dbo].[DS_tblFactPO].PO_NO
WHERE [BWDW].[dbo].[DS_tblFactPODtl].[PO_STATUS] = 'Closed'
AND [BWDW].[dbo].[DS_tblFactPODtl].[LINE_DOCK_DATE] <> 0
I have a snippet I saved from a previous project I worked on that needed to only display results from today through another year. That had a bunch of CAST and CONVERTS in it, but I tried the same methodology with no success.
In the long run, I want to add a column to each database table to contain a proper datetime column that is usable in the future... but that is another story. I have read numerous posts on stackoverflow that talk about converting to NUMERIC and such, but nothing out of a NUMERIC back to DATETIME.
Example data:
Start | End | Difference
--------------------------------
20110501 | 20111019 | 171
20120109 | 20120116 | 7
20120404 | 20120911 | 160
Just trying to calculate the difference..
MODIFIED PER AARON:
SELECT
FPODtl.[ITEM_NO] AS [Item]
,FPODtl.[ITEM_DESC] AS [Description]
,D.WHS_SHORT_NAME AS [Warehouse]
,FPODtl.[PO_NO] AS [PO NUMBER]
,FPODtl.[PO_DATE] AS [Start]
,FPODtl.[PO_STATUS] AS [Status]
,FPODtl.[LINE_DOCK_DATE] AS [End]
,DATEDIFF
(
DAY,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.PO_DATE)) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.PO_DATE)) END,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) END
)
FROM [dbo].[DS_tblFactPODtl] AS FPODtl
INNER JOIN [dbo].[DS_tblDimWhs] AS D
ON FPODtl.WAREHOUSE = D.WAREHOUSE
INNER JOIN [dbo].[DS_tblFactPO] AS FPO
ON FPODtl.PO_NO = FPO.PO_NO
WHERE FPODtl.[PO_STATUS] = 'Closed'
AND FPODtl.[LINE_DOCK_DATE] <> 0;
DECLARE #x NUMERIC(10,0);
SET #x = 20110501;
SELECT CONVERT(DATETIME, CONVERT(CHAR(8), #x));
Result:
2011-05-01 00:00:00.000
To compare two:
DECLARE #x NUMERIC(10,0), #y NUMERIC(10,0);
SELECT #x = 20110501, #y = 20111019;
SELECT DATEDIFF
(
DAY,
CONVERT(DATETIME, CONVERT(CHAR(8), #x)),
CONVERT(DATETIME, CONVERT(CHAR(8), #y))
);
Result:
171
More importantly, fix the table. Stop storing dates as numbers. Store them as dates. If you get errors with this conversion, it's because your poor data choice has allowed bad data into the table. You can get around that - potentially - by writing the old version of TRY_CONVERT():
SELECT DATEDIFF
(
DAY,
CASE WHEN ISDATE(col1)=1 THEN CONVERT(DATETIME, col1) END,
CASE WHEN ISDATE(col2)=1 THEN CONVERT(DATETIME, col2) END
)
FROM
(
SELECT
col1 = CONVERT(CHAR(8), col1),
col2 = CONVERT(CHAR(8), col2)
FROM dbo.table
) AS x;
This will produce nulls for any row where there is garbage in either column. Here is a modification to your original query:
SELECT
[ITEM_NO] -- what table does this come from?
,[ITEM_DESC] -- what table does this come from?
,[HEADER_DUE_DATE] -- what table does this come from?
,D.WHS_SHORT_NAME AS [Warehouse] -- don't use single quotes for aliases!
,FPODtl.[PO_NO] AS [PO NUMBER]
,FPODtl.[PO_DATE] AS [Start]
,FPODtl.[PO_STATUS] AS [Status]
,FPODtl.[LINE_DOCK_DATE] AS [End]
,DATEDIFF
(
DAY,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.PO_DATE)) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.PO_DATE)) END,
CASE WHEN ISDATE(CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) = 1
THEN CONVERT(DATETIME, CONVERT(CHAR(8), FPODtl.[LINE_DOCK_DATE])) END
)
FROM [dbo].[DS_tblFactPODtl] AS FPODtl
INNER JOIN [dbo].[DS_tblDimWhs] AS D
ON FPODtl.WAREHOUSE = D.WAREHOUSE
INNER JOIN [dbo].[DS_tblFactPO] AS FPO
ON FPODtl.PO_NO = FPO.PO_NO
WHERE FPODtl.[PO_STATUS] = 'Closed'
AND FPODtl.[LINE_DOCK_DATE] <> 0;
If the date stored as a number is like this: 20130226 for today, then the simpler way to convert to DATE or DATETIME would be:
SELECT CONVERT(DATETIME,CONVERT(VARCHAR(8),NumberDate),112)
Here is a quick formula to create a date from parts :
DateAdd( Month, (( #Year - 1900 ) * 12 ) + #Month - 1, #Day - 1 )
Simply use substrings from your original field to extract #Year, #Month and #Day. For instance, if you have a numeric like 19531231 for december 31th, 1953, you could do :
DateAdd( Month, (( SubString(Cast(DateField As Varchar(8)), 1, 4) - 1900 ) * 12 ) +
SubString(Cast(DateField As Varchar(8)), 5, 2) - 1,
SubString(Cast(DateField As Varchar(8)), 7, 2) - 1 )

Resources