case statement in where clause - SQL Server 2008 - sql-server-2008

SELECT
PDADate, T.Merchandizer_ID, T.Merchandizer, Merchandizer_LoginName,
STORE_ID, STORE_CODE, STORE_NAME,
ACCOUNT_ID, ACCOUNT_NAME, Account_Store_Format_Id, Account_Store_Format,
StoreType_Id, StoreType, T.Listid, T.Listname,
T.TimeIn, T.TimeOut, T.PlannedDate, T.Reason, TaskCode, TotalTime
FROM
[dbo].Report_RD_Coverage T
INNER JOIN
#TempLocationH TL ON TL.LocationId=T.Location_Id
WHERE
CONVERT(Date, PDADate) Between (#Start_Date) AND Isnull(#End_Date, #CurrentDate)
AND T.Account_Id IN
(SELECT
CASE WHEN #Account_Id IS NULL THEN T.Account_Id
ELSE (SELECT * FROM UDF_SplitString(#Account_Id,','))
END
)
AND T.StoreType_Id IN
(SELECT
CASE WHEN #StoreType_Id IS NULL THEN T.StoreType_Id
ELSE (SELECT * FROM UDF_SplitString(#StoreType_Id,','))
END
)
AND T.Store_Id IN
(SELECT
CASE WHEN #Store_Id IS NULL THEN T.Store_Id
ELSE (SELECT * FROM UDF_SplitString(#Store_Id,','))
END
)
If #Account_Id, #StoreType_Id and #Store_Id are null the it should select all the ACCOUNT_ID, STORETYPE_ID and STORE_ID otherwise based on parameter value it should filter.
UDF_SplitString is the function to split up comma-separated strings, and its return value is a table like:
- 1
- 2
- 3
I'm getting this error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

CASE must return a scalar value, so try this variation instead:
select PDADate, T.Merchandizer_ID, T.Merchandizer, Merchandizer_LoginName, STORE_ID, STORE_CODE,
STORE_NAME, ACCOUNT_ID, ACCOUNT_NAME, Account_Store_Format_Id, Account_Store_Format,
StoreType_Id, StoreType, T.Listid, T.Listname, T.TimeIn, T.TimeOut, T.PlannedDate,
T.Reason, TaskCode, TotalTime
from [dbo].Report_RD_Coverage T
inner join #TempLocationH TL on TL.LocationId = T.Location_Id
where CONVERT(date, PDADate) between (#Start_Date)
and Isnull(#End_Date, #CurrentDate)
and (
#Account_Id is null
or T.Account_Id in (
select *
from UDF_SplitString(#Account_Id, ',')
)
)
and (
#StoreType_Id is null
or T.StoreType_Id in (
select *
from UDF_SplitString(#StoreType_Id, ',')
)
)
and (
#Store_Id is null
or T.Store_Id in (
select *
from UDF_SplitString(#Store_Id, ',')
) end
)

I tried this and reached very closer but you have to do something from what I found a link.
This is my try. the only thing you need to build is the #udf data.
declare #Store_Id INT;
declare #Account_Id INT;
DECLARE #UDF[9] OF VARCHAR(30);
set #Store_Id = 99 --NULL
set #Account_Id = 15
SET #UDF = '11,12,13,14,15,16'
SELECT #Account_Id AS ACID
WHERE CAST(#Account_Id AS VARCHAR(6)) IN (
CASE WHEN #Store_Id IS NULL THEN CAST(#Account_Id AS VARCHAR(6))
ELSE #UDF END
The link is at
http://www.codeproject.com/Questions/473174/CreateplusArrayplusinplusSqlplusServer
DECLARE #INSTR as VARCHAR(MAX)
SET #INSTR = '2,3,177,'
DECLARE #SEPERATOR as VARCHAR(1)
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
SET #SEPERATOR = ','
CREATE TABLE #tempTab (id int not null)
WHILE PATINDEX('%' + #SEPERATOR + '%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%' + #SEPERATOR + '%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #tempTab (id) VALUES (#VALUE)
END
SELECT * FROM myTable WHERE id IN **(SELECT id FROM #tempTab)**
DROP TABLE #tempTab
you can extract for the sql in bold and the logic how to create temp table and its data and I hope you will get what you want.

> This is the my right solultion........now its working correctly
CREATE TABLE #Store_Id (StoreID varchar(20))
IF #Store_Id != '0'
BEGIN
INSERT INTO #Store_Id
SELECT data FROM UDF_SplitString(#Store_Id,',')
END
ELSE
BEGIN
INSERT INTO #Store_Id
SELECT '0'
END
CREATE TABLE #StoreType_Id (StoreTypeID varchar(20))
IF #StoreType_Id != '0'
BEGIN
INSERT INTO #StoreType_Id
SELECT data FROM UDF_SplitString(#StoreType_Id,',')
END
ELSE
BEGIN
INSERT INTO #StoreType_Id
SELECT '0'
END
CREATE TABLE #Account_Id (AccountID varchar(20))
IF #Account_Id != '0'
BEGIN
INSERT INTO #Account_Id
SELECT data FROM UDF_SplitString(#Account_Id,',')
END
ELSE
BEGIN
INSERT INTO #Account_Id
SELECT '0'
END
INSERT INTO #FinalTable(VisitDate,Merchandizer_Id,Merchandizer,MerchandizerLogin,StoreId,StoreCode,StoreName,AccountId,AccountName,
Account_Store_Format_Id,Account_Store_Format,StoreTypeId ,StoreType ,ListId ,ListName,TimeIn ,TimeOut,PlannedDate ,Reason ,TaskCode,TotalTime)
SELECT Visit_Date,T.Merchandizer_ID,T.Merchandizer,Merchandizer_LoginName,STORE_ID,STORE_CODE,STORE_NAME,ACCOUNT_ID,ACCOUNT_NAME,
Account_Store_Format_Id,Account_Store_Format,StoreType_Id,
StoreType,T.Listid,T.Listname,T.TimeIn,T.TimeOut,T.PlannedDate,T.Reason,TaskCode,TotalTime
FROM [dbo].Report_RD_Coverage T
INNER JOIN #TempLocationH TL ON TL.LocationId=T.Location_Id
INNER JOIN #Store_Id on CONVERT(VARCHAR,t.Store_Id) = CASE WHEN #Store_Id = '0' THEN convert(VARCHAR,t.Store_Id) ELSE StoreID END
INNER JOIN #StoreType_Id on CONVERT(VARCHAR,t.StoreType_Id) = CASE WHEN #StoreType_Id = '0' THEN convert(VARCHAR,t.StoreType_Id) ELSE StoreTypeID END
INNER JOIN #Account_Id on CONVERT(VARCHAR,t.Account_Id) = CASE WHEN #Account_Id = '0' THEN convert(VARCHAR,t.Account_Id) ELSE AccountID END
WHERE CONVERT(Date,PDADate) Between #Start_Date AND #End_Date

Related

Stored procedure is too slow in mysql

I have a routine. But it' s too slow. How can I improve the query?
My records: http://www.sqlfiddle.com/#!9/14cceb/1/0
My query:
CREATE DEFINER = 'root'#'localhost'
PROCEDURE example.ssa()
BEGIN
drop table if exists gps_table;
drop table if exists exam_datas;
CREATE TEMPORARY TABLE gps_table(ID int PRIMARY KEY AUTO_INCREMENT,timei
int,
trun_date_time datetime, tadd_meter int, tin_here int null);
insert into gps_table(timei,trun_date_time,tadd_meter,tin_here) select
imei, run_date_time, add_meter, in_here from example_table;
CREATE TEMPORARY TABLE exam_datas(ID int PRIMARY KEY AUTO_INCREMENT,vimei
int, vbas_run_date_time datetime, vbit_run_date_time datetime, vdifff int);
select tin_here from gps_table limit 1 into #onceki_durum;
select count(id) from gps_table into #kayit_sayisi;
set #i = 1;
set #min_mes = 0;
set #max_mes = 0;
set #frst_id = 0;
set #imei = 0;
set #run_date_time = '0000-00-00 00:00:00';
set #run_date_time2 = '0000-00-00 00:00:00';
myloop: WHILE (#i <= #kayit_sayisi) DO
select tin_here from gps_table where id = #i into #in_here_true;
if (#in_here_true = 1) then
select id,trun_date_time, tadd_meter from gps_table where id = #i into #frst_id,#run_date_time2, #min_mes;
select id from gps_table where id > #frst_id and tin_here =0 order by id asc limit 1 INTO #id;
SET #id = #id-1;
select id, timei, trun_date_time, tadd_meter from gps_table
where id = #id and tin_here =1 limit 1 into #i, #imei, #run_date_time, #max_mes;
if(#i-#frst_id>3) then
set #i:=#i+1;
insert into exam_datas(vimei,vbas_run_date_time,vbit_run_date_time,vdifff) Values (#imei, #run_date_time2, #run_date_time, #max_mes-#min_mes);
SELECT * FROM exam_datas;
SET #asd =1;
elseif 1=1 then
set #i:=#i+1;
End if;
ELSEIF 1=1
THEN SET #i:=#i+1;
End if;
IF (#i = #kayit_sayisi)
THEN set #tamam =1; LEAVE myloop;
END IF;
END WHILE myloop;
select DISTINCT * from exam_datas;
drop table if exists exam_datas;
drop table if exists gps_table;
END
I need: id= 6 first true and id= 11 last_true
firs_trure - last_true = 304-290= 14
id=14 first true and id=18 last_true
firs_true - last_true = 332-324= 8
This routine is too slow.
MySql version is 5.7 and There are 2 milions record in the table.
UPDATE:
Query is here. HERE
Thank you #LukStorms
It's possible to get such results in 1 query.
Thus avoiding a WHILE loop over records.
This example works without using window functions. Just using variables inside the query to calculate a rank. Which is then used to get the minimums and maximums of the groups.
select
imei,
min(run_date_time) as start_dt,
max(run_date_time) as stop_dt,
max(add_meter) - min(add_meter) as diff
from
(
select imei, id, run_date_time, add_meter, in_here,
case
when #prev_imei = imei and #prev_ih = in_here then #rnk
when #rnk := #rnk + 1 then #rnk
end as rnk,
#prev_imei := imei as prev_imei,
#prev_ih := in_here as prev_ih
from example_table t
cross join (select #rnk := 0, #prev_ih := null, #prev_imei := null) vars
order by imei, id, run_date_time
) q
where in_here = 1
group by imei, rnk
having count(*) > 4
order by imei, min(id);
In the procedure such query can be used to fill that final temporary table.
A test on db<>fiddle here

Update from select with join on same table

UPDATE inventory_audit_scans
SET inventory_audit_scans.inventory_item_id =
(SELECT id FROM (SELECT inventory_item.id as id
FROM inventory_item
RIGHT JOIN products
ON products.id = inventory_item.product_id
LEFT JOIN barcodes
ON barcodes.product_id = inventory_item.product_id
LEFT JOIN products_skus
ON products_skus.product_id = inventory_item.product_id
LEFT OUTER JOIN inventory_audit_scans ias
ON ias.inventory_item_id = inventory_item.id
AND ias.audit_report_id =
inventory_audit_scans.audit_report_id
WHERE ( products.sku LIKE inventory_audit_scans.text
OR barcodes.barcode LIKE inventory_audit_scans.text
OR products_skus.sku LIKE inventory_audit_scans.text
OR inventory_item.serial LIKE inventory_audit_scans.text )
AND inventory_item.store_id = in_store_id
AND inventory_audit_scans.inventory_item_id IS NULL
AND ( ( ( products.category_id =
inventory_audit_scans.category_id )
AND ( inventory_audit_scans.category_id IS NOT NULL ) )
OR ( ( ( products.category_id = 1 )
OR ( products.category_id = 2 ) )
AND inventory_audit_scans.category_id IS NULL ) )
ORDER BY inventory_item.id ASC
LIMIT 1) TmpTbl),
inventory_audit_scans.status_id = IF(inventory_audit_scans.inventory_item_id , 1 , 2) ,
updated = Now()
WHERE inventory_audit_scans.audit_report_id = 1
In the inner select where I get the error message
Error Code: 1054. Unknown column 'inventory_audit_scans.text' in 'where clause'
Is there any way i can access the actual row of the update statement in the enclosed SELECT to determine which would be the next available free ID which i not already used?
Before it tried it via the following method which takes approximately 3 minutes to "audit" 1600 records:
CREATE PROCEDURE `compareaudit`(IN audit_id INT(11))
BEGIN
DECLARE bDone INT DEFAULT 0;
DECLARE current_record_id INT(11) DEFAULT NULL;
DECLARE current_category_id INT(11) DEFAULT NULL;
DECLARE current_text VARCHAR(50) DEFAULT NULL;
DECLARE current_store_id INT(11) DEFAULT NULL;
DECLARE current_audit_id INT(11) DEFAULT NULL;
DECLARE res_inventory_item_id INT(11) DEFAULT NULL;
DECLARE res_serial VARCHAR(50) DEFAULT NULL;
DECLARE res_sku VARCHAR(20) DEFAULT NULL;
DECLARE res_barcode VARCHAR(30) DEFAULT NULL;
DECLARE res_product_skus VARCHAR(100) DEFAULT NULL;
DECLARE cur CURSOR FOR
SELECT `id`, `category_id`, `text`, `store_id`, `audit_report_id` FROM inventory_audit_scans WHERE audit_report_id = audit_id AND item_status IS NULL ORDER BY id ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET bDone = 1;
OPEN cur;
myloop:loop
FETCH cur INTO current_record_id, current_category_id, current_text, current_store_id, current_audit_id;
IF bDone = 1 THEN
leave myloop;
END IF;
call find_item(current_category_id, current_text, current_store_id, current_audit_id, res_inventory_item_id, res_serial, res_sku, res_barcode, res_product_skus);
IF (res_inventory_item_id IS NOT NULL) THEN
UPDATE inventory_audit_scans SET inventory_item_id = res_inventory_item_id, item_status = 1, updated = NOW() WHERE id = current_record_id;
END IF;
IF (res_inventory_item_id IS NULL) THEN
UPDATE inventory_audit_scans SET inventory_item_id = NULL, item_status = 2, updated = NOW() WHERE id = current_record_id;
END IF;
end loop myloop;
select true;
END
And:
CREATE PROCEDURE `find_item`(
IN in_category_id INT(11),
IN in_text VARCHAR(50),
IN in_store_id INT(11),
IN in_audit_id INT(11),
OUT my_inventory_item_id INT(11),
OUT my_serial VARCHAR(50),
OUT my_sku VARCHAR(20),
OUT my_barcode VARCHAR(30),
OUT my_product_skus VARCHAR(100))
BEGIN
SELECT inventory_item.id AS inventory_item_id,
inventory_item.`serial` AS `serial`,
products.sku AS SKU,
barcodes.barcode AS barcode,
products_skus.sku AS upc
INTO my_inventory_item_id,
my_serial,
my_sku,
my_barcode,
my_product_skus
FROM inventory_item
RIGHT JOIN products
ON products.id = inventory_item.product_id
LEFT JOIN barcodes
ON barcodes.product_id = inventory_item.product_id
LEFT JOIN products_skus
ON products_skus.product_id = inventory_item.product_id
LEFT OUTER JOIN inventory_audit_scans
ON inventory_audit_scans.inventory_item_id = inventory_item.id
AND inventory_audit_scans.audit_report_id = in_audit_id
WHERE (
products.sku LIKE in_text
OR barcodes.barcode LIKE in_text
OR products_skus.sku LIKE in_text
OR inventory_item.serial LIKE in_text )
AND inventory_item.store_id = in_store_id
AND inventory_audit_scans.inventory_item_id IS NULL
AND (
((products.category_id = in_category_id) AND (in_category_id IS NOT NULL))
OR
(((products.category_id = 1) OR (products.category_id = 2)) AND in_category_id IS NULL)
)
ORDER BY inventory_item.id ASC
LIMIT 1;
END

What can I use instead of #Temp table in sql function

Here is my sql query.
CREATE FUNCTION UF_GetOrderProducts
(
#OrderId int
)
RETURNS VARCHAR(500)
AS
BEGIN
SELECT Identity(int,1,1) ID, ProductId INTO #Temp FROM OrderProduct WHERE OrderId = #OrderId
Declare #Id int,
#Count int,
#LoopCount int,
#ProductList VARCHAR(500),
#ProductListTemp VARCHAR(500)
SET #Count = (Select Count(*) From #Temp)
SET #LoopCount = 1
SET #ProductList = ''
WHILE #LoopCount <= #Count
BEGIN
SET #ProductListTemp =( SELECT Name FROM Product WHERE ProductId =(Select ProductId from #Temp Where ID = #LoopCount))
SET #ProductList +=#ProductListTemp + '<br/>'
Set #LoopCount=#LoopCount + 1
END
DROP TABLE #Temp
RETURN #ProductList
END
GO
I have to loop in #Temp Table. Do you have any other suggestions?
Instead of temp table you can use a table variable.
declare #Temp TABLE (ID int identity, ProductId int)
insert into #Temp(ProductId)
select ProductId
from OrderProduct
where OrderId = #OrderId
But you could rewrite your function without a loop.
Something like this should do what you want.
create function IF_GetOrderProducts
(
#OrderId int
)
returns varchar(500)
as
begin
return
(
select Name+'<br/>'
from Product as P
inner join OrderProduct as OP
on P.ProductId = OP.ProductId
where OP.OrderId = #OrderId
for xml path(''), type
).value('.', 'varchar(500)')
end

Create LOG for tables in SQL Server

I have a table Sample and another SampleLog
With these structure I want to write codes to log. You can see my codes after structures of tables
CREATE TABLE [dbo].[Sample](
[ID] [int] NULL,
[Name] [varchar](10) NULL
)
CREATE TABLE [dbo].[SampleLog](
[ID] [int] NULL,
[Name] [varchar](10) NULL,
[Date] [datetime] NULL,
[UserName] [varchar](100) NULL,
[Type] [char](1) NULL
)
I have written this code but it doesn't work for Delete and Update .
CREATE TRIGGER SampleTrigger ON Sample
AFTER INSERT, UPDATE, DELETE
AS
DECLARE
#ID int ,
#Name varchar(10),
#Date datetime,
#UserName VARCHAR(128) ,
#Type CHAR(1) ,
#sql nvarchar(500)
SELECT
#UserName = SYSTEM_USER ,
#Date = CONVERT(VARCHAR(8), GETDATE(), 112)
+ ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
IF EXISTS (SELECT * FROM inserted)
BEGIN
IF EXISTS (SELECT * FROM deleted)
BEGIN
SELECT #Type = 'U'
select #ID = ID from deleted
select #Name = Name from deleted
END
ELSE
BEGIN
SELECT #Type = 'I'
select #ID = ID from inserted
select #Name = Name from inserted
END
END
ELSE
BEGIN
SELECT #Type = 'D'
select #ID = ID from deleted
select #Name = Name from deleted
END
insert into SampleLog(ID, Name, Date, UserName, Type)
values(#ID, #Name, #Date, #UserName, #Type)
SQL Server gives me this error
The row values updateed or deleted either do not make the row unique or they alter multiple rows(2 rows)
You've coded for single row updates and deletes. Think sets!
CREATE TRIGGER SampleTrigger ON Sample after INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
insert into SampleLog
(ID,Name,Date,UserName,Type)
SELECT
D.ID, D.NAME, GETDATE(), SYSTEM_USER,
CASE WHEN I.ID IS NULL THEN 'D' ELSE 'U' END
FROM
DELETED D
LEFT JOIN
INSERTED I ON D.ID = I.ID
UNION ALL
SELECT
I.ID, I.NAME, GETDATE(), SYSTEM_USER, 'I'
FROM
INSERTED I
LEFT JOIN
DELETED D ON D.ID = I.ID
WHERE
D.ID IS NULL
GO

Better Min/Max Function for Dates in T-SQL

I created the following function to determine the MAX date between two dates. It takes roughly from 00.030 to 00.050 seconds to run one of the SELECT statements in the function's comment.
Is there a better performing and cleaner way?
/* Returns the greater of two dates.
SELECT dbo.fnMaxDate(NULL , NULL)
SELECT dbo.fnMaxDate('1/1/2011', NULL)
SELECT dbo.fnMaxDate(NULL , '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')
*/
ALTER FUNCTION dbo.fnMaxDate
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS datetime
AS
BEGIN
DECLARE #Result DATETIME
IF #Date1 IS NULL AND #Date2 IS NULL
SET #Result = NULL;
ELSE IF #Date1 IS NULL
SET #Result = #Date2
ELSE IF #Date2 IS NULL
SET #Result = #Date1
ELSE
IF #Date1 >= #Date2
SET #Result = #Date1
ELSE
SET #Result = #Date2
RETURN #Result
END
ALTER FUNCTION dbo.fnMaxDate
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS datetime
AS
BEGIN
RETURN
CASE
WHEN ISNULL(#Date1, #Date2) > ISNULL(#Date2, #Date1)
THEN ISNULL(#Date1, #Date2)
ELSE ISNULL(#Date2, #Date1)
END
END
I found that a case structure runs about three times faster than a function call.
declare #d table(d1 date, d2 date);
insert into #d values(null,null)
, ('2/19/2012',null)
, (null,'2/19/2012')
, ('2/1/2012','2/15/2012')
, ('2/1/2012','1/15/2012');
declare #md date
, #i int=1
, #ts datetime2
, #ms1 int
, #ms2 int;
-- function
select #ts=GETDATE();
while #i<10000 begin select #md=dbo.fnMaxDate(d1,d2) from #d; set #i+=1; end
select #ms1=DATEDIFF(ms,#ts,GETDATE());
-- recommended case structure
set #i=1;
select #ts=GETDATE();
while #i<10000 begin select #md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from #d; set #i+=1; end
select #ms2=DATEDIFF(ms,#ts,GETDATE());
select [Function Run Time (ms)]=#ms1, [Case Run Time (ms)]=#ms2
go
Result:
Function Run Time (ms) Case Run Time (ms)
---------------------- ------------------
940 296
/* Returns the greater of two dates.
SELECT dbo.fnMaxDate(NULL , NULL)
SELECT dbo.fnMaxDate('1/1/2011', NULL)
SELECT dbo.fnMaxDate(NULL , '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')
*/
ALTER FUNCTION dbo.fnMaxDate
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS datetime
AS
BEGIN
DECLARE #Result datetime
SELECT TOP 1 #Result = T.DateValue
FROM
(
SELECT #Date1 DateValue
UNION ALL
SELECT #Date2 DateValue
) T
ORDER BY
T.DateValue DESC
RETURN #Result
END
I know this is an old question, and already answered, but seeing as there was already a test script I couldn't resist having a go :)
I've created a scalar function that outperforms all the other function tests here, and a table valued function that is even faster than the scalar UDF.
Comparable Result (to AMissico's test)
TVF = 0.022253313291
SVF = 0.04627526226
The Functions
/* Returns the greater of two dates.
SELECT dbo.fnMaxDate(NULL , NULL)
SELECT dbo.fnMaxDate('1/1/2011', NULL)
SELECT dbo.fnMaxDate(NULL , '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')
*/
CREATE FUNCTION dbo.svfMaxDate
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS datetime
AS
BEGIN
RETURN coalesce(dateadd(dd, ((datediff(dd, 0, #Date1) + datediff(dd, 0, #Date2)) + abs(datediff(dd, 0, #Date1) - datediff(dd, 0, #Date2))) / 2, 0), #Date1, #Date2)
END
GO
CREATE FUNCTION dbo.tvfMaxDate
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN SELECT [MaxDate] = coalesce(dateadd(dd, ((datediff(dd, 0, #Date1) + datediff(dd, 0, #Date2)) + abs(datediff(dd, 0, #Date1) - datediff(dd, 0, #Date2))) / 2, 0), #Date1, #Date2)
;
GO
I also used AMissico's simple test script to test this, but only bothered to test my version of the functions and the CASE statement to be used as a reference point/baseline:
Results of Test
Case(ms) svfMaxDate tvfMaxDate
0.01343000 0.03907000 0.01837000
Normalise Test Results to AMissico's Baseline
Baseline Case(ms) / This Test Case(ms) = Normalisation Factor
0.01616 / 0.01334000 = 1.21139430
svfMaxDate * Normalisation Factor= Comparable Result
0.03820000 * 1.21139430 = 0.04627526226
tvfMaxDate * Normalisation Factor= Comparable Result
0.01837 * 1.21139430 = 0.022253313291
Test Script
declare #d table(d1 date, d2 date);
insert into #d values(null,null)
, ('2/19/2012',null)
, (null,'2/19/2012')
, ('2/1/2012','2/15/2012')
, ('2/1/2012','1/15/2012');
declare #md date
, #i int=1
, #ts datetime2
, #ms0 int
, #ms1 int
, #ms2 int
;
-- case structure
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from #d; set #i+=1; end
select #ms0=DATEDIFF(ms,#ts,GETDATE());
-- svfMaxDate, Arithmetic
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.svfMaxDate(d1,d2) from #d; set #i+=1; end
select #ms1=DATEDIFF(ms,#ts,GETDATE());
-- tvfMaxDate, Arithmetic in TVF with CROSS APPLY
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md = tf.MaxDate from #d cross apply dbo.tvfMaxDate(d1,d2) as tf; set #i+=1; end
select #ms2=DATEDIFF(ms,#ts,GETDATE());
select [Case(ms)]=#ms0/100000.0, fnMaxDate=#ms1/100000.0, tvfMaxDate=#ms2/100000.0
go
The TVF credit goes to Jeff Moden, the following link was used as a reference for building it: How to Make Scalar UDFs Run Faster (SQL Spackle)
Based on my simple performance testing, I am going with a slightly modified version of my original function (see below).
IS NULL CASE IS NOT NULL UNION
Case(ms) Empty Func fnMaxDate1 fnMaxDate2 fnMaxDate3 fnMaxDate4
0.01616 0.0446 0.0518 0.04934 0.05036 0.06177
The fastest function method is the CASE statement, but only by approx. 0.003ms The slowest function is the one that uses SELECT UNION by "Pittsburgh DBA". I changed the order of my function to test for the most common result first, which would test both arguments as IS NOT NULL. This change in logic place the performance on par with the CASE function.
Therefore, I am giving up the 0.001ms performance for clarity of the IS NOT NULL function (see below).
Using the following script:
declare #d table(d1 date, d2 date);
insert into #d values(null,null)
, ('2/19/2012',null)
, (null,'2/19/2012')
, ('2/1/2012','2/15/2012')
, ('2/1/2012','1/15/2012');
declare #md date
, #i int=1
, #ts datetime2
, #ms0 int
, #ms1 int
, #ms3 int
, #ms2 int
, #ms4 int
, #ms5 int
;
-- case structure
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=case when ISNULL(d1,d2)<ISNULL(d2,d1) then ISNULL(d2,d1) else ISNULL(d1,d2) end from #d; set #i+=1; end
select #ms0=DATEDIFF(ms,#ts,GETDATE());
-- fnMaxDate1, IF IS NULL
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.fnMaxDate1(d1,d2) from #d; set #i+=1; end
select #ms1=DATEDIFF(ms,#ts,GETDATE());
-- fnMaxDate2, CASE
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.fnMaxDate2(d1,d2) from #d; set #i+=1; end
select #ms2=DATEDIFF(ms,#ts,GETDATE());
-- fnMaxDate3, IF IS NOT NULL
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.fnMaxDate3(d1,d2) from #d; set #i+=1; end
select #ms3=DATEDIFF(ms,#ts,GETDATE());
-- fnMaxDate4, SELECT UNION
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.fnMaxDate4(d1,d2) from #d; set #i+=1; end
select #ms4=DATEDIFF(ms,#ts,GETDATE());
-- fnMaxDate5, empty function call
set #i=1;
select #ts=GETDATE();
while #i<100000 begin select #md=dbo.fnMaxDate5(d1,d2) from #d; set #i+=1; end
select #ms5=DATEDIFF(ms,#ts,GETDATE());
select [Case(ms)]=#ms0/100000.0, [fnMaxDate5 (empty function call)]=#ms5/100000.0, fnMaxDate1=#ms1/100000.0, fnMaxDate2=#ms2/100000.0, fnMaxDate3 = #ms3/100000.0, fnMaxDate4=#ms4/100000.0
go
Lastly, here is the final version of the function:
/* Returns the greater of two dates.
SELECT dbo.fnMaxDate(NULL , NULL)
SELECT dbo.fnMaxDate('1/1/2011', NULL)
SELECT dbo.fnMaxDate(NULL , '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/2/2011', '1/1/2011')
SELECT dbo.fnMaxDate('1/1/2011', '1/2/2011')
*/
ALTER FUNCTION [dbo].[fnMaxDate]
(
#Date1 DATETIME,
#Date2 DATETIME
)
RETURNS DATETIME
AS
BEGIN
DECLARE #Result DATETIME
IF #Date1 IS NOT NULL AND #Date2 IS NOT NULL
IF #Date1 >= #Date2
SET #Result = #Date1
ELSE
SET #Result = #Date2
ELSE IF #Date1 IS NULL
SET #Result = #Date2
ELSE IF #Date2 IS NULL
SET #Result = #Date1
RETURN #Result
END