SELECT..CASE - Refactor T-SQL - tsql

Can I refactor the below SQL CASE statements into single for each case ?
SELECT
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.FAMILY_NAME ELSE NULL END AS [LEGAL_FAMILY_NAME],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.GIVEN_NAME ELSE NULL END AS [LEGAL_GIVEN_NAME],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.MIDDLE_NAMES ELSE NULL END AS [LEGAL_MIDDLE_NAMES],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.NAME_TITLE ELSE NULL END AS [LEGAL_NAME_TITLE],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.FAMILY_NAME ELSE NULL END AS [PREFERRED_FAMILY_NAME],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.GIVEN_NAME ELSE NULL END AS [PREFERRED_GIVEN_NAME],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.MIDDLE_NAMES ELSE NULL END AS [PREFERRED_MIDDLE_NAMES],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.NAME_TITLE ELSE NULL END AS [PREFERRED_NAME_TITLE]
FROM dbo.CLIENT_NAME CN
JOIN dbo.REFERENCE_DOMAIN_VALUE RDV
ON CN.NAME_TYPE_CODE = RDV.DOMAIN_CODE AND RDV.REFERENCE_DOMAIN_ID = '7966'

No, you will require 8 separate statements as case and other such variants can only be used in a select to modify the results of a single column, not a series of columns.

If RDV.DOMAIN_COD can only by 'P' or 'L' use NULLIf. It's cleaner.
NULLIF ( expression , expression )
NULLIF is equivalent to a searched CASE expression in which the two expressions are equal and the resulting expression is NULL.
SELECT
NullIf('P', RDV.DOMAIN_CODE) AS [LEGAL_FAMILY_NAME],
...
NullIf('L', RDV.DOMAIN_CODE) AS [PREFERRED_FAMILY_NAME],
...

Since a CASE expression returns a single value, you cannot take eight CASE expressions returning 8 values and make a single CASE expression that returns all eight.

A less efficient alternative with no cases:
SELECT LEGAL_FAMILY_NAME, LEGAL_GIVEN_NAME, LEGAL_MIDDLE_NAMES, LEGAL_NAME_TITLE,
PREFERRED_FAMILY_NAME, PREFERRED_GIVEN_NAME, PREFERRED_MIDDLE_NAMES, PREFERRED_NAME_TITLE
FROM dbo.REFERENCE_DOMAIN_VALUE RDV
LEFT OUTER JOIN
( SELECT
NAME_TYPE_CODE,
FAMILY_NAME AS [LEGAL_FAMILY_NAME],
GIVEN_NAME AS [LEGAL_GIVEN_NAME],
MIDDLE_NAMES AS [LEGAL_MIDDLE_NAMES],
NAME_TITLE AS [LEGAL_NAME_TITLE]
FROM dbo.CLIENT_NAME
WHERE NAME_TYPE_CODE = 'L') LN ON RDV.DOMAIN_CODE = LN.NAME_TYPE_CODE
LEFT OUTER JOIN
( SELECT
NAME_TYPE_CODE,
FAMILY_NAME AS [PREFERRED_FAMILY_NAME],
GIVEN_NAME AS [PREFERRED_GIVEN_NAME],
MIDDLE_NAMES AS [PREFERRED_MIDDLE_NAMES],
NAME_TITLE AS [PREFERRED_NAME_TITLE]
FROM dbo.CLIENT_NAME
WHERE NAME_TYPE_CODE = 'P') PN ON RDV.DOMAIN_CODE = PN.NAME_TYPE_CODE
WHERE RDV.REFERENCE_DOMAIN_ID = '7966'
You could also use a temp table or table variable with all 8 columns and then do two inserts. You could also use a UNION ALL. My guess is that the 8 case statements are the most efficient way. This is especially true if you have some key where you will want some type of ClientID, Legal Names, Preferred Names so you will wrap a MAX around the cases or something and group by a ClientID......
You could generate the SQL script using syscols/INFORMATION_SCHEMA.columns with two case whens for each column 'CASE WHEN DOMAIN_CODE = ''P'' THEN ' + COLUMN_NAME + ' ELSE NULL END AS PREFERRED_' + COLUMN_NAME and then another for L. You could make a LOOP so that the same code executes once for P and once for L and get it down to one loop. Then you could EXEC the string directly, or PRINT it and put it into your SQL script. Anyway for just 8 columns I would cut/paste the case statements...
But anyway in general T-SQL is limited on being able to do for eaches over columns. Either you generate the script using a code generator (done in t-sql or another programming language) or you rethink your problem in another way. But many times you get better performance from duplicate cut/paste code. And many times it isn't worth the hassle of writing an external code generator (ie just for 8 case statements).

