Update multiple columns from one table with one column from another table - sql

I have three tables that are structured in this format:
DECLARE #A
(
TypeName varchar(100),
Type1 varchar(3),
Type2 varchar(3),
Type3 varchar(3),
Type4 varchar(3),
Type5 varchar(3),
Type6 varchar(3)
)
Data:
| Bob | null | null | null | null | null | null |
| Steve | null | null | null | null | null | null |
| Bill | null | null | null | null | null | null |
...
DECLARE #B
(
NameID int,
Name varchar(100)
)
Data:
| 1 | Bob |
| 2 | Steve |
| 3 | Bill |
...
DECLARE #C
(
NameID int,
Type int
)
Data:
| 1 | 1 |
| 1 | 3 |
| 2 | 1 |
| 3 | 2 |
...
I want to update the Type# columns in table #A with a condition based on the Type column of table #C. Here is an SQL query that I have attempted to use:
UPDATE #A SET
Type1 = (CASE WHEN c.Type = 6 THEN 'Yes' ELSE 'No' END),
Type2 = (CASE WHEN c.Type = 1 THEN 'Yes' ELSE 'No' END),
Type3 = (CASE WHEN c.Type = 2 THEN 'Yes' ELSE 'No' END),
Type4 = (CASE WHEN c.Type = 3 THEN 'Yes' ELSE 'No' END),
Type5 = (CASE WHEN c.Type = 4 THEN 'Yes' ELSE 'No' END),
Type6 = (CASE WHEN c.Type = 5 THEN 'Yes' ELSE 'No' END),
FROM #A
INNER JOIN #B b ON b.Name = TypeName
INNER JOIN #C c ON c.NameID = b.NameID
SELECT * FROM #A
Instead of getting the expected values:
| Bob | No | Yes | No | Yes | No | No | <-- Correct
| Steve | No | Yes | No | No | No | No |
| Bill | No | No | Yes | No | No | No |
I get these values:
| Bob | No | Yes | No | No | No | No | <-- Incorrect (noticed the third column from the right being set to No instead of Yes)
| Steve | No | Yes | No | No | No | No |
| Bill | No | No | Yes | No | No | No |
I have also tried re-writing the statement to be like this:
UPDATE #A SET
Type1 = (CASE WHEN t.Type = 6 THEN 'Yes' ELSE 'No' END),
Type2 = (CASE WHEN t.Type = 1 THEN 'Yes' ELSE 'No' END),
Type3 = (CASE WHEN t.Type = 2 THEN 'Yes' ELSE 'No' END),
Type4 = (CASE WHEN t.Type = 3 THEN 'Yes' ELSE 'No' END),
Type5 = (CASE WHEN t.Type = 4 THEN 'Yes' ELSE 'No' END),
Type6 = (CASE WHEN t.Type = 5 THEN 'Yes' ELSE 'No' END),
FROM
(
SELECT b.Name, c.Type
FROM #C c
INNER JOIN #B b on b.NameID = c.NameID
) t
WHERE t.Name = TypeName
But it still returned the latter results.
So why is it that the columns are not being updated correctly for the TypeName column Bob and how would I fix that so that I can get my expected values?

The problem is that you have several rows in #C per person. You'll have to first make it one row to be able to update the data:
select
NameID,
max(case Type when 1 then 1 else 0 end) as Type1,
max(case Type when 2 then 1 else 0 end) as Type2,
max(case Type when 3 then 1 else 0 end) as Type3,
max(case Type when 4 then 1 else 0 end) as Type4,
max(case Type when 5 then 1 else 0 end) as Type5,
max(case Type when 6 then 1 else 0 end) as Type6
from
#C
group by
NameID
Can't test this now, but you probably can even make the text there, with something like this:
case when max(case Type when 1 the 1 else 0) = 1 then 'Yes' else 'No'
end as Type1
You can use this as derived table in the update, or maybe collect the results first into another table variable

Related

CASE expression from multiple tables

