SQL Server 2014 - Case statement not exiting - sql

I am having trouble writing a query to meet the following requirement. One point if Cust_3_3_a through Cust_3_3_c is selected, or Cust_3_3_f through Cust_3_3_h is selected, or Cust_3_4_ is selected; Zero points if Cust_3_3_e is selected and Cust_3_4_ is not selected with value '-'. Selected essentially means a the field has a value of a, b, c, etc.
Here is my test data and query. I would expect with 'Cust_3_4_' set to '-', that 0 should return but 1 returns instead. Is SQL not exiting the case when it hits 0 since the following check would return 1? Thanks in advance.
declare #test table (QuestionKey nvarchar(100), ResponseValue nvarchar(100))
insert into #test (QuestionKey, ResponseValue)
values
('Cust_3_3_a', ''), ('Cust_3_4_', '-'),
('Cust_3_3_b', ''),
('Cust_3_3_c', ''),
('Cust_3_3_e', 'a'),
('Cust_3_3_f', ''),
('Cust_3_3_g', ''),
('Cust_3_3_h', '')
select
max(case
when (
(t.questionkey = 'Cust_3_3_e' AND nullif(t.ResponseValue, '') IS NOT NULL)
and (t.questionkey = 'Cust_3_4_' AND nullif(t.ResponseValue, '') != '-')
) then 1
when (
(t.questionkey = 'Cust_3_3_e' AND nullif(t.ResponseValue, '') IS NOT NULL)
and (t.questionkey = 'Cust_3_4_' AND nullif(t.ResponseValue, '') = '-')
) then 0
when (
t.questionkey BETWEEN 'Cust_3_3_a' AND 'Cust_3_3_c'
OR t.questionkey BETWEEN 'Cust_3_3_f' AND 'Cust_3_3_h'
OR t.questionkey = 'Cust_3_4_'
) and nullif(t.ResponseValue, '') IS NOT NULL then 1
else 0
end) as test1
from #test t

Not sure what you want but I think you are confused with how case statement works. In order for you to return 0 for Cust_3_4_ where ResponseValue != '-', you need to have an actual row with QuestionKey = Cust_3_4_ and ResponseValue != '-' in your table (which you do not have in the above data set)
Also, you have applied MAX function outside your case so it will return you the maximum value 1 if any of the case condition returns 1. If you want the final result 0, you need to use MIN instead BUT please be aware that the 0 will be returned because of the following condition on Cust_3_3_e. Cust_3_4_ will have nothing to do with the 0
declare #test table (QuestionKey nvarchar(100), ResponseValue nvarchar(100))
insert into #test (QuestionKey, ResponseValue)
values
('Cust_3_3_a', ''), ('Cust_3_4_', '-'),
('Cust_3_3_b', ''),
('Cust_3_3_c', ''),
('Cust_3_3_e', 'a'),
('Cust_3_3_f', ''),
('Cust_3_3_g', ''),
('Cust_3_3_h', '')
select * from #test
order by 1 asc
select *,case when QuestionKey IN ('Cust_3_3_a','Cust_3_3_b','Cust_3_3_c') THEN 1
when QuestionKey = 'Cust_3_3_e' THEN 0
when QuestionKey IN ('Cust_3_3_f','Cust_3_3_g','Cust_3_3_h') THEN 1
when QuestionKey = 'Cust_3_4_' AND ResponseValue = '-' THEN 1
when QuestionKey = 'Cust_3_4_' AND ResponseValue <> '-' THEN 0
end test1
from #test

Related

Pull the particular string from nvarchar type column in SQL Server

