How do you handle NULLs in a DATEDIFF comparison? - sql

I have to compare 2 separate columns to come up with the most recent date between them. I am using DATEDIFF(minute, date1, date2) to compare them, however, in some records the date is Null, which returns a null result and messes up the CASE.
Is there a way around this, or a way to predetermine which date is null up front?
(psudocode)
UPDATE TABLE
SET NAME = p.name,
NEW_DATE = CASE WHEN DATEDIFF(minute,d.date1,d.date2) <= 0 THEN d.date
ELSE d.date2
END
FROM TABLE2 d
INNER JOIN TABLE3 p
ON d.ACCTNUM = p.ACCTNUM

You can just add extra logic into your CASE:
UPDATE TABLE
SET NAME = p.name,
NEW_DATE = CASE
WHEN d.date1 IS NULL THEN -- somewhat
WHEN d.date2 IS NULL THEN -- somewhat
WHEN DATEDIFF(minute,d.date1,d.date2) <= 0 THEN d.date
ELSE d.date2
END
FROM TABLE2 d
INNER JOIN TABLE3 p
ON d.ACCTNUM = p.ACCTNUM

I would use ISNULL.
UPDATE TABLE
SET NAME = p.name,
NEW_DATE = CASE WHEN ISNULL(DATEDIFF(minute,d.date1,d.date2), 0) <= 0 THEN d.date
ELSE d.date2
END
FROM TABLE2 d
INNER JOIN TABLE3 p
ON d.ACCTNUM = p.ACCTNUM
or maybe
ISNULL(DATEDIFF(minute,d.date1,d.date2), 1)
if you want to handle the null values the other way around.

You can try some think like this. You can use Is Null to check null.
UPDATE TABLE
SET NAME = p.name,
NEW_DATE = CASE Case When date2 Is Null Then GetDate()
Case When date1 Is Null Then GetDate()
WHEN DATEDIFF(minute,d.date1,d.date2) <= 0 THEN d.date
ELSE d.date2
END
FROM TABLE2 d
INNER JOIN TABLE3 p
ON d.ACCTNUM = p.ACCTNUM
Microsoft's ISNULL() function is used to specify how we want to treat
NULL values.
In this case we want NULL values to be zero.
Below, if "UnitsOnOrder" is NULL it will not harm the calculation,
because ISNULL() returns a zero if the value is NULL:
SQL Server / MS Access
SELECT ProductName,UnitPrice*(UnitsInStock+ISNULL(UnitsOnOrder,0)) FROM Products
SQL NULL Functions

This should give you what you want with minimal processing required.
UPDATE TABLE
SET NAME = p.name,
NEW_DATE = CASE WHEN COALESCE(date1, date2)>COALESCE(date2, date1)
THEN COALESCE(date1, date2)
ELSE COALESCE(date2, date1)
END
FROM TABLE2 d
INNER JOIN TABLE3 p
ON d.ACCTNUM = p.ACCTNUM
WHERE NOT (date1 is null and date2 is null);

I personally use this:
IIF([yourvariable] is null, expression_when_true, expression_when_false)
This actually works well if you have more complex datatypes like DATE. I've looked for the solution also and lots of people asked how to substract null date or why simple 0000-00-00 doesn't work. The IIF can avoid substraction when minuend or subtrahend is null.
Example:
IIF([ReturnDate] is null,
datediff(day,[BookDate],getdate())*CostPerDay,
datediff(day,[BookDate],[ReturnDate])*CostPerDay)

Related

If else condition in MSSQL