I'm trying to write a CASE expression where I look at 2 tables (same DB) to output a 1 or 0 and then SUM that column with some grouping on BRANCH and EVUSERID.
This may be the wrong method altogether for what I'm wanting to achieve so am completely open to suggestions.
EVENT table
+--------+----------+-----------+---------------------------+-----------+
| BRANCH | EVUSERID | EVENTTYPE | REFERENC | ENQUIRYNO |
+--------+----------+-----------+---------------------------+-----------+
| 1 | cunnc | SAS | New Cust - Unknown Source | 1468731 |
| 2 | malce | SAS | New Cust - Walk-in | 1792469 |
| 1 | cunnc | SAS | Old Cust - Retention | 897324 |
| 4 | wardt | OPT | | 986234 |
| 5 | shera | OPT | | 576324 |
| 4 | hamzk | SAS | Old Cust - Service | 1238746 |
| 5 | chinc | SAS | New Cust - Digital | 687236 |
+--------+----------+-----------+---------------------------+-----------+
SORDER table
+-----------+---------+
| ENQUIRYNO | NEWUSED |
+-----------+---------+
| 1468731 | N |
| 1792469 | N |
| 897324 | U |
| 986234 | N |
| 576324 | U |
| 1238746 | U |
| 687236 | U |
+-----------+---------+
My desired output would be
+--------+----------+----------------+---------+-------+---------+-----------+---------+-------------+--------------+---------+
| BRANCH | EVUSERID | Unknown Source | Walk-in | Phone | Digital | Retention | Service | New Enquiry | Used Enquiry | Unknown |
+--------+----------+----------------+---------+-------+---------+-----------+---------+-------------+--------------+---------+
| 1 | cunnc | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
| 2 | malce | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 4 | hamzk | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 5 | chinc | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
+--------+----------+----------------+---------+-------+---------+-----------+---------+-------------+--------------+---------+
Where I am so far. All is working as expected until I added my join in
SELECT
E.CM_EV_BRANCH,
E.CM_EV_EVUSERID,
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Unknown%' THEN 1 END) AS 'UnknownSource',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Walk%' THEN 1 END) AS 'Walk-in',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Phone%' THEN 1 END) AS 'Phone',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Digital%' THEN 1 END) AS 'Digital',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Retention%' THEN 1 END) AS 'Retention',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Service%' THEN 1 END) AS 'Service',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND S.VM_SO_NEWUSED = 'N' THEN 1 END) AS 'New Enquiry',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND S.VM_SO_NEWUSED = 'U' THEN 1 END) AS 'Used Enquiry',
SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND S.VM_SO_NEWUSED <> 'U' AND S.VM_SO_NEWUSED <> 'N' THEN 1 END) AS 'Unknown'
FROM
Datalake.dbo.CM_01_EVENT E
LEFT JOIN Datalake.dbo.VM_01_SORDER S ON E.CM_EV_ENQUIRYNO = S.VM_SO_ENQUIRYNO
WHERE
E.CM_EV_BRANCH <> 0 AND E.CM_EV_DEPTTYPE = 'S'
GROUP BY
E.CM_EV_BRANCH, E.CM_EV_EVUSERID
/*
HAVING SUM(CASE WHEN E.CM_EV_EVENTTYPE = 'SAS' AND E.CM_EV_REFERENC LIKE '%Unknown%' OR E.CM_EV_REFERENC LIKE '%Walk%' OR E.CM_EV_REFERENC LIKE '%Phone%' OR E.CM_EV_REFERENC LIKE '%Digital%' THEN 1 ELSE 0 END) <> 0
*/
ORDER BY
E.CM_EV_BRANCH, E.CM_EV_EVUSERID
I've commented out the HAVING clause as that didn't work as desired. There are instances in the DB where an EVUSERID exists for a given branch but doesn't meet the criteria in the CASE expressions. These would display as "0" which whilst is correct, I wasn't interested in seeing during developing the SQL statement.
Any help would be gratefully appreciated.
I think you want a where clause: this filters out rows that would have all 0s, and lets you simplify the case expressions in the subquery (since one condition is common to all expressions).
Also: don't use single quotes for column aliases! They are meant for literal strings. Use aliases that do not require quoting - or use square brackets to quote them.
So:
SELECT
E.CM_EV_BRANCH,
E.CM_EV_EVUSERID,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Unknown%' THEN 1 ELSE 0 END) AS UnknownSource,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Walk%' THEN 1 ELSE 0 END) AS Walk_in,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Phone%' THEN 1 ELSE 0 END) AS Phone,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Digital%' THEN 1 ELSE 0 END) AS Digital,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Retention%' THEN 1 ELSE 0 END) AS Retention,
SUM(CASE WHEN E.CM_EV_REFERENC LIKE '%Service%' THEN 1 ELSE 0 END) AS Service,
SUM(CASE WHEN S.VM_SO_NEWUSED = 'N' THEN 1 ELSE 0 END) AS NewEnquiry,
SUM(CASE WHEN S.VM_SO_NEWUSED = 'U' THEN 1 ELSE 0 END) AS Used Enquiry,
SUM(S.VM_SO_NEWUSED NOT IN ('U', 'N') THEN 1 ELSE 0 END) AS Unknown
FROMDatalake.dbo.CM_01_EVENT E
LEFT JOIN Datalake.dbo.VM_01_SORDER S ON E.CM_EV_ENQUIRYNO = S.VM_SO_ENQUIRYNO
WHERE
E.CM_EV_BRANCH <> 0
AND E.CM_EV_DEPTTYPE = 'S'
AND E.CM_EV_EVENTTYPE = 'SAS'
AND (
E.CM_EV_REFERENC LIKE '%Unknown%'
OR E.CM_EV_REFERENC LIKE '%Walk%'
OR E.CM_EV_REFERENC LIKE '%Phone%'
OR E.CM_EV_REFERENC LIKE '%Digital%'
OR E.CM_EV_REFERENC LIKE '%Retention%'
OR E.CM_EV_REFERENC LIKE '%Service%'
)
GROUP BY E.CM_EV_BRANCH, E.CM_EV_EVUSERID
ORDER BY E.CM_EV_BRANCH, E.CM_EV_EVUSERID

