Equivalent of GETDATE() BETWEEN Two DateTime Fields where Both can be NULL - sql-server

Is
SELECT [Id]
,[DateOnline] --Nullable
,[DateOffline] --Nullable
,[PageId]
,[DownloadId]
,[Weight]
FROM [DownloadPage]
WHERE GETDATE() BETWEEN [DateOnline] AND [DateOffline]
Equivalent to:
SELECT [Id]
,[DateOnline] --Nullable
,[DateOffline] --Nullable
,[PageId]
,[DownloadId]
,[Weight]
FROM [DownloadPage]
WHERE ([DateOnline] IS NULL OR [DateOnline] <= GETDATE())
AND ([DateOffline] IS NULL OR [DateOffline] > GETDATE())
But with also catering for NULLs?
Or is there a more elegant way of doing it?
Where are parentheses needed here?
Thanks.
EDIT:
Both [DateOnline] AND [DateOffline] are of type DateTime
If [DateOnline] is NULL then the logic is "online now"
If [DateOffline] is NULL then the logic is "never go offline (once online)"
Sorry, I should have included this in my question to begin with.

The author's second query will net better performance even if there is no indexes on those columns. If there are indexes, that's a no brainer... using coalesce will disable the index and do a table scan instead of index seek (very bad for performance).
Even if there aren't any indexes on those columns, "is null" will return a constant... whereas, in the case of coalesce function, the NULL values will still need to be evaluated each time. If you have a table full of NULL DateOnline and DateOffline, this performance leak cannot be ignored.
In any case, I can't think of a reason why you'd use coalesce in this case.
Also, I'm guessing (since you're checking date range) that those two dates are all or nothing. You probably only have to check for one of those dates.
WHERE ([DateOnline] IS NULL)
OR GETDATE() BETWEEN [DateOnline] AND [DateOffline]

You can use COALESCE to convert nulls into a meaningful value. In this example I've chose default values that will always be inside the valid range.
WHERE GETDATE() BETWEEN COALESCE([DateOnline] , '1900-01-01')
AND COALESCE([DateOffline], '2099-12-31')
Demo: http://www.sqlfiddle.com/#!3/def09/1

Related

Cast SQL Column definition and default value