Suppose I have serial number, test name and few other columns, i want to write a condition if TESTNAME is null for a particular serial number then set the TESTNAME to blank else perform inner join
SELECT
(A.PTNUMBER + '-' +A.SL_NO) AS ENUMBER,
D.ENGINEER AS REQ, D.DATETIME as "DATE",
(select Value
from DROPDOWN
where B.TEST_NAME=CONVERT(VARCHAR,DropdownID)) TESTNAME,
TABLE_NAME AS TABLETD
FROM INSPECTION D
INNER JOIN TABLEA A ON D.ENGID = CONVERT(VARCHAR,A.EN_ID)
INNER JOIN TABLEB B ON B.ENGID = CONVERT(VARCHAR,A.EN_ID)
INNER JOIN TABLEC C ON C.ENGID = CONVERT(VARCHAR,A.EN_ID)
not sure what you mean by set testname to blank but if you meant to be using a SELECT query then you can do like
select *,
case when TESTNAME is null and serial_number = some_value then '' end as TESTNAME
from mytable
You could combine a case expression and coalesce() along with your join to choose the value you want to return.
select serial_number, ...
,case when coalesce(testname,'') <> ''
then t2.testname
else coalesce(testname,'') end
from t
inner join t2
on ...
You can use isnull() or coalesce() in sql server to return a different value to replace null.
select isnull(testname,'')
or
select coalesce(testname,'')
The main difference between the two is that coalesce() can support more than 2 parameters, and it selects the first one that is not null. More differences between the two are answered here.
select coalesce(testname,testname2,'')
coalesce() is also standard ANSI sql, so you will find it in most RDBMS. isnull() is specific to sql server.
Reference:
isnull() - msdn
coalesce() - msdn
SELECT (A.PTNUMBER + '-' + A.SL_NO) AS ENUMBER,
D.ENGINEER AS REQ,
D.DATETIME as "DATE",
case
when SerialNo = xxx and TESTNAME is null then ''
else (select Value from DROPDOWN where B.TEST_NAME = CONVERT(VARCHAR, DropdownID))
end AS TESTNAME,
TABLE_NAME AS TABLETD
FROM INSPECTION D
INNER JOIN TABLEA A ON D.ENGID = CONVERT(VARCHAR, A.EN_ID)
INNER JOIN TABLEB B ON B.ENGID = CONVERT(VARCHAR, A.EN_ID)
INNER JOIN TABLEC C ON C.ENGID = CONVERT(VARCHAR, A.EN_ID);

How to determine two different aggregate dates on employee attendance data?

I Need help to implement employee attendance sheet. Presently am having employee attendance i.e
Query:
SELECT c.First_name + c.Middle_name + c.last_name AS employeename,
b.Device_Person_id,a.Dept_Id,Date1,
CASE WHEN b.Device_Person_id IS NOT NULL
THEN 'P'
ELSE 'A' END AS status
FROM Emp_setting a
LEFT OUTER JOIN (SELECT Device_Person_id, MAX(logDateTime) AS Date1
FROM tempDeviceLogs
GROUP BY Device_Person_id) b
ON a.personal_id = b.Device_Person_id
LEFT OUTER JOIN persons_profile c
ON c.pesonal_id=a.personal_id
Result:
employeename Device_person_id dept_id date1 status
MEHABOOB NULL 4 NULL A
UDAY NULL 26 NULL A
SHANKRAYYA NULL 10 NULL A
BASAVARAJ NULL 24 NULL A
BHIMAPPA 5 10 2014-05-23 14:14:00.000 P
i.e. Employeename BHIMAPPA is present on 2014-05-23.
NOW I want the list of employees who is present on 2014-05-23.
Please help?
Assuming that logDateTime indicates the presence of the employee, and given that the current derived table returns the MAX() of this field (presumably the last time the employee was detected) you would need another join to the tempDeviceLogs table to do the filtering. You could do the filtering at the same time with an INNER JOIN, viz:
...
INNER JOIN (SELECT Device_Person_id
FROM tempDeviceLogs
WHERE logDateTime >= '2014-05-23' and logDateTime < '2014-05-24') x
ON x.Device_Person_id = a.personal_id
Edit
Given that you want to SELECT the date as well, I'm assuming you want to parameterize it / use it for a range. And making yet another assumption about your SqlServer version being >= 2008, cast the DateTime to a Date and group by it:
SELECT c.First_name + c.Middle_name + c.last_name AS employeename,
b.Device_Person_id,a.Dept_Id, Date1 as DateTimeLastSeen,
x.logDate As DatePresent,
CASE WHEN b.Device_Person_id IS NOT NULL THEN 'P' ELSE 'A' END AS status
FROM Emp_setting a
LEFT OUTER JOIN (SELECT Device_Person_id, MAX(logDateTime) AS Date1
FROM tempDeviceLogs
GROUP BY Device_Person_id) b
ON a.personal_id = b.Device_Person_id
LEFT OUTER JOIN persons_profile c
ON c.pesonal_id=a.personal_id
INNER JOIN (SELECT Device_Person_id, CAST(logDateTime AS DATE) as logDate
FROM tempDeviceLogs
GROUP BY Device_Person_id, CAST(logDateTime AS DATE)) x
ON x.Device_Person_id = a.personal_id
WHERE
logDate BETWEEN '2014-05-01' AND '2014-05-23';
This could also be done with a DISTINCT. If you have an earlier version of SqlServer, use a hack like this to obtain the date part of a DateTime (in the select + group)
Create a variable to hold the date which you are looking for.
DECLARE #cutOffDate DATE
SET #cutOffDate = '23-05-2014'
Then add to the end of your statement
Where Date1 = #cutOffDate