I have a string like &hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true and want to pull the values partitioned by & from string.
As we see above each string is separated with & and both the values have a name i.e. Outputtype= and ReportCode=
In SQL query it should return only values in different columns. AllCol1 Aand PDF
I have tried the below query but it is pulling string ReportCode=AllCol1
declare #Str varchar(500)
select SUBSTRING(SUBSTRING(#Str, CHARINDEX('&ReportCode=', #Str) + 1, LEN(#Str)), 0, CHARINDEX('&', SUBSTRING(#Str, CHARINDEX('&', #Str) +1, LEN(#Str))))
As you are using SQL Server 2016, you can take advantage of STRING_SPLIT() to split your url into the component query parameters, e.g.
SELECT *
FROM STRING_SPLIT(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true', '&');
Will return:
value
-----------------
hprop=anprop_p
asofmonth=01/2017
OutputType=PDF
IsGrid=-
ReportCode=AllCol1
Attach=NO
IsRequestQue=true
You would then need to split each result on = to separate it into the parameter name and the argument. e.g.
SELECT s.value,
Parameter = CASE WHEN CHARINDEX('=', s.value) = 0 THEN s.value ELSE LEFT(s.value, CHARINDEX('=', s.value) - 1) END,
Value = CASE WHEN CHARINDEX('=', s.value) = 0 THEN NULL ELSE SUBSTRING(s.value, CHARINDEX('=', s.value) + 1, LEN(s.value)) END
FROM STRING_SPLIT(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true', '&') s;
Returns:
value Parameter Value
-------------------------------------------------
NULL
hprop=anprop_p hprop anprop_p
asofmonth=01/2017 asofmonth 01/2017
OutputType=PDF OutputType PDF
IsGrid=- IsGrid -
ReportCode=AllCol1 ReportCode AllCol1
Attach=NO Attach NO
IsRequestQue=true IsRequestQue true
Finally, you would just need to extract the terms you are actually interested in, and PIVOT them to bring back one row. Bringing it all together, you get:
DECLARE #T TABLE (ID INT IDENTITY, Col NVARCHAR(MAX));
INSERT #T (Col)
VALUES
(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true'),
(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=XLS&IsGrid=-&ReportCode=AllCol3&Attach=NO&IsRequestQue=false');
SELECT pvt.ID, pvt.OutputType, pvt.ReportCode
FROM ( SELECT T.ID,
t.Col,
Parameter = CASE WHEN CHARINDEX('=', s.value) = 0 THEN s.value ELSE LEFT(s.value, CHARINDEX('=', s.value) - 1) END,
Value = CASE WHEN CHARINDEX('=', s.value) = 0 THEN NULL ELSE SUBSTRING(s.value, CHARINDEX('=', s.value) + 1, LEN(s.value)) END
FROM #T AS t
CROSS APPLY STRING_SPLIT(T.Col, '&') AS s
WHERE s.value <> ''
) AS t
PIVOT (MAX(Value) FOR Parameter IN ([ReportCode], [OutputType])) AS pvt;
Which returns:
ID OutputType ReportCode
----------------------------------
1 PDF AllCol1
2 XLS AllCol3
Example on DB<>Fiddle
Use string_split():
select max(case when s.value like 'Outputtype=%'
then stuff(s.value, 1, 11, '')
end) as Outputtype,
max(case when s.value like 'ReportCode=%'
then stuff(s.value, 1, 11, '')
end) as ReportCode
from string_split(#str, '&') s;
Here is a db<>fiddle.

Update AND select in one statement (with 1 WHERE clause)

How can I simplify the following SQL statement to 1 WHERE clause:
SELECT *
FROM tabA
WHERE (colCar IS NULL OR colCar = '')
OR (colBike IS NULL OR colBike = '')
OR (colTrain IS NULL OR colTrain = '')
UPDATE tabA
SET Distance = 999
WHERE (colCar IS NULL OR colCar = '') OR
(colBike IS NULL OR colBike = '') OR
(colTrain IS NULL OR colTrain = '')
This statement makes no sense, but it is working fine.
I just want avoid to use the WHERE clause twice.
Some ideas?
Thanks in advance!
Mike
SQL Server
SELECT *
FROM tabA
WHERE (colCar IS NULL OR colCar = '')
OR (colBike IS NULL OR colBike = '')
OR (colTrain IS NULL OR colTrain = '')
UPDATE tabA
SET Distance = 999
WHERE (colCar IS NULL OR colCar = '')
OR (colBike IS NULL OR colBike = '')
OR (colTrain IS NULL OR colTrain = '')
Code is working fine
You can use OUTPUT clause to return updated rows:
UPDATE tabA
SET Distance = 999
OUTPUT deleted.colCar
, inserted.colCar
, deleted.colBike
, inserted.Distance
WHERE (colCar IS NULL OR colCar = '') OR
(colBike IS NULL OR colBike = '') OR
(colTrain IS NULL OR colTrain = '')

Converting multiple delimited fields into rows in SQL Server

I have a data source which contains data in delimited fields which exist in a staging area in SQL Server. I'd like to transform this data into many rows so it is easier to work with. This differs from the numerous other questions and answers on similar topics in that I have multiple fields where this delimited data exists. Here is an example of what my data looks like:
ID | Field | Value
---+-------+------
1 | a,b,c | 1,2,3
2 | a,c | 5,2
And this is the desired output:
ID | Field | Value
---+-------+------
1 | a | 1
1 | b | 2
1 | c | 3
2 | a | 5
2 | c | 2
My code so far uses the XML parsing method like the one mentioned here: Turning a Comma Separated string into individual rows I needed to extend it to join each field to its corresponding value which I have done by generating a row_number for each ID and then matching based on the ID and this row_number.
My issue is that it is painfully slow so I wondered if anyone has any more performant methods?
select
[Value].ID, [Field], [Value]
from
(select
A.ID, Split.a.value('.', 'varchar(100)') as [Value],
row_number() over (partition by ID order by Split.a) as RowNumber
from
(select
ID, cast('<M>' + replace([Value], ',', '</M><M>') + '</M>' as xml) as [Value]
from
#source_table
where
[Field] not like '%[<>&%]%' and [Value] not like '%[<>&%]%') as A
cross apply
[Value].nodes ('/M') as Split(a)
) [Value]
inner join
(
select
A.ID, Split.a.value('.', 'varchar(100)') as [Field],
row_number() over (partition by A.ID order by Split.a) as RowNumber
from
(select
ID, cast('<M>' + replace([Field], ',', '</M><M>') + '</M>' as xml) as [Field]
from
#source_table
where
[Field] not like '%[<>&%]%' and [Value] not like '%[<>&%]%') as A
cross apply
[Field].nodes ('/M') as Split(a)
) [Field] on [Value].ID = [Field].ID and [Value].RowNumber = [Field].RowNumber
Here is an approach using the splitter from Jeff Moden. http://www.sqlservercentral.com/articles/Tally+Table/72993/ One nice feature of that splitter is that it returns the ordinal position of each element so you can use it for joins and such.
Starting with some data.
declare #Something table
(
ID int
, Field varchar(50)
, Value varchar(50)
)
insert #Something values
(1, 'a,b,c', '1,2,3')
, (2, 'a,c', '5,2')
;
Since you have two sets of delimited data you will be forced to split this for each set of delimited values. Here is how you can leverage this splitter to accomplish this.
with Fields as
(
select *
from #Something s
cross apply dbo.DelimitedSplit8K(s.Field, ',') f
)
, Value as
(
select *
from #Something s
cross apply dbo.DelimitedSplit8K(s.Value, ',') v
)
select f.ID
, Field = f.Item
, Value = v.Item
from Fields f
join Value v on v.ItemNumber = f.ItemNumber and v.ID = f.ID
If at all possible it would be best to see if you can change whatever process it is that is populating your source data so it is normalized and not delimited because it is a pain to work with.
Basing on #Gordon Linoff s query here another recursive cte:
DECLARE #t TABLE(
ID int
,Field VARCHAR(MAX)
,Value VARCHAR(MAX)
)
INSERT INTO #t VALUES
(1, 'a,b,c', '1,2,3')
,(2, 'a,c', '5,2')
,(3, 'x', '7');
with cte as (
select ID
,SUBSTRING(Field, 1, CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field)-1 ELSE LEN(Field) END) AS Field
,SUBSTRING(Value, 1, CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value)-1 ELSE LEN(Value) END) AS Value
,SUBSTRING(Field, CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field)+1 ELSE 1 END, LEN(Field)-CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field) ELSE 0 END) as field_list
,SUBSTRING(Value, CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value)+1 ELSE 1 END, LEN(Value)-CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value) ELSE 0 END) as value_list
,0 as lev
from #t
WHERE CHARINDEX(',', Field) > 0
UNION ALL
select ID
,SUBSTRING(field_list, 1, CASE WHEN CHARINDEX(',', field_list) > 0 THEN CHARINDEX(',', field_list)-1 ELSE LEN(field_list) END) AS Field
,SUBSTRING(value_list, 1, CASE WHEN CHARINDEX(',', value_list) > 0 THEN CHARINDEX(',', value_list)-1 ELSE LEN(value_list) END) AS Value
,CASE WHEN CHARINDEX(',', field_list) > 0 THEN SUBSTRING(field_list, CHARINDEX(',', field_list)+1, LEN(field_list)-CHARINDEX(',', field_list)) ELSE '' END as field_list
,CASE WHEN CHARINDEX(',', value_list) > 0 THEN SUBSTRING(value_list, CHARINDEX(',', value_list)+1, LEN(value_list)-CHARINDEX(',', value_list)) ELSE '' END as value_list
,lev + 1
from cte
WHERE LEN(field_list) > 0
)
select ID, Field, Value
from cte
UNION ALL
SELECT ID, Field, Value
FROM #t
WHERE CHARINDEX(',', Field) = 0
ORDER BY ID, Field
OPTION (MAXRECURSION 0)
One method is a recursive CTE:
with cte as (
select id, cast(NULL as varchar(max)) as field, cast(NULL as varchar(max)) as value, field as field_list, value as value_list, 0 as lev
from t
union all
select id, left(field_list, charindex(',', field_list + ',') - 1),
left(value_list, charindex(',', value_list + ',') - 1),
substring(field_list, charindex(',', field_list + ',') + 1, len(field_list)),
substring(value_list, charindex(',', value_list + ',') + 1, len(value_list)),
1 + lev
from cte
where field_list <> '' and value_list <> ''
)
select *
from cte
where lev > 0;
Here is an example of how it works.