Related

TSQL if statement within

I have 160 columns like below
name,preweight,postweight,prebp,postbp,presugar,postsugar...
I am trying to include new column at the end for the list of factors (like weight is different between pre and post, sugar is different pre and post.).
Written sql like below
select name,preweight,postweight,prebp,postbp,presugar,postsugar
case
when (preweight!=postweight)
then 'weight changed'
when (presugar!=postsugar)
then 'sugar changed'
end
from clientinfo
now always shows one changed. but how I can list all the factors which are changed?
One option uses concat_ws() and a series of case expressions:
select
name,
preweight,
postweight,
prebp,
postbp,
presugar,
postsugar
concat_ws(
', '
case when preweight <> postweight then 'weight changed' end,
case when prebp <> postbp then 'bp changed' end,
case when presugar <> postsugar then 'sugar changed' end
) what_changed
from clientinfo

Is there a way to make this case statement work?

I am getting the following error with the code below:
ERROR: argument of AND must be type boolean, not type character varying
LINE 9: and case
^
SQL state: 42804
Character: 300
This code does a few things, but what I am having trouble with is the case statment. I want this piece to look for instances where the first 11 characters of two strings match. If that is not true for a given record, then look at the first 10 characters, then 9, then 8. After that, null is an acceptable result.
select cm.course_id, cm.course_name, cmp.course_id as parentcourse,
(select cmm.course_id
from course_main cmm
where cmm.course_id ilike '%Master%'
and cmm.course_id not ilike '%Ground%'
and cmm.course_id not ilike '%Surprise%'
and cmm.course_id not ilike '%emba%'
and cmm.row_status != 2
and case
when left(cm.course_id,11) = left(cmm.course_id,11)
then cmm.course_id
else
case
when left(cm.course_id,10) = left(cmm.course_id, 10)
then cmm.course_id
else
case
when left(cm.course_id,9) = left(cmm.course_id, 9)
then cmm.course_id
else
case
when left(cm.course_id,8) = left(cmm.course_id,8)
then cmm.course_id
end
end
end
end) as mastercourse
from course_main as cm
left join course_course cc
on cc.crsmain_pk1 = cm.pk1
left join course_main cmp
on cmp.pk1 = cc.crsmain_parent_pk1
where cm.course_id ilike '%-ES-2020-%'
and cm.row_status != 2
and cmp.course_id is null
order by cm.course_id;
Thank you for the assistance, Z4. I took a shot at applying your suggestions and was able to get past the error. The problem with these strings is that I am trying to match something like:
'NRSG-46009-ES-2020-OA' to 'NRSG-46009-Master-Online-Content' in an environment that also contains these:
'NRSG-46006-Master-Online-Content'
'NRSG-46003-Master-Online-Content'
'NRSG-4600-Master-Online-Content'
If I look at the first 8 characters before looking at the first 11, I will get mismatches. So, I am looking at 11 first. If there is nothing that matches, look at the first ten, and so on. Strings of 8 are the floor in our ID schema with examples like this:
'IT-7003-ES-2019-AE' that needs to be matched with 'IT-7003-Master-Online-Content'
Anyway, I took your advice and ran the following:
select distinct cm.course_id, cm.course_name, cmp.course_id as parentcourse,
case
when left(cm.course_id,11) = left(cmm.course_id,11)
then cmm.course_id
else
case
when left(cm.course_id,10) = left(cmm.course_id, 10)
then cmm.course_id
else
case
when left(cm.course_id,9) = left(cmm.course_id, 9)
then cmm.course_id
else
case
when left(cm.course_id,8) = left(cmm.course_id,8)
then cmm.course_id
end
end
end
end as mastercourse
from course_main cm
left join course_course cc
on cc.crsmain_pk1 = cm.pk1
left join course_main cmp
on cmp.pk1 = cc.crsmain_parent_pk1
left join course_main cmm
on cm.pk1 = cm.pk1
where cm.course_id ilike '%-ES-2020-%'
and cm.row_status != 2
and cmp.course_id is null
and cmm.course_id ilike '%Master%'
and cmm.course_id not ilike '%Ground%'
and cmm.course_id not ilike '%Surprise%'
and cmm.course_id not ilike '%emba%'
and cmm.row_status != 2
order by cm.course_id;
This seems to be working, but I am getting duplicate results:
results of 'successful' query
Any ideas on how I can exclude the duplicates?
First, there is really no need to use such a complex case statement. If the first 8 characters do not match, then certainly the first 9, or 10 or 11 are not going to match either, right? So just get rid of the whole thing and check only the first 8:
CASE
WHEN LEFT(cm.course_id, 8) = LEFT(cmm.course_id, 8)
THEN cmm.course_id
END
Next, the CASE statement needs to come out of the WHERE clause and into the SELECT list (this is what is causing the boolean type error):
select
[...]
(
select
CASE
WHEN LEFT(cm.course_id, 8) = LEFT(cmm.course_id, 8)
THEN cmm.course_id
END
from
course_main cmm
where
cmm.course_id ilike '%Master%'
and cmm.course_id not ilike '%Ground%'
and cmm.course_id not ilike '%Surprise%'
and cmm.course_id not ilike '%emba%'
and cmm.row_status != 2
) as mastercourse
FROM
[...]
I don't know anything about the data that you're trying to capture here, but I would also strongly suggest taking some time to rethink the use of a correlated subquery. Even if this is correctly describing the data set you want, the subquery is really inefficient. I would guess that it can probably be replaced entirely by a LEFT JOIN in the main query body (likely a self join from course_main to itself).