SQL Conditional Cast VARCHAR for FLOAT

When I run the following query I get error "Cannot cast VARCHAR to FLOAT". The problem is some else on my team designed the DB a while back and put Milliseconds as varchar and sSometime the value isn't there so he put "NA" instead.
Now when I cast the value for Milliseconds to take the AVE(), "NA" cannot be casted. Is there a way to define default value for "NA", like:
IF a.Milliseconds == "NA"
0
ELSE
CAST(a.Milliseconds AS FLOAT)
SELECT
b.Date,
AVG(CAST(a.Milliseconds AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date
Sample Data
CyclesId | Milliseconds
1 | 24.1557
2 | 23.4886
3 | NA
Use a CASE statement around your value
SELECT
b.Date,
AVG(CAST(CASE WHEN a.Milliseconds = 'NA' THEN '0' ELSE a.Milliseconds END AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date
If you want the NA values excluded all together then you will do the following:
SELECT
b.Date,
AVG(CAST(a.Milliseconds AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
WHERE a.Milliseconds != 'NA'
GROUP BY b.Date
see SQL Fiddle with Demo
use CASE... for example...
SELECT
b.Date,
AVG(CAST( (case when a.Milliseconds = 'NA' then 0 else a.Milliseconds end) AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date

SQL Query + more efficient alternative

I have a query which involves 2 tables 'Coupons' and 'CouponUsedLog' in SQL Server, the query below will obtain some information from these 2 tables for statistics study use. Somehow I feel that while my query works and returns me the desired results, I feel that I can be written in a more efficient way, can someone please advice if there's a better way to rewrite this? Am I using too many unnecessary variables and joins? Thanks.
DECLARE #CouponISSUED int=null
DECLARE #CouponUSED int=null
DECLARE #CouponAVAILABLE int=null
DECLARE #CouponEXPIRED int=null
DECLARE #CouponLastUsed Date=null
--Total CouponIssued
SET #CouponISSUED =
(
select count(*)
from Coupon C Left Join
couponusedlog CU on C.autoid = CU.Coupon_AutoID
where C.VoidedBy is null and
C.VoidedOn is null and
DeletedBy is null and
DeletedOn is null and
Card_AutoID in (Select AutoID
from Card
where MemberID = 'Mem001')
)
--Total CouponUsed
SET #CouponUSED =
(
select count(*)
from couponusedlog CU Left Join
Coupon C on CU.Coupon_AutoID = V.autoid
where CU.VoidedBy is null and
CU.VoidedOn is null and
C.Card_AutoID in (select AutoID
from Card
where MemberID = 'Mem001')
)
SET #CouponAVAILABLE = #CouponISSUED - #CouponUSED
--Expired Coupons
SET #CouponEXPIRED =
(
select Count(*)
from Coupon C Left Join
couponusedlog CU on C.autoid = CU.Coupon_AutoID
where C.VoidedBy is null and
C.VoidedOn is null and
deletedBy is null and
deletedOn is null and
Card_AutoID in (select AutoID
from Card
where MemberID = 'Mem002') and
CONVERT (date, getdate()) > C.expirydate
)
--Last Used On
SET #CouponLastUsed =
(
select CONVERT(varchar(10),
Max(VU.AddedOn), 103) AS [DD/MM/YYYY]
from couponusedlog CU Left Join
coupon C on CU.Coupon_AutoID = C.autoid
where CU.voidedBy is null and
CU.voidedOn is null and
C.Card_AutoID in (select AutoID
from Card
where MemberID = 'Mem002')
)
Select #CouponISSUED As Coupon_Issued,
#CouponUSED As Coupon_Used,
#CouponAVAILABLE As Coupon_Available,
#CouponEXPIRED As Coupon_Expired,
#CouponLastUsed As Last_Coupon_UsedOn
In general its better to do things in a single query if you you're just looking for counts of things particularly against nearly the same data set then in four separate queries.
This query combines what you need into a single query by converting your WHERE Clauses into SUMS of CASE statements. The MAX of the date is just a normal thing you can do when you're doing a count or a sum.
SELECT COUNT(*) couponissued,
SUM(CASE
WHEN deletedby IS NULL
AND deletedon IS NULL THEN 1
ELSE 0
END) AS couponused,
SUM(CASE
WHEN deletedby IS NULL
AND deletedon IS NULL
AND Getdate() > c.expirydate THEN 1
ELSE 0
END) AS couponex,
MAX(vu.addedon) CouponEXPIRED
FROM [couponusedlog] cu
LEFT JOIN [Coupon] c
ON ( cu.coupon_autoid = v.autoid )
WHERE cu.voidedby IS NULL
AND cu.voidedon IS NULL
AND ( c.card_autoid IN (SELECT [AutoID]
FROM [Card]
WHERE memberid = 'Mem001') )
You can then convert that into a Common Table Expression to do your subtraction and formatting
Are you asking this question out of a proactive desire to be as effecient as possible, or because of an actual performance issue you would like to correct? You can make this more effecient at the cost of having code that is harder to manage. If the performance is okay right now I would highly recommend you leave it because the next person to come along will be able to understand it just fine. If you make one huge effecient but garbled sql statement out of it then when you or anyone else wants to update something about it it's going to take you 3 times longer as you try to re-figure out what the heck you were thinking when you wrote it.

Aggregate function in comparison of 2 rows in the same table (SQL)

Given the table definition:
create table mytable (
id integer,
mydate datetime,
myvalue integer )
I want to get the following answer by a single SQL query:
id date_actual value_actual date_previous value_previous
where:
date_previous is the maximum of all the dates preceeding date_actual
for each id and values correspond with the two dates
{max(date_previous) < date_actual ?}
How can I achieve that?
Thanks for your hints
This is a variation of the common "greatest N per group" query which comes up every week on StackOverflow.
SELECT m1.id, m1.mydate AS date_actual, m1.myvalue AS value_actual,
m2.mydate AS date_previous, m2.myvalue AS value_previous
FROM mytable m1
LEFT OUTER JOIN mytable m2
ON (m1.id = m2.id AND m1.mydate > m2.mydate)
LEFT OUTER JOIN mytable m3
ON (m1.id = m3.id AND m1.mydate > m3.mydate AND m3.mydate > m2.mydate)
WHERE m3.id IS NULL;
In other words, m2 is all rows with the same id and a lesser mydate, but we want only the one such that there is no row m3 with a date between m1 and m2. Assuming the dates are unique, there will only be one row in m2 where this is true.
Assuming I understood your requirements correctly, here's something you can try.
select a.id,
a.mydate as date_actual,
a.value as value_actual,
b.date as date_previous,
b.value as value_previous
from mytable a, mytable b
where a.id = b.id and
a.mydate > b.mydate and
b.mydate = (select max(mydate) from mytable c where c.id = a.id and c.mydate < a.mydate)
Apologies for the ugly SQL. I am sure there are better ways of doing this.