How to specify multiple values in when using case statement in sql server

I have the following table
Id Number TypeOfChange
1 2X Scope,Cost,Schedule,EVM,PA
2 3x Scope,Cost
Expected output:
Id Number TypeOfChange Scope Cost Schedule EVM PA
1 2X Scope,Cost,Schedule,EVM,PA X X X X X
2 3x Scope,Cost X X
I try the following script but its not working
SELECT
Id,
Number,
TypeOfChange,
Scope = CASE
WHEN TypeOfChange = 'Scope' THEN 'X'
ELSE '' END,
Cost = CASE
WHEN TypeOfChange = 'Cost' THEN 'X'
ELSE '' END,
Schedule = CASE
WHEN TypeOfChange = 'Schedule' THEN 'X'
ELSE '' END,
EVM = CASE
WHEN TypeOfChange = 'EVM' THEN 'X'
ELSE '' END,
PA = CASE
WHEN TypeOfChange = 'PA' THEN 'X'
ELSE '' END
FROM A
Use Like operator.
SELECT
Id,
Number,
TypeOfChange,
Scope = CASE
WHEN TypeOfChange Like '%Scope%' THEN 'X'
ELSE '' END,
Cost = CASE
WHEN TypeOfChange Like '%Cost%' THEN 'X'
ELSE '' END,
Schedule = CASE
WHEN TypeOfChange Like '%Schedule%' THEN 'X'
ELSE '' END,
EVM = CASE
WHEN TypeOfChange Like '%EVM%' THEN 'X'
ELSE '' END,
PA = CASE
WHEN TypeOfChange Like '%PA%' THEN 'X'
ELSE '' END
FROM A
Example:
we can try charindex or patindex
SELECT
Id,
Number,
TypeOfChange,
Scope = CASE
WHEN CHARINDEX('Scope',TypeOfChange)>0 THEN 'X'
ELSE '' END,
Cost = CASE
WHEN CHARINDEX('Cost',TypeOfChange)>0 THEN 'X'
ELSE '' END,
Schedule = CASE
WHEN CHARINDEX('Schedule',TypeOfChange)>0 THEN 'X'
ELSE '' END,
EVM = CASE
WHEN CHARINDEX('EVM',TypeOfChange)>0 THEN 'X'
ELSE '' END,
PA = CASE
WHEN CHARINDEX('PA',TypeOfChange)>0 THEN 'X'
ELSE '' END
FROM #AA
output
Id Number TypeOfChange Scope Cost Schedule EVM PA
1 2X Scope,Cost,Schedule,EVM,PA X X X X X
2 3x Scope,Cost X X
If TypeOfChange is a dynamic value, you may want to go the dynamic route.
select * into [T1] from
(values (1, '2X', 'Scope,Cost,Schedule,EVM,PA'), (2, '3x', 'Scope,Cost'), (3, '4x', 'someOtherType')) t(Id, Number, TypeOfChange)
--typeOfChange into column list
Declare #SQL varchar(max) = Stuff((
SELECT distinct ',' + QuoteName(LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))))
FROM ( SELECT CAST('<XMLRoot><RowData>' + REPLACE(TypeOfChange,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
FROM [T1]) t CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Id],[Number], [TypeOfChange],' + #SQL + '
From (
SELECT Id, Number, TypeOfChange,
LTRIM(RTRIM(m.n.value(''.[1]'',''varchar(8000)''))) AS [Type], ''X'' as Value
FROM ( SELECT Id, Number, TypeOfChange, CAST(''<XMLRoot><RowData>'' + REPLACE(TypeOfChange,'','',''</RowData><RowData>'') + ''</RowData></XMLRoot>'' AS XML) AS x
FROM [T1]) t CROSS APPLY x.nodes(''/XMLRoot/RowData'')m(n)
) A
Pivot (max(Value) For [Type] in (' + #SQL + ') ) pvt'
Exec(#SQL);
Alternatively you may want to define your Types in a lookup table
select * into [Types] from
(values (1, 'Scope'), (2, 'Cost'), (3, 'Schedule'), (4, 'EVM'), (5, 'PA'), (6, 'someOtherType')) a (Id, TypeOfChange)
Then change the above --typeOfChange into column.. block like this:
--typeOfChange into column list
Declare #SQL varchar(max) = Stuff((
SELECT distinct ',' + QuoteName(TypeOfChange)
FROM [Types]
Order by 1 For XML Path('')),1,1,'')
I think, using LIKE is wrong approach. Especcialy in cases, when one of your strings become f.e."Periscope". You will get false positives.
Try to create user defined function to split strs:
CREATE FUNCTION [dbo].[str__split](
#str NVARCHAR(MAX)
,#delimiter NVARCHAR(MAX)
)
RETURNS #split TABLE(
[str] NVARCHAR(MAX)
)
AS
BEGIN
INSERT INTO #split(
[str]
)
SELECT
[X].[C].[value]('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT
[X] = CONVERT(XML, '<i>' + REPLACE(#str, #delimiter, '</i><i>') + '</i>').query('.')
) AS [A]
CROSS APPLY
[X].[nodes]('i') AS [X]([C]);
RETURN;
END
And then use query:
SELECT
[t].*
,[Scope] = CASE WHEN [t2].[Scope] IS NULL THEN NULL ELSE 'X' END
,[Cost] = CASE WHEN [t2].[Cost] IS NULL THEN NULL ELSE 'X' END
,[Schedule] = CASE WHEN [t2].[Schedule] IS NULL THEN NULL ELSE 'X' END
,[EVM] = CASE WHEN [t2].[EVM] IS NULL THEN NULL ELSE 'X' END
,[PA] = CASE WHEN [t2].[PA] IS NULL THEN NULL ELSE 'X' END
FROM
[your table] AS [t]
OUTER APPLY
(
SELECT * FROM (SELECT [str] from [dbo].[str__split]([TypeOfChange], ',')) AS [d]
PIVOT
(MAX([str]) FOR [str] IN ([Scope], [Cost], [Schedule], [EVM], [PA])) AS [piv]
) AS [t2]

TSQL Replace with Length Constraint

So FIELD2 can return 2 groups of fields concatenated as a single result depending on the value of Mycondition.
My problem is only when Mycondition = 1
If MyCondition = 1 then I need to concatenate INT_FIELD_ONE + 'A' + INT_FIELD_TWO.
The concatenation is not the problem.
The problem is if INT_FIELD_ONE (is null) + 'A' + INT_FIELD_TWO (is null), then I have to return nothing.
My Replace command would work if both fields ONE and TWO are null. But if only 1 is NULL and the other is not the "A" gets deleted any way. The A needs to remain if 1 field is not null.
For Example:
NULL + 'A' + NULL = Nothing
NULL + 'A' + xxxx = Axxxx
xxxx + 'A' + NULL = xxxxA
Therefore I need to make a TSQL replace with a length constraint of result > 1
Any Ideas?
SELECT XXX,
CASE --Case Statement to Return Field2
WHEN MyCondition = 1 THEN
--Constraint on the Replace Starts Here
REPLACE(
Isnull(CAST(INT_FIELD_ONE AS VARCHAR), '') + 'A' +
Isnull(CAST(INT_FIELD_TWO AS
VARCHAR), '')
,'A','')
ELSE
REPLACE(
Coalesce(REPLACE(INT_FIELD_THREE, '', '-'), Isnull(INT_FIELD_THREE, '-'), INT_FIELD_THREE) +
' / ' + Coalesce(REPLACE(INT_FIELD_FOUR, '', '-'),
Isnull(INT_FIELD_FOUR, '-'), INT_FIELD_FOUR) + ' ', '- / - ',
'')
END
AS FIELD2
FROM TABLEX
how about this?
CASE WHEN MyCondition = 1 AND (INT_FIELD_ONE IS NOT NULL OR INT_FIELD_TWO IS NOT NULL) THEN concat..
WHEN MyCondition = 1 THEN NULL -- at that point we know that both are null
ELSE ... END
Notice that now you don't need the REPLACE function when you are doing the concat because you know for sure that one of your fields is not null
…
WHEN MyCondition = 1 THEN
ISNULL(
NULLIF(
ISNULL(CAST(int1 AS VARCHAR), '') + 'A' + ISNULL(CAST(int2 AS VARCHAR), ''),
'A'
),
''
)
…
When both int1 and int2 are NULL, the result of the concatenation will be A. NULLIF() will return NULL if the expression returns A, otherwise it will return the result of the expression. The outer ISNULL() will transform NULL into the empty string or return whatever non-NULL value its first argument has got.