use defined CASE further in the same SELECT as variable - sql-server

I have a SELECT statement in which I defined CASE expression which I'm using in another calculations within the same SELECT. However it's quite big CASE so I don't want to copy it all the time. Is it possible to define it as variable and then use only name of the variable in my calculations?
E.g.:
CASE WHEN xxxx END AS - define it as #test
then use it for example in WHERE part - WHEN #test='X'
is it possible? thanks

Yes you can AND doing so has great performance benefits. Consider these the following sample data and query.
-- Sample data
IF OBJECT_ID('tempdb..#sometable') IS NOT NULL DROP TABLE #sometable;
CREATE TABLE #sometable(someid int identity, somevalue decimal(10,2));
INSERT #sometable(somevalue) VALUES (100),(1050),(5006),(111),(4);
-- Query:
DECLARE #var1 int = 100, #var2 int = 50, #var3 int = 900, #topPct tinyint = 90;
SELECT TOP (#topPct) PERCENT
someid,
somevalue,
someCalc =
CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END,
someRank = dense_rank() OVER (ORDER BY
CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END)
FROM #sometable
WHERE CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END
BETWEEN 900 AND 2000
ORDER BY -- simulate another event that causes a sort
CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END;
As mentioned, you could you CROSS APPLY like so:
-- Improved
SELECT TOP (#topPct) PERCENT
s.someid, s.somevalue, someCalc=iTVF.result, someRank=DENSE_RANK() OVER (ORDER BY iTVF.result)
FROM #sometable AS s
CROSS APPLY (VALUES(CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3
ELSE #var3+somevalue END)) AS iTVF(result)
WHERE iTVF.result BETWEEN 900 and 2000
ORDER BY iTVF.result;
Notice how I have replaced all references of the CASE statement with itvf.result. The code is much cleaner. The other benefit is the execution plan. Using CROSS APPLY in this way prevents the optimizer from performing the same calculation twice.

You want something like that :
select *
from . . . cross apply
( values (case when . . .
then . . .
else . . .
end)
) tt (test)
where tt.test = 'X'

You can wrap your query inside a sub-query and place the SELECT and WHERE clauses outside it:
SELECT a, b, c, test
FROM (
SELECT a, b, c, CASE
WHEN ...
WHEN ...
END AS test
FROM ...
) AS sq
WHERE sq.test = 'something'
Ideally you should avoid case inside where clause.

Related

Where do my parameter variables go in T-SQL function?

This is the function that I am trying to make below with two parameters and one single output that is the matching words. I am using #searchentry and #bestmatch for my parameters. My questions is where should the parameters go in the function so that I can just call the function when it is created Select dbo.FunMatch('enamel cleaner', 'cleaner') it will excecute the function and return the matching words from the two arguments which would be 1 ?
Create Function dbo.FunMatch(
#searchentry varchar,
#bestmatch varchar
)
Returns INT
As
Begin
Declare #output INT
Set #output = (select
#searchentry,
#bestmatch,
cast(count(isMatch) as float) as matchingWords
from
(
select
s.value as word_from_search_entry_txt,
b.value as word_from_best_match,
case
when s.value = b.value or s.value+'s'=b.value or s.value=b.value+'s' then 'match'
else null
end as isMatch,
t.*
from (
SELECT
#searchentry,#bestmatch
FROM #tmp_parts
) t
cross apply
string_split(#searchentry, ' ') s
cross apply
string_split(#bestmatch, ' ') b
) a
group by
#searchentry,
#bestmatch)
Return #output
I am writing a function to return the matching words between two strings. example data below
CREATE TABLE #tmp_parts
(
search_entry_txt VARCHAR(30),
best_match VARCHAR(30),
);
INSERT INTO #tmp_parts
VALUES ('rotating conveyors', 'conveyor'),
('rivet tool', 'rivet nut tool'),
('enamel cleaner', 'cleaner'),
('farm ring', 'ring'),
('tire gauge', 'gauge'),
('ice cream','ice cream');
You can see the expected out here which is the matchingWords column
select
search_entry_txt,
best_match,
cast(count(isMatch) as float) as matchingWords
from
(
select
s.value as word_from_search_entry_txt,
b.value as word_from_best_match,
case
when s.value = b.value or s.value+'s'=b.value or s.value=b.value+'s' then 'match'
else null
end as isMatch,
t.*
from (
SELECT
search_entry_txt,best_match
FROM #tmp_parts
) t
cross apply
string_split(search_entry_txt, ' ') s
cross apply
string_split(best_match, ' ') b
) a
group by
search_entry_txt,
best_match
There are some issues with your function script.
The parameters #searchentry, #bestmatch might add type length otherwise that will declare length as 1.
you are missing the END on the function end.
from your code you don't need to use #tmp_parts temp table, just use parameters #searchentry, #bestmatch.
There are some verbosity script you might not need, (group by part, subquery which be able to use aggregate function to instead)
I had rewritten your script, you can try this.
Create Function dbo.FunMatch(
#searchentry varchar(max),
#bestmatch varchar(max)
)
Returns INT
As
Begin
Declare #output INT
set #output =(select
COUNT(case
when s.value = b.value or s.value+'s'=b.value or s.value=b.value+'s' then 'match'
else null
end)
from
string_split(#searchentry, ' ') s
cross apply
string_split(#bestmatch, ' ') b)
Return #output
END
sqlfiddle

SQL Server : Create Function Wrong Syntax near 'Begin'

I have a problem figuring out how to get rid of an error. It says there is wrong Syntax near the Begin statement. I assume it means before, but I do not know what. I've tried many different declarations of the function but did not get it to work.
I've table that is feeded a line in every step of a process, for multiple processes. The function should take a process name (unit) and time and should result all lines for that process from start to end.
Executing the sql without a function works fine.
CREATE FUNCTION [GetFullCIP]
(
#pTime DATETIME2,
#pName NVARCHAR(50)
)
RETURNS TABLE
AS
BEGIN
DECLARE #cipid int
SELECT TOP(1) #cipid=unit_id FROM [dbo].[md_units] WHERE unit=#pName
DECLARE #stop Datetime2;
DECLARE #start Datetime2;
--start
SELECT TOP (1) #start=[begin_date] FROM [dbo].[log] WHERE [operation_id]=1 AND unit_id=#cipid AND [begin_date] <=#pTime ORDER BY [cip_id] DESC
--stop
SELECT TOP (1) #stop=[inserted_date] FROM [dbo].[log] WHERE [operation_id]=99 AND unit_id=#cipid AND [inserted_date]>=#pTime ORDER BY [cip_id] ASC
RETURN (SELECT * FROM [dbo].[log] WHERE unit_id=#cipid AND [begin_date]>=#start AND [inserted_date]<=#stop)
END
GO
I read that i should give the return table a name, like #resdata. I tried that and at the end write
SET #resdata=(SELECT ...) but that doesnt work, by than it does not know #resdata anymore.
Thx in advance
As I mentioned, I would use an inline table-value function. This is untested, due to no sample data or expected results, but is a literal translation of the ml-TVF you have posted.
CREATE FUNCTION dbo.[GetFullCIP] (#pTime datetime2(7), #pName nvarchar(50))
RETURNS table
AS RETURN
SELECT L.* --Replace this with your explicit columns
FROM dbo.log L
JOIN dbo.md_units MU ON L.unit_id = MU.unit_id
WHERE MU.Unit = #pName
AND L.begin_date >= (SELECT TOP (1) sq.begin_date
FROM dbo.log sq
WHERE sq.operation_id = 1
AND sq.unit_id = MU.unit_id
AND sq.begin_date <= #pTime
ORDER BY sq.cip_id DESC)
AND L.inserted_date <= (SELECT TOP (1) sq.inserted_date
FROM dbo.log sq
WHERE sq.operation_id = 99
AND sq.unit_id = MU.unit_id
AND sq.inserted_date >= #pTime
ORDER BY sq.cip_id ASC)
GO

Where Clause Return Null Record

I am not sure this is possible, but here is what I am trying to do: In the where clause below it is working fine but now I need to somehow:
If #Contact has a value of 'Steve' but 'Steve' does not exist in contact I want to return records where contact is null.
How can I achieve that?
WHERE (contact = #Contact OR (contact IS NULL AND #Contact IS NULL))
One method uses a subquery. A version that doesn't use a subquery is:
select top (1) with ties t.*
from t
where contact = #Contact or contact is null
order by contact desc;
I'm guessing a bit on exactly what you are after, but one possibility might be something like this?
select * from someTable t
where 1 =
(case when exists(select * from someTable t2 where t2.contact = #contact)
then
case when #contact = t.contact then 1 end
else
case when t.contact IS NULL then 1 end
end)
This means:
IF at least one row exists in someTable, where someTable.contact = #contact,
then return all rows where someTable.contact = #contact .
Otherwise, return all rows where someTable.contact IS NULL.
If you set it up as a stored procedure, you can set the default to a blank and then use that in the WHERE statement. Below is a pseudo-example from a working proc. It uses a Full-Text Index but you get the idea.
CREATE PROCEDURE [dbo].[procMyProc]
#p_searchtermKeyword NVARCHAR(255) = '""'
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM MyTable
WHERE ((CONTAINS(Keywords, #p_searchtermKeyword)) OR #p_searchtermKeyword = '""')
END

Need help removing functions from CASE WHEN

I have a situation where I have created script to select data in our company's environment. In doing so, I decided to use functions for some pattern matching and stripping of characters in a CASE WHEN.
However, one of our clients doesn't want to let us put their data in our local environment, so I now have the requirement of massaging the script to be able to run on their environment--essentially meaning I need to remove the functions, and I am having trouble thinking about how I need to move stuff around to do so.
An example of the function call would be:
SELECT ....
CASE WHEN Prp = 'Key Cabinet'
AND SerialNumber IS NOT NULL
AND dbo.fnRemoveNonNumericCharacters(SerialNumber) <> ''
THEN dbo.fnRemoveNonNumericCharacters(SerialNumber)
....
INTO #EmpProperty
FROM ....
Where Prp is a column that contains the property type and SerialNumber is a column that contains a serial number, but also some other random garbage because data entry was sloppy.
The function definition is:
WHILE PATINDEX('%[^0-9]%', #strText) > 0
BEGIN
SET #strText = STUFF(#strText, PATINDEX('%[^0-9]%', #strText), 1, '')
END
RETURN #strText
where #strText is the SerialNumber I am passing in.
I may be stuck in analysis paralysis because I just can't figure out a good way to do this. I don't need a full on solution per-say, perhaps just point me in a direction you know will work. Let me know if you would like some sample DDL/DML to mess around with stuff.
Example 'SerialNumber' values: CA100 (Trash bins), T110, 101B.
There are also a bunch of other types of values such as all text or all numbers, but we are filtering those out. The current patterning matching is good enough.
So I think you mean you can't use a function... so, perhaps:
declare #table table (SomeCol varchar(4000))
insert into #table values
('1 ab2cdefghijk3lmnopqr4stuvwxyz5 6 !7##$8%^&9*()-10_=11+[]{}12\|;:13></14? 15'),
('CA100 (Trash bins), T110, 101B')
;with cte as (
select top (100)
N=row_number() over (order by ##spid) from sys.all_columns),
Final as (
select SomeCol, Col
from #table
cross apply (
select (select X + ''
from (select N, substring(SomeCol, N, 1) X
from cte
where N<=datalength(SomeCol)) [1]
where X between '0' and '9'
order by N
for xml path(''))
) Z (Col)
where Z.Col is not NULL
)
select
SomeCol
,cast(Col as varchar) CleanCol --change this to BIGINT if it isn't too large
from Final

SQL return true if criteria match

I have an assignment table in my database. I also have a assignment_notes table, which has a reference to the assignment table. There can exists multiple rows in this table.
When I select all my assignments, I want to check if there exists some notes to this assignment. And all I want is a true/false return.
Is it possible to do something like (pseudo):
Select all assignments; if assignment
has assignment_notes HasNotes = true; else
HasNotes = false.
I hope I made this clear enough - I'm not so good at explaining programming stuff ;-)
DECLARE #Assignments TABLE
(
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(30) NOT NULL
)
DECLARE #AssignmentNotes TABLE
(
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
AssignmentId INT NOT NULL,
Note VARCHAR(MAX)
)
INSERT INTO #Assignments(Name) VALUES('Biology')
INSERT INTO #Assignments(Name) VALUES('Chemistry')
INSERT INTO #AssignmentNotes (AssignmentId, Note) VALUES(1, 'Studies on DNA')
INSERT INTO #AssignmentNotes (AssignmentId, Note) VALUES(1, 'Evolution notes from Darwin')
SELECT
A.*,
CASE WHEN COUNT(AN.Id) > 0 THEN 1 ELSE 0 END AS HasNotes
FROM
#Assignments AS A
LEFT JOIN
#AssignmentNotes AS AN
ON A.Id = AN.AssignmentId
GROUP BY
A.Id,
A.Name
Don't have SQL Server ready to test, but a query like this should work:
SELECT A.*,
CAST(
CASE (SELECT TOP 1 AssignmentNotes_ID
FROM AssignmentNotes AN
WHERE AN.AssignmentID = A.AssignmentID)
WHEN NULL THEN 0 ELSE 1 END
AS BIT) AS HasNotes
FROM Assignments A
SELECT a.*,
(SELECT COUNT(*)
FROM assignment_notes an
WHERE an.assignmentid = a.id) as NumNotes
FROM Assignment a
That will give you the number of notes with that assignment.
You can translate your pseudo code to SQL if you use the case statement. On MSDN: http://msdn.microsoft.com/en-us/library/ms181765.aspx
And another article just in case: http://www.devx.com/tips/Tip/15633
This approach means that you don't need a huge GROUP BY statement as a result of returning lots of Assignment fields.
SELECT Assignment.*,
CAST(CASE WHEN NotesQty>0 THEN 1 ELSE 0 END as bit) AS HasNotes
FROM Assignment
LEFT JOIN
(SELECT AssignmentId,COUNT(*) AS NotesQty
FROM assignment_notes
GROUP BY AssignmentId) as Assignment_NotesQty
ON Assignment_NotesQty.AssignmentId=Assignment.AssignmentId

Resources