Query to generate string based on values in multiple columns

I have a table in database that has structure and data as follows:
+-----+--------+--------+--------+--------+--------+
| ID | Col1 | Col2 | Col3 | Col4 | Col5 |
+-----+--------+--------+--------+--------+--------+
| 1 | MALE | MALE | FEMALE | NULL | NULL |
| 2 | FEMALE | MALE | NULL | NULL | NULL |
| 3 | FEMALE | NULL | NULL | NULL | NULL |
| 4 | MALE | OTHER | FEMALE | FEMALE | NULL |
| 5 | MALE | OTHER | FEMALE | MALE | FEMALE |
+-----+--------+--------+--------+--------+--------+
The order of data has to be in order of first appearance in the columns, from Col1 to Col5, to get the following output:
+-----+--------------------------------------------+
| ID | Remarks |
+-----+--------------------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+-----+--------------------------------------------+
I would do this use apply.
select t.*,
(case when num_male = 0 and num_female = 0 and num_other = 0
then ''
when num_male = 0 and num_female = 0
then replace('num_other OTHER', 'num_other', num_other)
when num_male = 0 and num_other = 0
then replace('num_female FEMALE', 'num_female', num_female)
when num_male = 0 and num_female = 0
then replace('num_male MALE', 'num_male', num_male)
when num_male = 0
then replace(replace(replace('num_other OTHER AND num_female FEMALE'), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_other = 0
then replace(replace(replace('num_male MALE AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_female = 0
then replace(replace(replace('num_male MALE AND num_other OTHER), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
else replace(replace(replace('num_male MALE, num_other OTHER AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
end) as remarks
from t cross apply
(select sum(case when col = 'FEMALE' then 1 else 0 end) as num_females,
sum(case when col = 'MALE' then 1 else 0 end) as num_males,
sum(case when col = 'OTHER' then 1 else 0 end) as num_other
from (values (col1), (col2), (col3), (col4), (col5)) v(col)
) v;
I don't see an advantage to cleverness in calculating the remarks structure.
This is a very unusual requirement and I would imagine that you don't really need to have the values in the order you are asking for, but to answer the question as asked:
-- Build data
declare #t table(ID int,Col1 varchar(10),Col2 varchar(10),Col3 varchar(10),Col4 varchar(10),Col5 varchar(10));
insert into #t values
(1,'MALE','MALE','FEMALE',null,null)
,(2,'FEMALE','MALE',null,null,null)
,(3,'FEMALE',null,null,null,null)
,(4,'MALE','OTHER','FEMALE','FEMALE',null)
,(5,'MALE','OTHER','FEMALE','MALE','FEMALE')
;
-- Query
with d as
( -- Manually unpivot the 5 Cols and add a row number
select 1 as r
,ID
,Col1 as Col
from #t
union all
select 2
,ID
,Col2 as Col
from #t
union all
select 3
,ID
,Col3 as Col
from #t
union all
select 4
,ID
,Col4 as Col
from #t
union all
select 5
,ID
,Col5 as Col
from #t
)
,c as
( -- Replace Col values for presentation and add in aggregated row numbers, ranks and counts for each value
select ID
,case Col when 'MALE' then 'Male'
when 'FEMALE' then 'Female'
when 'OTHER' then 'Other'
end as Col
,row_number() over (partition by ID order by r) as rn
,dense_rank() over (partition by ID order by r) as drk
,rank() over (partition by ID, Col order by r) as rk
,count(Col) over (partition by ID, Col) as c
from d
where Col is not null
)
,o as
( -- Filter rows down to just the first instance of that Col value to get the presentation order
select ID
,row_number() over (partition by ID order by rn) as o
,cast(c as varchar(10)) + ' ' + Col as c
from c
where rk = 1
)
-- Collate the data per presentation rules
select o.ID
,o.c
+ isnull(case when o3.ID is null
then ' and ' + o2.c
else ', ' + o2.c + ' and ' + o3.c
end
,'') as Remarks
from o
left join o as o2
on o.ID = o2.ID
and o2.o = 2
left join o as o3
on o.ID = o3.ID
and o3.o = 3
where o.o = 1
order by o.ID;
Output:
+----+------------------------------+
| ID | Remarks |
+----+------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+----+------------------------------+

SQL query to compare records having count greater than 1

I have a table which contains id and answer columns which looks like:
| id | answer |
| 1 | Yes |
| 2 | No |
| 3 | Unsure |
| 1 | No |
| 1 | Unsure |
| 3 | Yes |
| 2 | Unsure |
| 4 | NULL |
| 4 | Unsure |
| 4 | No |
I want to the output in a way that if the userid has ever answered 'Yes' (like for example id 1) then the final output should have 1.
But if the userid has answered "No" and "NULL" then output should be "No"
Further if the userid has answered "Unsure" and "No" or "Unsure" and "Null" then output should be "Unsure"
Final Output:
| id | answer |
| 1 | Yes |
| 2 | Unsure |
| 3 | Yes |
| 4 | Unsure |
You want to use aggregation and case logic for this:
select id,
(case when sum(case when Answer = 'yes' then 1 else 0 end) > 0
then 'yes'
when sum(case when answer = 'unsure' then 1 else 0 end) > 0
then 'unsure'
else 'no' -- or perhaps max(answer)
end) as answer
from t
group by id;
If the only possibilities are "yes", "no", "unsure", and NULL, then you can take a short-cut:
select id,
(case when max(Answer) = 'yes'
then 'yes'
when max(answer) = 'unsure'
then 'unsure'
else 'no' -- or perhaps max(answer)
end) as answer
from t
group by id;
CASE WHEN (.Answer IS NULL OR .Answer = 'No') THEN 'NO' ELSE (.Answer) END AS 'FILTERED ANSWER'
Could This help you?

Sum all Inserted Values in Trigger

Suppose, I have the following table T1:
| type | col1 | col2 |
|------|------|------|
| abc | 0 | 0 |
| def | 0 | 3 |
| abc | 3 | 123 |
| def | 0 | 5 |
| def | 2 | 4 |
Periodically, some new values are inserted into T1. I now want to create a trigger, which populates another table T2, depending on the values inserted into T1.
The values to insert into T2 can be calculated using the following pseudo code:
IF col1 = 0 AND col2 = 0
A++
ELSE IF col1 = 0 col2 > 0
B++
ELSE IF col1 > 0
C++
I already created the following trigger:
Create TRIGGER TRI1
ON dbo.T1
FOR INSERT
AS
BEGIN
INSERT INTO dbo.T2
SELECT Sum(CASE WHEN col1 = 0 AND col2 = 0 THEN 1 END) as 'A',
Sum(CASE WHEN col1 = 0 AND col2 > 0 THEN 1 END) as 'B',
Sum(CASE WHEN col1 > 0 THEN 1 END) as 'C'
FROM INSERTED
END
When I test it with:
INSERT INTO dbo.T1 VALUES ('abc',2,3)
INSERT INTO dbo.T1 VALUES ('abc',0,0)
INSERT INTO dbo.T1 VALUES ('def',0,3)
INSERT INTO dbo.T1 VALUES ('abc',0,0)
I get the following output:
| A | B | C |
|------|------|------|
| NULL | NULL | 1 |
| 1 | NULL | NULL |
| NULL | 1 | NULL |
| 1 | NULL | NULL |
But the expected output is only 1 row per insert operation:
| A | B | C |
|---|---|---|
| 2 | 1 | 1 |
You should create a trigger like this,
CREATE TRIGGER TRI1 ON dbo.T1
FOR INSERT
AS
BEGIN
IF EXISTS (
SELECT 1
FROM dbo.T2
)
BEGIN
UPDATE T
SET A = Sum(CASE
WHEN T.col1 = 0
AND T.col2 = 0
THEN 1
ELSE 0
END)
,B = Sum(CASE
WHEN T.col1 = 0
AND T.col2 > 0
THEN 1
ELSE 0
END)
,C = Sum(CASE
WHEN T.col1 > 0
THEN 1
ELSE 0
END)
FROM dbo.t1 T
END
ELSE
BEGIN
INSERT INTO dbo.T2
SELECT Sum(CASE
WHEN T.col1 = 0
AND T.col2 = 0
THEN 1
ELSE 0
END) AS 'A'
,Sum(CASE
WHEN T.col1 = 0
AND T.col2 > 0
THEN 1
ELSE 0
END) AS 'B'
,Sum(CASE
WHEN T.col1 > 0
THEN 1
ELSE 0
END) AS 'C'
FROM dbo.t1 T
END
END

Combine multiple lines onto one line

I have the following query
SELECT orderno,
CASE WHEN param_id = 'variant' THEN param_val END AS 'variant',
CASE WHEN param_id = 'period_from' THEN param_val END AS'period_from',
CASE WHEN param_id = 'period_to' THEN param_val END AS'period_to',
CASE WHEN param_id = 'division' THEN param_val END AS 'division',
CASE WHEN param_id = 'show_div' THEN param_val END AS 'show_div',
CASE WHEN param_id = 'group_div' THEN param_val END AS 'group_div',
FROM orderreport
order by orderno
This returns a grid similar to the below (there are another number of columns which I have removed for the purpose of the question) There is also an infinite number of order nos
orderno | variant | period_from | period_to | division | show_div | group_div
3 | AH003 | NULL | NULL | NULL | NULL | NULL
3 | NULL | 201300 | NULL | NULL | NULL | NULL
3 | NULL | NULL | 201304 | NULL | NULL | NULL
3 | NULL | NULL | NULL | SALES | NULL | NULL
3 | NULL | NULL | NULL | NULL | Y | NULL
3 | NULL | NULL | NULL | NULL | NULL | Y
My desired output is as the below, no matter what i have tried I am stumped.
orderno | variant | period_from | period_to | division | show_div | group_div
3| AH003 | 201300 | 201304 | SALES | Y | Y
Your query is very close, I would add an aggregate function around your CASE expressions and then a GROUP BY:
SELECT orderno,
max(CASE WHEN param_id = 'variant' THEN param_val END) AS variant,
max(CASE WHEN param_id = 'period_from' THEN param_val END) AS period_from,
max(CASE WHEN param_id = 'period_to' THEN param_val END) AS period_to,
max(CASE WHEN param_id = 'division' THEN param_val END) AS division,
max(CASE WHEN param_id = 'show_div' THEN param_val END) AS show_div,
max(CASE WHEN param_id = 'group_div' THEN param_val END) AS group_div
FROM orderreport
group by orderno
order by orderno