SQL Query + more efficient alternative - sql

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.

Related

Simplify combining multiple table variables [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I have the code below (obviously with some changed names). I'm trying to find a simpler or more-effective way to create the same results. Any suggestions appreciated. THIS IS FOR FUN. MINE ALREADY WORKS
DECLARE #Scenes TABLE(IncNo numeric(12,0), SceneSquad int, ScDate datetime)
INSERT INTO #Scenes
SELECT A.IncNo, SceneSquad, ScDate
FROM DATABASE as A
LEFT JOIN DATABASE as B
On A.IncNo = B.IncNo
WHERE ScDate BETWEEN '1/1/2021' AND '10/31/2021'
AND A.DivID = ''
DECLARE #Assigned TABLE(IncNo numeric(12,0), Followupsquad int, AssignmentDate
datetime)
INSERT INTO #Assigned
SELECT IncNo, Followupsquad, ASSIGNMENTDATE
FROM DATABASE AS C
LEFT JOIN DATABASE AS D
ON D.IncNo = C.CIncNo
WHERE SOMETHING = 'Y' AND DateRuled BETWEEN '1/1/2021' AND '10/31/2021'
AND D.AssignD = ''
DECLARE #Squads TABLE(Squad int, RN int)
INSERT INTO #Squads
SELECT Squad, ROW_NUMBER () OVER (ORDER BY Squad) AS E
FROM DATABASE
WHERE UnitType = '' AND DivId = '' AND SquadStatus = 'A'
ORDER BY Squad
DECLARE #MSS TABLE (SqdNo int, SOMETHING int, SOMETHINGELSE int)
DECLARE #DATE datetime, #X int, #SQ int, #M int, #S int
SET #DATE = 1/1/2021 - 10/31/2021
SET #X = 1
WHILE #X <= (SELECT MAX(RN) FROM #Squads)
BEGIN
SET #SQ = (SELECT Squad FROM #Squads WHERE RN = #X)
SET #S = (SELECT COUNT(*) FROM #Scenes WHERE SceneSquad = #SQ)
SET #M = (SELECT COUNT(*) FROM #Assigned WHERE Followupsquad = #SQ)
INSERT INTO #MSS VALUES (#SQ, #M, #S)
SET #X = #X + 1
END
SELECT SqdNo , SOMETHING , SOMETHINGELSE
FROM #MSS
You can use Common Table Expressions (CTEs) in place of table variables, and this can help reduce and often speed up the code, such that we can get everything down to one (large) query with no loop:
WITH Scenes As (
SELECT A.IncNo, SceneSquad, ScDate
FROM DATABASE as A
LEFT JOIN DATABASE as B
On A.IncNo = B.IncNo
WHERE ScDate BETWEEN '20210101' AND '20211031'
AND A.DivID = ''
)
, Assigned As (
SELECT IncNo, Followupsquad, ASSIGNMENTDATE
FROM DATABASE AS C
LEFT JOIN DATABASE AS D
ON D.IncNo = C.CIncNo
WHERE SOMETHING = 'Y' AND DateRuled BETWEEN '20210101' AND '20211031'
AND D.AssignD = ''
)
, Squads As (
-- Removed row_number(), since it only seemed to be used for an index later
SELECT Squad
FROM DATABASE
WHERE UnitType = '' AND DivId = '' AND SquadStatus = 'A'
-- also moved the ORDER BY to the final SELECT
)
SELECT sq.Squad As SqdNo
, (SELECT COUNT(*) FROM Scenes WHERE SceneSquad = sq.Squad) As Something
, (SELECT COUNT(*) FROM Assigned WHERE FollowupSquad = sq.Squad) As SomethingElse
FROM Squads sq
ORDER BY SqdNo
Each INSERT/SELECT for a table variable in the original question is instead now a CTE, and we then roll up the loop into correlated sub-queries (nested SELECTS).
The weakness here is it re-runs the Scenes and Assigned CTEs for each row, where the table variables effectively let the server cache those query results. The strength is the original table indexes are still available.
But that's just step one. We can improve this further to get the best of both worlds, using GROUP BY and JOINs. The trick is if we tried to use GROUP BY to get counts from both additional queries this in the same step, the row counts would be multiplied. However, we can use another CTE to force them to happen sequentially and get the right answers. If I skip past the prior CTEs to save space, it looks like this:
, SquadPlusSceneCounts As (
SELECT sq.Squad, COUNT(*) As Something
FROM Squads sq
LEFT JOIN Scenes sc ON sc.SceneSquad = sq.Squad
GROUP BY sq.Squad
)
SELECT sq.Squad As SqdNo, sq.Something, COUNT(*) As SomethingElse
FROM SquadPlusSceneCounts sq
LEFT JOIN Assigned a ON a.FollowupSquaud = sq.Squad
GROUP BY sq.Squad, sq.Something
ORDER BY sq.Squad
This will likely perform much better. The code is also simpler because we no longer need table variables or INSERT statements and I was able to remove the row_number() and loop. Finally, we preserved the intermediate logic and naming, so it will still be maintainable in a similar way (you can run most of the CTEs independently to verify them).
All of this was a straight-up translation of the code from the question. Better understanding of the data might also allow for logical improvements with further performance benefits and simpler code.
However, there's still the matter of the #Date variable; it's not at all clear what you were doing there. SET #DATE = 1/1/2021 - 10/31/2021 makes no sense, and it wasn't used later. You may need an additional CTE that generates dates based on an initial value on the fly, that you can then also JOIN to. There are a number of ways to do this, and since I don't know it's even needed I'll leave it as an exercise.
One more thing. In many cases where CTEs are used, it's possible to wrap them up into nested SELECT queries instead. I tend to avoid this, because it's harder to maintain (you lose a level of naming or mnemonics and you can't run the CTE by itself as easily for testing/maintenance), but you can sometimes get a small performance benefit this way.
So for completeness, view the monstrosity below:
SELECT sq.Squad As SqdNo, sq.Something, COUNT(*) As SomethingElse
FROM (
SELECT sq0.Squad, COUNT(*) As Something
FROM (
SELECT Squad
FROM DATABASE
WHERE UnitType = '' AND DivId = '' AND SquadStatus = 'A'
) sq0
LEFT JOIN (
SELECT A.IncNo, SceneSquad, ScDate
FROM DATABASE as A
LEFT JOIN DATABASE as B On A.IncNo = B.IncNo
WHERE ScDate BETWEEN '20210101' AND '20211031'
AND A.DivID = ''
) sc ON sc.SceneSquad = sq0.Squad
GROUP BY sq0.Squad
) sq
LEFT JOIN (
SELECT IncNo, Followupsquad, ASSIGNMENTDATE
FROM DATABASE AS C
LEFT JOIN DATABASE AS D
ON D.IncNo = C.CIncNo
WHERE SOMETHING = 'Y' AND DateRuled BETWEEN '20210101' AND '20211031'
AND D.AssignD = ''
) a ON a.FollowupSquaud = sq.Squad
GROUP BY sq.Squad, sq.Something
ORDER BY sq.Squad
But again, don't do this last part unless you have to.
As a complete aside, as bad as this is, it demonstrates why I'm not a fan or ORMs. As ugly as this code is, I don't want to think about trying to replicate it via ORM syntax. And even if you could, you'd still need to know how to write code like this in order be sure it's right... at which point you may just as well devote your time to learning good SQL.

Is it possible to improve the performance of query with distinct and multiple joins?

There is following query:
SELECT DISTINCT ID, ACCOUNT,
CASE
WHEN p.GeneralLevel = '1' THEN '1'
WHEN p.Level3 IS NULL THEN '2'
WHEN p.Level4 IS NULL THEN '3'
WHEN p.Level5 IS NULL THEN '4'
WHEN p.Level6 IS NULL THEN '5'
WHEN p.Level7 IS NULL THEN '6'
WHEN p.Level8 IS NULL THEN '7'
ELSE '8'
END AS LEVEL,
CASE
WHEN c.codeValueDescription IS NULL THEN p.Level2
ELSE c.codeValueDescription
END AS L2_CODE,
CASE
WHEN d.codeValueDescription IS NULL THEN p.Level3
ELSE d.codeValueDescription
END AS L3_CODE,
CASE
WHEN j.codeValueDescription IS NULL THEN p.Level4
ELSE j.codeValueDescription
END AS L4_CODE,
CASE
WHEN f.codeValueDescription IS NULL THEN p.Level5
ELSE f.codeValueDescription
END AS L5_CODE,
CASE
WHEN g.codeValueDescription IS NULL THEN p.Level6
ELSE g.codeValueDescription
END AS L6_CODE,
CASE
WHEN h.codeValueDescription IS NULL THEN p.Level7
ELSE h.codeValueDescription
END AS L7_CODE,
p.Level8
FROM generic p
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '2') c ON p.Level2 = c.codeValue
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '3') d ON p.Level3 = d.codeValue
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '4') j ON p.Level4 = j.codeValue
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '5') f ON p.Level5 = f.codeValue
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '3') g ON p.Level6 = g.codeValue //yes, code is 3 again
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '3') h ON p.Level7 = h.codeValue //and yes, again code 3 here
Some columns of the table 'generic' (excluded dates and other non-important columns for us):
ID INTEGER NOT NULL,
ACCOUNT VARCHAR(50) NOT NULL,
GeneralLevel1 VARCHAR(50),
Level2 VARCHAR(50),
Level3 VARCHAR(50),
Level4 VARCHAR(50),
Level5 VARCHAR(50),
Level6 VARCHAR(50),
Level7 VARCHAR(50),
Level8 VARCHAR(50)
Simple data:
ID,ACCOUNT_ID,LEVEL_1,LEVEL_2,...LEVEL_8
id1,ACCOUNT_ID1,GENERAL,null,...null
id1,ACCOUNT_ID2,GENERAL,A,...null
id1,ACCOUNT_ID2,GENERAL,B,...null
id2,ACCOUNT_ID1,GENERAL,null,...null
id2,ACCOUNT_ID2,GENERAL,A,...null
id2,ACCOUNT_ID3,GENERAL,B,...H
Current query is running more than 1s, usually it returns between 100 and 1000 records, I want to improve the performance of this query. The idea is to get rid of these LEFT JOINS and somehow rewrite this query to improve performance.
Maybe there are ways to improve this query to fetch data a bit faster? I hope I've provided enough information here. Database is custom, NO_SQL giant under the hood but syntax of our database bridge is very similar to MySQL. Unfortunately, I cannot provide the EXECUTION PLAN of this query because it is processing on the server side and then generate some SQL for which I cannot have an access.
You're doing key/value lookups from your codes tables. Your query contains several of these LEFT JOIN patterns.
FROM generic p
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '2') c ON p.Level2 = c.codeValue
LEFT JOIN
(SELECT codeValue, codeValueDescription
FROM codes
WHERE code = '3') d ON p.Level3 = d.codeValue
These LEFT JOINs can be refactored to eliminate the subqueries. This refactoring may signal your intent to your SQL system more clearly. The result looks like this.
FROM generic p
LEFT JOIN codes c ON p.Level2 = c.codeValue AND c.code = '2'
LEFT JOIN codes d ON p.Level3 = d.codeValue AND d.code = '3'
If your SQL system allows indexes, a covering index like this on your codes table will help speed up your key/value lookup.
ALTER TABLE codes ADD INDEX (codeValue, code, codeValueDescription)
Your SELECT clause contains a lot of this sort of thing:
CASE
WHEN c.codeValueDescription IS NULL THEN p.Level2
ELSE c.codeValueDescription
END AS L2_CODE,
CASE
WHEN d.codeValueDescription IS NULL THEN p.Level3
ELSE d.codeValueDescription
END AS L3_CODE
It probably doesn't help much, but this can be simplified by rewriting it as
COALESCE(c.codeValueDescription, p.Level2) AS L2_CODE,
COALESCE(d.codeValueDescription, p.Level3) AS L3_CODE
What happens if you eliminate your DISTINCT qualifier? It probably takes some processing time. If your generic.ID column is the primary key, DISTINCT does you no good at all: those column values don't repeat. (Most modern SQL query planners detect that case and skip the deduplication step, but we don't know how modern your query planner is.)
Your query contains no overall WHERE clause so it necessarily must handle every row in your generic table. And, if that table is large your result set will be large. As I'm sure you know, scanning entire large tables takes time and resources.
All that being said, a millisecond per row for a query like this through a SQL bridge isn't smoking-gun-horrible performance. You may have to live with it. The alternative might be to apply the codes to your data in your application program: slurp the entire codes table then write some application logic to do your CASE / WHEN / THEN or COALESCE work. In other words, move the LEFT JOIN operations to your app. If your SQL bridge is fast at handling dirt-simple SELECT * FROM generic single table queries this will help a lot.