I have the following table create definition in SQL Server:
CREATE TABLE [dbo].[EventLog]
(
[RawEvent] NVARCHAR(MAX) NOT NULL,
[ReceivedDate] AS CAST(JSON_VALUE(RawEvent, '$.ReceivedTime') AS DATETIME)
)
When I receive a JSON string, that string has ReceivedTime which I parse out and use that as the ReceivedDate column's value when inserting this record.
This is currently working. However, when RawEvent string doesn't have the property ReceivedTime, I want to use the current SQL date time as the default value instead of NULL which is what it is doing now.
Is this possible to do in the table definition?
In SQL you can use COALESCE to supply a default value instead of null
COALESCE(CAST(JSON_VALUE(RawEvent, '$.ReceivedTime') AS DATETIME),GETDATE())
While I think #AaronBertrand idea of COALESCE is more straightforward, here's a working example using computed columns:
CREATE TABLE [dbo].[EventLog] (
[RawEvent] NVARCHAR(MAX) NOT NULL,
[ReceivedDate] AS CAST(JSON_VALUE(RawEvent, '$.ReceivedTime') AS DATETIME),
[ReceivedDateDefault] DATETIME NOT NULL DEFAULT GETDATE(),
[ReceivedDateCalculated] AS (CASE WHEN JSON_VALUE(RawEvent, '$.ReceivedTime') IS NULL
THEN [ReceivedDateDefault]
ELSE JSON_VALUE(RawEvent, '$.ReceivedTime') END),
)
INSERT INTO dbo.EventLog (RawEvent)
VALUES
( N'{"Foo":"Bar",
"ReceivedTime":"2019-08-02"
}'),
( N'{"Foo":"Baz"
}')
And the results:
SELECT ReceivedDateCalculated FROM dbo.EventLog
ReceivedDateCalculated
2019-08-02 00:00:00.000
2019-08-02 13:51:14.910
Might be some funky edge cases with empty strings or something, you would definitely want to consider that. There is one additional benefit of having the second date column, that you will always have a timestamp on this row (if you don't already). Which I've found tends to be pretty useful.

SQL Server : Select ... From with FULL JOIN with a Default value for a column

I created a table, tblNewParts with 3 columns:
NewCustPart
AddedDate
Handled
and I am trying to FULL JOIN it to an existing table, tblPartsWorkedOn.
tblNewParts is defined to have Handled defaulted to 'N'...
SELECT *
FROM dbo.tblPartsWorkedOn AS BASE
FULL JOIN dbo.tblNewParts AS ADDON ON BASE.[CustPN] = ADDON.[NewCustPart]
WHERE ADDON.[Handled] IS NULL
ORDER BY [CustPN] DESC
And I want the field [Handled] to come back as 'N' instead of NULL when I run the query. The problem is that when there aren't any records in the new table, I get NULL's instead of 'N's.
I saw a SELECT CASE WHEN col1 IS NULL THEN defaultval ELSE col1 END as a mostly suitable answer from here. I am wondering if this will work in this instance, and how would I write that in T-SQL for SQL Server 2012? I need all of the columns from both tables, rather than just the one.
I'm making this a question, rather than a comment on the cited link, so as to not obscure the original link's question.
Thank you for helping!
Name the column (alias.column_name) in select statement and use ISNULL(alias.column,'N').
Thanks
After many iterations I found the answer, it's kind of bulky but here it is anyway. Synopsis:
Yes, the CASE statement does work, but it gives the output as an unnamed column. Also, in this instance to get all of the original columns AND the corrected column, I had to use SELECT *, CASE...END as [ColumnName].
But, here is the better solution, as it will place the information into the correct column, rather than adding a column to the end of the table and calling that column 'Unnamed Column'.
Select [ID], [Seq], [Shipped], [InternalPN], [CustPN], [Line], [Status],
CASE WHEN ADDON.[NewCustPart] IS NULL THEN BASE.[CustPN] ELSE
ADDON.[NewCustomerPart] END as [NewCustPart],
GetDate() as [AddedDate],
CASE WHEN ADDON.[Handled] IS NULL THEN 'N' ELSE ADDON.[Handled] END as [Handled]
from dbo.tblPartsWorkedOn as BASE
full join dbo.tblNewParts as AddOn ON Base.[CustPN] = AddOn.NewCustPart
where AddOn.Handled = 'N' or AddOn.Handled is null
order by [NewCustPart] desc
This sql code places the [CustPN] into [NewCustPart] if it's null, it puts a 'N' into the field [Handled] if it's null and it assigns the date to the [AddedDate] field. It also only returns records that have not been handled, so that you get the ones that need to be looked at; and it orders the resulting output by the [NewCustPart] field value.
Resulting Output looks something like this: (I shortened the DateTime for the output here.)
[ID] [SEQ] [Shipped] [InternalPN] [CustPN] [Status] [NewCustPart] [AddedDate] [Handled]
1 12 N 10012A 10012A UP 10012A 04/02/2016 N
...
Rather than with the nulls:
[ID] [SEQ] [Shipped] [InternalPN] [CustPN] [Status] [NewCustPart] [AddedDate] [Handled]
1 12 N 10012A 10012A UP NULL NULL NULL
...
I'm leaving this up, and just answering it rather than deleting it, because I am fairly sure that someone else will eventually ask this same question. I think that lots of examples showing how and why something is done, is a very helpful thing to have as not everything can be generalized. Just some thoughts and I hope that this helps someone else!

SQL Server Converting int to Date

I have a scenario where I have an int column with the following dates for example:
20131210
20131209
What I want is, I want to convert the above to a date datatype so that I can use it with GETDATE() function.
This is my try but I am getting an error:
SELECT CONVERT(DATETIME, CONVERT(varchar(8), MyColumnName))
FROM MyTable
WHERE DATEADD(day, -2, CONVERT(DATETIME, CONVERT(CHAR(8), MyColumnName))) < GetDate()
This is the error I am getting:
Conversion failed when converting date and/or time from character string.
You have at least one bad date in your column (it could be 99999999 or 20130231 or who knows). This is what happens when you choose the wrong data type. You can identify the bad row(s) using:
SELECT MyColumnName FROM dbo.MyTable
WHERE ISDATE(CONVERT(CHAR(8), MyColumnMame)) = 0;
Then you can fix them or delete them.
Once you do that, you should fix the table. There is absolutely no upside to storing dates as integers, and a whole lot of downsides.
Once you have the date being stored in the correct format, your query is much simpler. And I highly recommend applying functions etc. to the right-hand side of the predicate as opposed to applying it to the column (this kills SQL Server's ability to make efficient use of any index on that column, which you should have if this is a common query pattern).
SELECT MyColumnName
FROM dbo.MyTable
WHERE MyColumnName < DATEADD(DAY, 2, GETDATE());
Try:
CREATE TABLE IntsToDates(
Ints INT
)
INSERT INTO IntsToDates
VALUES (20131210)
, (20131209)
SELECT CAST(CAST(Ints AS VARCHAR(12)) AS DATE)
FROM IntsToDates
I've had a similar problem where I need to convert INT to DATE and needed to cater for values of 0. This case statement did the trick for me
CASE MySourceColumn
WHEN ISDATE(CONVERT(CHAR(8),MySourceColumn)) THEN CAST('19000101' AS DATE)
ELSE CAST(CAST(MySourceColumn AS CHAR) AS DATE)
END
AS MyTargetColumn

how do i ensure a select statement returns null values for specific columns?

This might sound kind of weird, but I have a query that joins two tables. I'm using an IF statements that dictates what to return. One path runs the query/join as is, the other needs to return all of the data from the first column, but only return column names with null values. Here's the query i have now:
declare #Date DATE = '06/07/2012'
IF #DATE >= GETDATE()
BEGIN
SELECT DisplayName, '' [RegularHours], ''[OvertimeHours]
FROM Sites
ORDER BY DisplayName
END
ELSE
SELECT sites.DisplayName, hrs.SiteDate, hrs.RegularHrs, hrs.OverTimeHrs
FROM Sites sites
left join SiteHours hrs on sites.SiteID = hrs.SiteID
ORDER BY DisplayName
What's making me nervous is that the second and third columns do not have values at all, not even NULL. I'm worried that this will pose a problem later. Any ideas?
If I understand the question correctly, I think you can do:
SELECT DisplayName, NULL as 'RegularHours', NULL as 'OvertimeHours'

SQL Server ORDER BY date and nulls last

I am trying to order by date. I want the most recent dates coming in first. That's easy enough, but there are many records that are null and those come before any records that have a date.
I have tried a few things with no success:
ORDER BY ISNULL(Next_Contact_Date, 0)
ORDER BY ISNULL(Next_Contact_Date, 999999999)
ORDER BY coalesce(Next_Contact_Date, 99/99/9999)
How can I order by date and have the nulls come in last? The data type is smalldatetime.
smalldatetime has range up to June 6, 2079 so you can use
ORDER BY ISNULL(Next_Contact_Date, '2079-06-05T23:59:00')
If no legitimate records will have that date.
If this is not an assumption you fancy relying on a more robust option is sorting on two columns.
ORDER BY CASE WHEN Next_Contact_Date IS NULL THEN 1 ELSE 0 END, Next_Contact_Date
Both of the above suggestions are not able to use an index to avoid a sort however and give similar looking plans.
One other possibility if such an index exists is
SELECT 1 AS Grp, Next_Contact_Date
FROM T
WHERE Next_Contact_Date IS NOT NULL
UNION ALL
SELECT 2 AS Grp, Next_Contact_Date
FROM T
WHERE Next_Contact_Date IS NULL
ORDER BY Grp, Next_Contact_Date
According to Itzik Ben-Gan, author of T-SQL Fundamentals for MS SQL Server 2012, "By default, SQL Server sorts NULL marks before non-NULL values. To get NULL marks to sort last, you can use a CASE expression that returns 1 when the" Next_Contact_Date column is NULL, "and 0 when it is not NULL. Non-NULL marks get 0 back from the expression; therefore, they sort before NULL marks (which get 1). This CASE expression is used as the first sort column." The Next_Contact_Date column "should be specified as the second sort column. This way, non-NULL marks sort correctly among themselves." Here is the solution query for your example for MS SQL Server 2012 (and SQL Server 2014):
ORDER BY
CASE
WHEN Next_Contact_Date IS NULL THEN 1
ELSE 0
END, Next_Contact_Date;
Equivalent code using IIF syntax:
ORDER BY
IIF(Next_Contact_Date IS NULL, 1, 0),
Next_Contact_Date;
order by -cast([Next_Contact_Date] as bigint) desc
If your SQL doesn't support NULLS FIRST or NULLS LAST, the simplest way to do this is to use the value IS NULL expression:
ORDER BY Next_Contact_Date IS NULL, Next_Contact_Date
to put the nulls at the end (NULLS LAST) or
ORDER BY Next_Contact_Date IS NOT NULL, Next_Contact_Date
to put the nulls at the front. This doesn't require knowing the type of the column and is easier to read than the CASE expression.
EDIT: Alas, while this works in other SQL implementations like PostgreSQL and MySQL, it doesn't work in MS SQL Server. I didn't have a SQL Server to test against and relied on Microsoft's documentation and testing with other SQL implementations. According to Microsoft, value IS NULL is an expression that should be usable just like any other expression. And ORDER BY is supposed to take expressions just like any other statement that takes an expression. But it doesn't actually work.
The best solution for SQL Server therefore appears to be the CASE expression.
A bit late, but maybe someone finds it useful.
For me, ISNULL was out of question due to the table scan. UNION ALL would need me to repeat a complex query, and due to me selecting only the TOP X it would not have been very efficient.
If you are able to change the table design, you can:
Add another field, just for sorting, such as Next_Contact_Date_Sort.
Create a trigger that fills that field with a large (or small) value, depending on what you need:
CREATE TRIGGER FILL_SORTABLE_DATE ON YOUR_TABLE AFTER INSERT,UPDATE AS
BEGIN
SET NOCOUNT ON;
IF (update(Next_Contact_Date)) BEGIN
UPDATE YOUR_TABLE SET Next_Contact_Date_Sort=IIF(YOUR_TABLE.Next_Contact_Date IS NULL, 99/99/9999, YOUR_TABLE.Next_Contact_Date_Sort) FROM inserted i WHERE YOUR_TABLE.key1=i.key1 AND YOUR_TABLE.key2=i.key2
END
END
Use desc and multiply by -1 if necessary. Example for ascending int ordering with nulls last:
select *
from
(select null v union all select 1 v union all select 2 v) t
order by -t.v desc
I know this is old but this is what worked for me
Order by Isnull(Date,'12/31/9999')
I think I found a way to show nulls in the end and still be able to use indexes for sorting.
The idea is super simple - create a calculatable column which will be based on existing column, and put an index on it.
ALTER TABLE dbo.Users
ADD [FirstNameNullLast]
AS (case when [FirstName] IS NOT NULL AND (ltrim(rtrim([FirstName]))<>N'' OR [FirstName] IS NULL) then [FirstName] else N'ZZZZZZZZZZ' end) PERSISTED
So, we are creating a persisted calculatable column in the SQL, in that column all blank and null values will be replaced by 'ZZZZZZZZ', this will mean, that if we will try to sort based on that column, we will see all the null or blank values in the end.
Now we can use it in our new index.
Like this:
CREATE NONCLUSTERED INDEX [IX_Users_FirstNameNullLast] ON [dbo].[Users]
(
[FirstNameNullLast] ASC
)
So, this is an ordinary nonclustered index. We can change it however we want, i.e. include extra columns, increase number of indexes columns, change sorting order etc.
I know this is a old thread, but in SQL Server nulls are always lower than non-null values. So it's only necessary to order by Desc
In your case Order by Next_Contact_Date Desc should be enough.
Source: order by with nulls- LearnSql

Resources