Convert value to unique value (ex John to John_1)

The user writes his name and i want to store it into the database. If the name is already in the database i want to insert a postfix. ie Convert 'John' to the first one available between ('John_1', 'John_2' ... etc).
This is my way of doing this so far, but i'm sure there's a better way.
select n from
(
select 'John' n ,0 v
union
select 'John'||'_'||generate_series(1,100),generate_series(1,100)
) possible_names
where n not in
(select my_name from all_names u)
order by v
limit 1
Any suggestions?
If you need to worry about concurrency, the simplest way to guarantee uniqueness is by issuing insert statements until one succeeds. (This assumes you've a unique constraint, of course.)
Pseudocode:
while true
if db.execute(insert_sql, [..., name + postfix, ...])
break
end
counter += 1
postfix = '_' + counter
end
You can make the procedure run in a shorter amount of time by starting at the maximum existing postfix (see the other answers with approaches to do that).
An awkward alternative would be to find the maximum existing postfix using a select statement, and then to try to acquire an advisory lock on something unique to the applicable name and postfix, e.g. 'username:' + name + postfix. It's much less robust though, because it opens up the possibility of two transactions finding the same max_postfix, and then one transaction trying to acquire the lock immediately after other is done committing its insert and releasing that lock -- thus resulting in a duplicate.
SELECT CASE WHEN num IS NULL THEN 'John' ELSE 'John' || '_' || num END AS new_name
FROM (
SELECT max(substr(my_name, position('_' in my_name) + 1)::int) + 1 AS num
FROM all_names
WHERE my_name ilike 'John' || '_%'
) new_number
With all three instances of 'John' being where you pass in the name entered. (This is assuming that the user can't make an underscore part of their name and a number will always follow the underscore.)
Edit: This is also assuming that 'John' and 'john' should be treated the same. If they shouldn't, then replace the ilike with like instead.
CREATE FUNCTION get_username_proposal(text) RETURNS text AS $$
SELECT
CASE WHEN (SELECT COUNT(*) FROM all_names WHERE my_name = $1)=0 THEN
$1
ELSE
$1 || '_' || COALESCE(MAX(LTRIM(SUBSTRING(my_name FROM '_[0-9]+$'), '_')::int), 0)+1
END
FROM
all_names
WHERE
my_name ~ ($1 || '_[0-9]+$');
$$ LANGUAGE SQL STABLE;

need to translate specific t-sql case in pl/sql

Can anyone tell me how to translate the following T-SQL statement:
SELECT fileld1 = CASE
WHEN T.option1 THEN -1
ELSE
CASE WHEN T.option2 THEN 0
ELSE 1
END
END
FROM Table1 AS T
The point is I need to validate two different options from the table for a single field in the select statement..
I have tried to do somthing with an IF statement in pl/sql, but it just doesnt work for me:
SELECT IF T.option1 THEN -1
ELSE IF T.option2 THEN 0
ELSE 1
END
FROM Table1 AS T
I am not actually sure how to write IF statement inside the SELECT statement..
And also, I need to do it INSIDE the select statement because I am constructing a view.
Use:
SELECT CASE
WHEN T.option1 = ? THEN -1
WHEN T.option2 = ? THEN 0
ELSE 1
END AS field1
FROM Table1 AS T
I can't get your original TSQL to work - I get:
Msg 4145, Level 15, State 1, Line 4
An expression of non-boolean type specified in a context where a condition is expected, near 'THEN'.
...because there's no value evaluation. If you're checking if the columns are null, you'll need to use:
SELECT CASE
WHEN T.option1 IS NULL THEN -1
WHEN T.option2 IS NULL THEN 0
ELSE 1
END AS field1
FROM Table1 AS T
...or if you need when they are not null:
SELECT CASE
WHEN T.option1 IS NOT NULL THEN -1
WHEN T.option2 IS NOT NULL THEN 0
ELSE 1
END AS field1
FROM Table1 AS T
CASE expressions shortcircuit - if the first WHEN matches, it returns the value & exits handling for that row - so the options afterwards aren't considered.
If I remember correctly, PL/SQL also supports the case. You just would have to move the column alias from "field1=" before the expression to "AS filed1" after the expression.

How do I use T-SQL's Case/When?

I have a huge query which uses case/when often. Now I have this SQL here, which does not work.
(select case when xyz.something = 1
then
'SOMETEXT'
else
(select case when xyz.somethingelse = 1)
then
'SOMEOTHERTEXT'
end)
(select case when xyz.somethingelseagain = 2)
then
'SOMEOTHERTEXTGOESHERE'
end)
end) [ColumnName],
Whats causing trouble is xyz.somethingelseagain = 2, it says it could not bind that expression. xyz is some alias for a table which is joined further down in the query. Whats wrong here? Removing one of the 2 case/whens corrects that, but I need both of them, probably even more cases.
SELECT
CASE
WHEN xyz.something = 1 THEN 'SOMETEXT'
WHEN xyz.somethingelse = 1 THEN 'SOMEOTHERTEXT'
WHEN xyz.somethingelseagain = 2 THEN 'SOMEOTHERTEXTGOESHERE'
ELSE 'SOMETHING UNKNOWN'
END AS ColumnName;
As soon as a WHEN statement is true the break is implicit.
You will have to concider which WHEN Expression is the most likely to happen. If you put that WHEN at the end of a long list of WHEN statements, your sql is likely to be slower. So put it up front as the first.
More information here: break in case statement in T-SQL
declare #n int = 7,
#m int = 3;
select
case
when #n = 1 then
'SOMETEXT'
else
case
when #m = 1 then
'SOMEOTHERTEXT'
when #m = 2 then
'SOMEOTHERTEXTGOESHERE'
end
end as col1
-- n=1 => returns SOMETEXT regardless of #m
-- n=2 and m=1 => returns SOMEOTHERTEXT
-- n=2 and m=2 => returns SOMEOTHERTEXTGOESHERE
-- n=2 and m>2 => returns null (no else defined for inner case)
If logical test is against a single column then you could use something like
USE AdventureWorks2012;
GO
SELECT ProductNumber, Category =
CASE ProductLine
WHEN 'R' THEN 'Road'
WHEN 'M' THEN 'Mountain'
WHEN 'T' THEN 'Touring'
WHEN 'S' THEN 'Other sale items'
ELSE 'Not for sale'
END,
Name
FROM Production.Product
ORDER BY ProductNumber;
GO
More information - https://learn.microsoft.com/en-us/sql/t-sql/language-elements/case-transact-sql?view=sql-server-2017