How to optimize MSSQL CASE WHEN queries - sql

Here's a sample of my code:
SET #variable_out =
'Report: '
+ CASE WHEN (SELECT name FROM person WITH(NOLOCK) WHERE person_id = #person_id) != ''
THEN 'Name: ' + (SELECT name FROM person WITH(NOLOCK) WHERE person_id = #person_id) + CHAR(13)+CHAR(10)
ELSE 'Name: not found' + CHAR(13)+CHAR(10)
END
+ CASE WHEN (SELECT home_phone FROM person WITH(NOLOCK) WHERE person_id = #person_id) != ''
THEN 'Phone #: ' + (SELECT home_phone FROM person WITH(NOLOCK) WHERE person_id = #person_id) + CHAR(13)+CHAR(10)
ELSE 'Phone #: not found' + CHAR(13)+CHAR(10)
END
etc...
As you can see, I am redundantly performing two selects for each CASE WHENE... of the variable that I am constructing, and I would love to collapse this down to only one select for each line.
The only solution I know of would be to create a unique variable for CASE WHEN..., run all of the selects before hand, and then if the variables aren't empty, concat them into #variable_out.
Is there a more clever way to accomplish this?

DECLARE #name the_same_datatype_as_name_field_from_person_table --Ex. VARCHAR(100)
,#home_phone the_same_datatype_as_homephone_field_from_person_table; --Ex. VARCHAR(15)
SELECT #name = NULLIF(p.name,''), #home_phone = NULLIF(p.home_phone,'')
FROM person p --WITH(NOLOCK)
WHERE p.person_id = #person_id;
SET #variable_out =
'Report: '
+ ISNULL('Name: ' + #name, 'Name: not found')
+ CHAR(13)+CHAR(10)
+ ISNULL('Phone #: ' + #home_phone, 'Phone #: not found')
+ CHAR(13)+CHAR(10);
Note:
or, you can use for #name & #home_phone variables the same data type like as #variable_out variable (ex. VARCHAR).
NOLOCK pros & cons.

Related

Disregard certain lines if false

I wrote this query and it is designed to send an email to a customer with info in a database. I would like it to do a check for any values that return as 0 and not send them in the #message. I imagine I'll need an if statement but I haven't been able to figure out how to make it work. Any help is appreciated.
Query:
select #AssignedCount = (select COUNT(*)
FROM event
where status_name = 'ASSIGNED' AND primary_unitid = NULL
OR status_name = 'ASSIGNED' AND primary_unitid = '')
select #UnitResource = (select COUNT (*)
from unit_resources
where unit_seqnum NOT IN (select unit_seqnum from unit))
select #UnitEmployee = (select COUNT (*)
from unit_employees
where unit_seqnum NOT IN (select unit_seqnum from unit))
select #LoggedOff = (select COUNT(*)
from unit
where status_name = 'LOGGED_OFF')
select #Duplicates = (SELECT ISNULL(
(select COUNT(*)
from unit
group by unitid
having COUNT(*) > 1), 0))
select #message =
'Status Report' +
' Events in assigned status with no primary unit: '
+ REPLACE((str(#AssignedCount)),' ','') +
' Un-linked unit resource table rows: '
+ REPLACE((str(#UnitResource)),' ','') +
' Un-linked Unit resource table rows: '
+ REPLACE((str(#UnitEmployee)),' ','') +
' Units with a status of Logged Off: '
+ REPLACE((str(#LoggedOff)),' ','') +
' Duplicate Units: '
+ REPLACE((str(#Duplicates)),' ','')`
You will have to use if statements to dynamically build the string:
-- setting to blank; otherwise, the string would be null
set #message = ''
-- dynamically building #message with if statements
if #AssignedCount > 0
set #message = 'Status Report' +
' Events in assigned status with no primary unit: '
+ REPLACE((str(#AssignedCount)),' ','')
if #UnitResource > 0
set #message = #message + ' Un-linked unit resource table rows: '
+ REPLACE((str(#UnitResource)),' ','')
if #UnitEmployee > 0
set #message = #message + ' Un-linked Unit resource table rows: '
+ REPLACE((str(#UnitEmployee)),' ','')
if #LoggedOff > 0
set #message = #message + ' Units with a status of Logged Off: '
+ REPLACE((str(#LoggedOff)),' ','')
if #Duplicates > 0
set #message = #message + ' Duplicate Units: '
+ REPLACE((str(#Duplicates)),' ','')
-- removing leading space from #message if AssignedCount is 0
set #message = ltrim(#message)
Personally, actually, rather than a bunch of IF statements, I'd likely go for something like this and do it all in one go:
SET #message = CONCAT('Status Report',
(SELECT CONCAT(' Events in assigned status with no primary unit: ',COUNT(*))
FROM event
WHERE status_name = 'ASSIGNED'
AND primary_unitid = NULL
OR status_name = 'ASSIGNED'
AND primary_unitid = ''
HAVING COUNT(*) > 0),
((SELECT CONCAT(' Un-linked unit resource table rows: ',COUNT (*))
FROM unit_resources ur
WHERE NOT EXISTS (SELECT 1 --I changed this from a NOT IN to an EXISTS, as NOT in have behave oddly with NULLs
FROM unit u
WHERE u.unit_seqnum = ur.seqnum)
HAVING COUNT(*) > 0))); --You get the idea now
I haven't done the whole lot here, however, the HAVING COUNT(*) > 0 means that no rows (including 0) will be returned when there are no relevant rows. This means that information won't be concatenated to the value of #message.

some weird signs appearing on SQL SERVER Query output

i have written a SELECT Query on SQL SERVER 2014 . I have got the desired output . but an apostrophe symbol(') appearing on 'TaskAction' field data(at the end of each data). here it is my script:
SELECT
WOtask.PK,
WOPK,
TaskNo,
TaskAction =
CASE
WHEN WOTask.AssetPK IS NOT NULL THEN '<b>' + Asset.AssetName + ' [' + Asset.AssetID + ']</b> ' + CASE
WHEN Asset.Vicinity IS NOT NULL AND
Asset.Vicinity <> '''' THEN RTRIM(Asset.Vicinity) + ': '
ELSE ''''
END + WOtask.TaskAction + CASE
WHEN CONVERT(varchar, ValueLow) IS NOT NULL AND
CONVERT(varchar, ValueHi) IS NOT NULL AND
Spec = 1 THEN ' (Range: '' + CONVERT(VARCHAR,CONVERT(FLOAT,ISNULL(ValueLow,0))) + '' - '' + CONVERT(VARCHAR,CONVERT(FLOAT,ISNULL(ValueHi,0))) + )'
ELSE ''''
END
ELSE WOtask.TaskAction + CASE
WHEN CONVERT(varchar, ValueLow) IS NOT NULL AND
CONVERT(varchar, ValueHi) IS NOT NULL AND
Spec = 1 THEN ' (Range: '' + CONVERT(VARCHAR,CONVERT(FLOAT,ISNULL(ValueLow,0))) + '' - '' + CONVERT(VARCHAR,CONVERT(FLOAT,ISNULL(ValueHi,0))) + )'
ELSE ''''
END
END,
Rate,
Measurement,
Initials,
Fail,
Complete,
Header,
LineStyle,
WOtask.Comments,
WOtask.NotApplicable,
WOTask.Photo1,
WOTask.Photo2
FROM WOtask WITH (NOLOCK)
LEFT OUTER JOIN Asset WITH (NOLOCK)
ON Asset.AssetPK = WOTask.AssetPK
LEFT OUTER JOIN AssetSpecification
ON AssetSpecification.PK = WOTask.AssetSpecificationPK
WHERE (WOPK IN (SELECT
WOPK
FROM WO WITH (NOLOCK)
LEFT OUTER JOIN Asset WITH (NOLOCK)
ON Asset.AssetPK = WO.AssetPK
LEFT OUTER JOIN AssetHierarchy WITH (NOLOCK)
ON AssetHierarchy.AssetPK = WO.AssetPK
WHERE WO.WOPK = 10109)
)
ORDER BY WOPK, TaskNo
now please check my output and error
please help to solve this issue. Thanks in Advance.
As noted in comments, use ELSE '' instead of ELSE ''''. The reason is that within a pair of single quotes, then '' tells SQL to escape a single quote into your output.
For instance, to show the output User's, you would need SELECT 'User''s'.

Cannot resolve collation conflict for column 3 in SELECT statement in SQL Server 2014

I have a table 'CustomerAccount' with the following fields:
AccountNumber
AmountDue
CompanyName
Cust_FirstName
Cust_LastName
Address
I am trying to pull a simple report but I get an error message. I can't seem to figure out what is wrong.
My Code:
SELECT AccountNumber
,AmountDue
--,ISNULL(Cust_LastName, '') + ', ' + ISNULL(Cust_FirstName, '') CustomerName
,CASE WHEN ISNULL(CompanyName, '') = ''
THEN Cust_LastName + ', ' + Cust_FirstName
ELSE CompanyName
END CustomerName
-- Above: This is the line where it gives an error.
-- If there is no Company Name then give the Last and First name of the customer.
,CASE WHEN ISNULL(CompanyName, '') = ''
THEN ''
ELSE Cust_FirstName + ' ' + Cust_LastName
END Attn_To
-- Above: If there IS a Company Name then give the First and Last name of the customer.
,[Address]
FROM CustomerAccount
The Error Message:
Cannot resolve collation conflict for column 3 in SELECT statement.
What I have tried:
,CASE WHEN ISNULL(CompanyName, '') = ''
THEN '' --Cust_LastName + ', ' + Cust_FirstName
ELSE CompanyName
END CustomerName
-- This works but only gives a CompanyName when there is one.
-- But does not give me the customer name when there is no CompanyName
If I try only ,ISNULL(Cust_LastName, '') + ', ' + ISNULL(Cust_FirstName, '') instead of the CASE Statement then it works. I get the LastName, FirstName of the customer.
Here is how I fixed it:
,CASE WHEN ISNULL(CompanyName, '') = ''
THEN Cust_LastName + ', ' + Cust_FirstName
ELSE CompanyName
END COLLATE Latin1_General_CI_AI CustomerName
Thanks to #Aaron Bertrand , #Paul and #DT.

SQL Server : OR statement within CASE statement

I have a stored procedure that is querying some employee records based on what the user sends over.
On the UI, the user will enter multiple data points such as email addresses, User ID's, or Employee Names. This stored procedure checks what datatype they are providing and then searches that field in the database for the records.
Input to stored procedure:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<data>
<dataPoints>
<dataPoint>
<order>0</order>
<value>Jim Bob</value>
</dataPoint>
<dataPoint>
<order>1</order>
<value>Sally Jones</value>
</dataPoint>
</dataPoints>
</data>
</root>
Query:
#dataType VARCHAR (20), #data XML
AS
BEGIN
SET NOCOUNT ON;
BEGIN
-- Create a temp table
DECLARE #dataSet TABLE (data VARCHAR(100), [order] INT);
INSERT INTO #dataSet( data , [order] )
SELECT
ParamValues.x1.value('value[1]', 'VARCHAR(100)') ,
ParamValues.x1.value('order[1]', 'INT')
FROM
#data.nodes('/root/data/dataPoints/dataPoint') AS ParamValues(x1)
-- Search Employees
SELECT
ec.FirstName, ec.PreferredName, ec.LastName,
ec.NTID, ec.QID,
ec.DepartmentName, ec.SegmentName,
ec.CenterName, ec.RoleName, ec.MarketName,
ec.IncentivePlanName,
ec.CostCenterID,
ec.SupFirstName, ec.SupPreferredName, ec.SupLastName,
ec.SiloName,
ec.AreaName,
ec.PersonnelID,
d.[order]
FROM
Resources.emp.EmployeeComplete AS ec
INNER JOIN
#dataset AS d ON d.data = CASE
WHEN #dataType = 'NTID' THEN ec.ntid
WHEN #dataType = 'QID' THEN ec.QID
WHEN #dataType = 'Emp ID' THEN ec.EmpID
WHEN #dataType = 'Email Address' THEN ec.Email
WHEN #dataType = 'Personnel ID' OR #dataType = 'Sap ID' THEN ec.PersonnelID
--WHEN #dataType = 'Name' THEN (
-- (ec.FirstName + ' ' + ec.LastName)
-- OR (ec.PreferredName + ' ' + ec.LastName)
-- OR (ec.LastName + ', ' + ec.FirstName)
-- OR (ec.LastName + ', ' + ec.PreferredName
-- )
END
FOR XML PATH ('employees'), ELEMENTS, TYPE, ROOT ('root');
In short, I take the multiple data points being searched and throw them into an XML string to pass to the stored procedure. Once they arrive, I put them into a temp table so that I can join that with my main employee records.
The problem / question:
You will see I have some commented out code in my example and this is where my issue is. There are three name fields in my database. First Name, Preferred Name, Last Name.
I essentially need to test what the user provided and find employees based on the combination they entered them. All the user selects in the UI is that they are providing a name but not the format that its in.
For this reason, I need to check to see if I can find records in a couple of different formats.
Issue in this case is that I can't join my dataset using OR conditions in the CASE statement.
If #dataType = 'Name', I need to be able to join my temp table on a couple of the different combination possibilities.
The one thing we do make them aware of is that they can't mix and match. Meaning they cant do a FirstName LastName with a LastName FirstName search.
I had trouble explaining this so please let me know if I need to somehow clarify.
Push the equations in the CASE. If they are true let the THEN return 1. Check if the CASE returned 1. If and only if it did, you've found a match. In the conditions of WHEN you can use Boolean operators. So you can build your OR (or use IN as i did below) there.
...
CASE
WHEN #dataType = 'NTID' AND d.data = ec.ntid THEN 1
WHEN #dataType = 'QID' AND d.data = ec.QID THEN 1
...
WHEN (#dataType = 'Personnel ID' OR #dataType = 'Sap ID') AND d.data = ec.PersonnelID THEN 1
WHEN #dataType = 'Name' AND (d.data IN ('' + ec.FirstName + ' ' + ec.LastName,
'' + ec.PreferredName + ' ' + ec.LastName,
'' + ec.LastName + ', ' + ec.FirstName,
'' + ec.LastName + ', ' + ec.PreferredName) THEN 1
END = 1
...
What about just putting ORs in your join?
FROM #dataset AS d
INNER JOIN Resources.emp.EmployeeComplete AS ec
ON (#dataType = 'NTID' AND ec.ntid = d.data)
OR (#dataType = 'QID' AND ec.QID = d.data)
OR (#dataType = 'Emp ID' AND ec.EmpID = d.data)
OR (#dataType = 'Email Address' AND ec.Email = d.data)
OR ((#dataType = 'Personnel ID' OR #dataType = 'Sap ID') AND ec.PersonnelID = d.data)
OR (#dataType = 'Name' AND (ec.FirstName + ' ' + ec.LastName) = d.data)
OR (#dataType = 'Name' AND (ec.PreferredName + ' ' + ec.LastName) = d.data)
OR (#dataType = 'Name' AND (ec.LastName + ', ' + ec.FirstName) = d.data)
OR (#dataType = 'Name' AND (ec.LastName + ', ' + ec.PreferredName) = d.data)
I'm not sure if SQL is smart enough to use the proper indexes on those fields if you have them, probably especially not when combining columns. You would create indexed views for those combined names, or computed columns you could index on. It may be better to spread them out into separate queries in if/thens so SQL could optimize each query based on the field you are joining on and just execute the one query.

Combine First, Middle Initial, Last name and Suffix in T-SQL (No extra spaces)

I'm trying not to reinvent the wheel here...I have these four fields:
[tbl_Contacts].[FirstName],
[tbl_Contacts].[MiddleInitial],
[tbl_Contacts].[LastName],
[tbl_Contacts].[Suffix]
And I want to create a FullName field in a view, but I can't have extra spaces if fields are blank...
So I can't do FirstName + ' ' + MiddleInitial + ' ' + LastName + ' ' + Suffix... Because if there is no middle initial or suffix I'd have 2 extra spaces in the field. I think I need a Case statement, but I thought someone would have a handy method for this...Also, the middleinitial and suffix may be null.
Assuming that all columns could be nullable, you can do something like:
RTrim(Coalesce(FirstName + ' ','')
+ Coalesce(MiddleInitial + ' ', '')
+ Coalesce(LastName + ' ', '')
+ Coalesce(Suffix, ''))
This relies on the fact that adding to a NULL value yields a NULL.
Whichever options you choose, here's something to think about: this will be a rather involved and thus time consuming option, especially if you have it in a view which gets evaluated each and every time for each and every row in question.
If you need this frequently, I would recommend you add this to your base table as a persisted, computed field - something like:
ALTER TABLE dbo.tbl_Contacts
ADD FullName AS (insert the statement of your choice here) PERSISTED
When it's persisted, it becomes part of the underlying table, and it's stored and kept up to date by SQL Server. When you query it, you get back the current value without incurring the cost of having to concatenate together the fields and determine which to use and which to ignore...
Just something to think about - something that too many DBA's and database devs tend to ignore and/or not know about....
You may want to pass the FirstName + ' ' + MiddleInitial + ' ' + LastName + ' ' + Suffix concatenation through the REPLACE() function in order to substitute duplicate spaces into a single space.
REPLACE(FirstName + ' ' + MiddleInitial + ' ' + LastName + ' ' + Suffix, ' ', ' ')
-- -- -
EDIT:
Just noticed that some of your fields may be NULL, and therefore the above would not work in that case, as the whole string would become NULL. In this case, you can use the COALESCE() method as suggested by Thomas, but still wrapped it in a REPLACE():
REPLACE(RTRIM(COALESCE(FirstName + ' ', '') +
COALESCE(MiddleInitial + ' ', '') +
COALESCE(LastName + ' ', '') +
COALESCE(Suffix, '')), ' ', ' ')
Test:
SELECT REPLACE(RTRIM(COALESCE('John' + ' ', '') +
COALESCE('' + ' ', '') +
COALESCE('Doe' + ' ', '') +
COALESCE(NULL, '')), ' ', ' ')
-- Returns: John Doe
I had to join Firstname, Middlename, and Lastname. my challenge was to handle NULL values, used following code.
RTRIM(LTRIM(RTRIM(isnull(#firstname,'') + ' ' + isnull(#middlename,'')) + ' ' + isnull(#lastname,'')))
Test different scenarios if you are interested :)
DECLARE #firstname VARCHAR(MAX)
DECLARE #middlename VARCHAR(MAX)
DECLARE #lastname VARCHAR(MAX)
set #firstname = 'FirstName'
set #middlename = NULL
set #lastname = 'LastName'
SELECT '|'+RTRIM(LTRIM(RTRIM(isnull(#firstname,'') + ' ' + isnull(#middlename,'')) + ' ' + isnull(#lastname,'')))+'|'
--
If you are using SQL Server 2012+ you could use CONCAT and +:
SELECT RTRIM(
CONCAT(FirstName + ' ', MiddleInitial + ' ', LastName + ' ', Suffix)
) AS [FullName]
FROM tbl_Contacts;
How it works:
If any part of full name is NULL then NULL + ' ' → NULL
CONCAT handles NULL
In case that after part of name there are only NULLs, TRIM last space.
LiveDemo
Here is a solution:
CREATE FUNCTION dbo.udf_IsNullOrEmpty
(
#vchCheckValue VARCHAR(MAX)
,#vchTrueValue VARCHAR(MAX)
,#vchFalseValue VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
RETURN CASE WHEN NULLIF(RTRIM(LTRIM(#vchCheckValue)),'') IS NULL THEN #vchTrueValue ELSE #vchFalseValue END
END
SELECT FirstName + ' ' +
dbo.udf_IsNullOrEmpty(MiddleInitial,'',MiddleInitial + ' ') +
LastName +
dbo.udf_IsNullOrEmpty(Suffix,'',' ' + Suffix)
FROM tbl_Contacts
select CONCAT(IFNULL(FirstName, ''),
'',
IFNULL(MiddleName, ''),
'',
IFNULL(LastName, '')) AS name
from table
Why not like this:
select concat(fName,' ',
case length(mName)
when 0 then ''
else concat(mName, ' ') end, lName) as fullName
My columns are not null, so this works for me.
create function getfname(#n varchar(30))
returns varchar(30)
as
begin
declare #s varchar(30)
set #s=LEFT(#n,charindex(' ',#n)-1)
return #s
end
create function getLname(#n varchar(30))
returns varchar(30)
as
begin
declare #s varchar(30)
set #s=substring(#n,charindex(' ',#n+1),Len(#n))
return #s
end
the query:
SELECT retire.employeehrmsid,
Isnull(retire.firstname, '') + ' '
+ Isnull(retire.middlename, '') + ' '
+ Isnull(retire.lastname, '') AS FullName,
retire.dojtoservice,
retire.designation,
emphistory.currentdoj,
emphistory.presentddo,
emphistory.office,
transfer.generatetid AS TransferID,
transfer.transferdate,
transfer.currentlocation,
transfer.newlocation,
transfer.datas AS Transfer_Doc,
release.generaterid AS ReleaseID,
release.releasedate,
release.datar AS Release_Doc,
employeeserviceupdate.dataeu AS Join_Doc
FROM retire
INNER JOIN emphistory
ON retire.id = emphistory.id
INNER JOIN employeeserviceupdate
ON retire.id = employeeserviceupdate.id
INNER JOIN transfer
ON retire.id = transfer.id
AND emphistory.ehrid = transfer.ehrid
INNER JOIN release
ON transfer.tid = release.tid