How to use two parameters for the same column in a stored procedure

I tried to find a similar question on Stack overflow without success...
Here is my Stored Procedure code:
USE [DATABASENAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SPNAME]
#IN_dtmin datetime,
#IN_dtmax datetime = null,
#IN_key varchar(500),
#IN_set varchar(80),
#IN_locktype varchar(500)
WITH EXEC AS CALLER
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT IDLOCK, DT, LOCKTYPE,
MAX(CASE WHEN PRODUCTATTRIBUTE = 'CodeSet' THEN VALUE END) AS CodeSet,
MAX(CASE WHEN PRODUCTATTRIBUTE = 'KeySet' THEN VALUE END) AS KeySet
FROM LOCKREGISTER LR LEFT JOIN
LOCKTYPES T
ON LR.IDLOCKTYPE = T.IDLOCKTYPE LEFT JOIN
PRODUCTATTRIBUTES PA
ON LR.IDPRODUCT = PA.IDPRODUCT AND
PRODUCTATTRIBUTE IN ('CodeSet','KeySet')
WHERE LR.DT BETWEEN IIF(#IN_DTMIN IS NULL,GETDATE(),#IN_DTMIN) AND IIF(#IN_DTMAX IS NULL,GETDATE(),#IN_DTMAX)
AND (PA.VALUE like ISNULL(#IN_key,'%') OR (PA.VALUE IS NULL AND #IN_key IS NULL))
AND (PA.VALUE like ISNULL(#IN_set,'%') OR (PA.VALUE IS NULL AND #IN_set IS NULL))
AND (T.LOCKTYPE like ISNULL(#IN_locktype,'%') OR (T.LOCKTYPE IS NULL AND #IN_locktype IS NULL))
GROUP BY IDLOCK, DT, LOCKTYPE;
END
This is the result if I don't apply any filters:
And this is the result if I execute the Stored Procedure with the dates filter and the Set filter:
As you can see in the image above, in the CodeSet column the results become 'NULL'.
How can I keep the values ​​of the CodeSet column when executing the SP? (Some of the filters might be null in some case).
This would be an example of the desired result for the first line:
I believe the problem is the fact that PRODUCTATTRIBUTES is an Entity Attribute Value (EAV) table, which are notoriously hard to deal with in queries.
Note that in the second execution of the query, which includes the parameters, the value you entered for #IN_set shows up in the KeySet column because you're not distinguishing between PRODUCTATTRIBUTE values (CodeSet vs KeySet) in your WHERE clause.
In order to simplify the logic in queries like this, I typically join to the EAV table repeatedly, creating sub-queries that only contain the key/value pairs I need for each attribute. So, in this case, I would join to PRODUCTATTRIBUTES twice, once to retrieve the CodeSet values of interest, then again to get the KeySet values. I would also shift the aggregation to those sub-queries, which eliminates ambiguity and also reduces the amount of data being pulled into memory.
This is untested, for lack of tables and data to test against, of course, but an educated guess at the new structure would look something like this.
SELECT IDLOCK, DT, LOCKTYPE,
CS.CodeSet,
KS.KeySet
FROM LOCKREGISTER LR LEFT JOIN
LOCKTYPES T
ON LR.IDLOCKTYPE = T.IDLOCKTYPE LEFT JOIN
( -- Sub-query to get 'CodeSet' values by IDPRODUCT
SELECT
IDPRODUCT,
MAX(VALUE) AS CodeSet
FROM PRODUCTATTRIBUTES
WHERE PRODUCTATTRIBUTE = 'CodeSet'
GROUP BY IDPRODUCT
) AS CS
ON LR.IDPRODUCT = CS.IDPRODUCT LEFT JOIN
( -- Sub-query to get 'KeySet' values by IDPRODUCT
SELECT
IDPRODUCT,
MAX(VALUE) AS KeySet
FROM PRODUCTATTRIBUTES
WHERE PRODUCTATTRIBUTE = 'KeySet'
GROUP BY IDPRODUCT
) AS KS
ON LR.IDPRODUCT = KS.IDPRODUCT
WHERE LR.DT BETWEEN IIF(#IN_DTMIN IS NULL,GETDATE(),#IN_DTMIN) AND IIF(#IN_DTMAX IS NULL,GETDATE(),#IN_DTMAX)
AND (KS.KeySet like ISNULL(#IN_key,'%') OR (KS.KeySet IS NULL AND #IN_key IS NULL))
AND (CS.CodeSet like ISNULL(#IN_set,'%') OR (CS.CodeSet IS NULL AND #IN_set IS NULL))
AND (T.LOCKTYPE like ISNULL(#IN_locktype,'%') OR (T.LOCKTYPE IS NULL AND #IN_locktype IS NULL));
What ever I have understood from your code is, You merely need a ISNULL function. So upgrade your SELECT query to -
SELECT IDLOCK, DT, LOCKTYPE,
ISNULL(MAX(CASE WHEN PRODUCTATTRIBUTE = 'CodeSet' THEN VALUE END), #IN_set) AS CodeSet,
ISNULL(MAX(CASE WHEN PRODUCTATTRIBUTE = 'KeySet' THEN VALUE END), #IN_set) AS KeySet
FROM LOCKREGISTER LR LEFT JOIN
LOCKTYPES T
ON LR.IDLOCKTYPE = T.IDLOCKTYPE LEFT JOIN
PRODUCTATTRIBUTES PA
ON LR.IDPRODUCT = PA.IDPRODUCT AND
PRODUCTATTRIBUTE IN ('CodeSet','KeySet')
WHERE LR.DT BETWEEN IIF(#IN_DTMIN IS NULL,GETDATE(),#IN_DTMIN) AND IIF(#IN_DTMAX IS NULL,GETDATE(),#IN_DTMAX)
AND (PA.VALUE like ISNULL(#IN_key,'%') OR (PA.VALUE IS NULL AND #IN_key IS NULL))
AND (PA.VALUE like ISNULL(#IN_set,'%') OR (PA.VALUE IS NULL AND #IN_set IS NULL))
AND (T.LOCKTYPE like ISNULL(#IN_locktype,'%') OR (T.LOCKTYPE IS NULL AND #IN_locktype IS NULL))
GROUP BY IDLOCK, DT, LOCKTYPE;

How do you handle NULLs in a DATEDIFF comparison?

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)

SQL Text Matching Query Tuning

I'm trying to do some free text search matching, and wondering if I can improve this query (using MSSQL 2008):
#FreeText is a table, where each row is a search word
DECLARE #WordCount = (SELECT COUNT(*) from #FreeText)
SELECT p.ID
FROM Product p
OUTER APPLY
(
SELECT COUNT(ID) as MatchCount
FROM Product pm
INNER JOIN #FreeText ft
ON pm.txt like '%'+ft.text+'%'
WHERE pm.ID = p.ID
AND (SELECT TOP 1 [text] FROM #FreeText) IS NOT NULL
)MC
WHERE MatchCount = #WordCount
So I'm wondering if there is any way to avoid the "FROM Product pm" in the outer apply?
I cannot always INNER JOIN #FreeText because sometimes we don't use free text searching.
Any thoughts or tips would be greatly appreciated, also let me know if I can clarify anything. Thanks in advance.
P.S. I do know that MS SQL has a FREETEXT() search, but I unfortunately cannot use that at the moment.
Here's a query without OUTER APPLY, that returns all results when there are no search critera.
DECLARE #FreeText TABLE
(
[text] varchar(200)
)
INSERT INTO #FreeText SELECT 'a'
INSERT INTO #FreeText SELECT 'c'
-- what, null? No.
DELETE FROM #FreeText WHERE [text] is null
DECLARE #WordCount int
SET #WordCount = (SELECT Count(*) FROM #FreeText)
SELECT p.ID
FROM Product p
LEFT JOIN #FreeText ft
ON p.txt like '%' + ft.text + '%'
WHERE ft.text is not null OR #WordCount = 0
GROUP BY p.ID
HAVING COUNT(*) = #WordCount OR #WordCount = 0
Note: it would be my preference to not use the "freetext" query when there is not any freetext criteria - instead use another query (simpler). If you choose to go that route - go back to an INNER JOIN and drop the OR #WordCount = 0 x2.