Interview : update table values using select statement - sql

Interviewer asked me one question, which seems very easy, but I couldn't figure out, how to solve this
Name | Gender
--------------
A | F
B | M
C | F
D | F
E | M
From the above data, gender was wrongly entered, which means in place of F it should be M and in place of M it should F. How to update whole table with a single line sql query (don't use pl/sql block). Since, if I will update gender column one by one, then possible error would be all rows values of gender column becomes either F or M.
Final output should be
Name | Gender
--------------
A | M
B | F
C | M
D | M
E | F

Try this..
Update TableName Set Gender=Case when Gender='M' Then 'F' Else 'M' end
On OP request..update using Select...
Update TableName T Set Gender=(
Select Gender from TableName B where T.Gender!=B.Gender and rownum=1);
SQL FIDDLE DEMO

update table_name
set gender = case when gender = 'F' then 'M'
when gender = 'M' then 'F'
end
SQL works on Set theory principles, so updates are happening in parallel, you don't
need Temporary storage to store the values before overwriting like we do in
other programming language while swapping two values.

The right way to do such an update is as Amit singh first answered.
But if you really want to have a select statement in your update (have know idea why), then you can do something like this:
update table1 t
set Gender = (select case when i.Gender = 'F' Then 'M' else 'F' end
from table1 i
where i.Name = t.Name);
Here is a sqlfiddle demo

sql server example but same applies
declare #Table TABLE
(
Id int,
Value char(1)
)
insert into #Table
select 1, 'F'
union select 2, 'F'
union select 3, 'F'
union select 4, 'M'
union select 5, 'M'
union select 6, 'M'
select * from #Table
update #Table set Value = case when Value = 'F' then 'M' when Value = 'M' then 'F' else Value End
select * from #Table

you can try this:-
update [table] a
set Gender=(select case when gender='F' then 'M' else 'F' end from [table] b
where a.name=b.name)
above query will match the names and will update gender accordingly.

Related

combining boolean results from two columns SQL

I have the following example data from two Boolean columns:
ID Male Female
1 1 0
2 0 1
3 0 1
4 1 0
5 0 1
I would like to combine the two columns into a single column containing just 'M' and 'F'. Also, I would preferably like to do it in the SELECT statement I am writing while defining the column.
The result then should be something like:
ID Gender
1 M
2 F
3 F
4 M
5 F
I know I could achieve this with separate update statements like:
UPDATE table_1
SET Gender='M'
FROM table_2 t2
WHERE t2.Male=1
and
UPDATE table_1
SET Gender='F'
FROM table_2 t2
WHERE t2.Female=1
But I was really hoping to achieve the same result while declaring the Gender column?
Does anyone know if this is possible?
Thanks in advance!
You can use a CASE expression.
Query
UPDATE t1
SET t1.Gender = (
CASE WHEN t2.Male = 1 AND t2.Female = 0 THEN 'M'
WHEN t2.Male = 0 AND t2.Female = 1 THEN 'F'
ELSE NULL END
)
FROM Table_1 t1
JOIN Table_2 t2
ON t1.ID = t2.ID;
UPDATE table_1
SET Gender= CASE WHEN Male = 1 THEN 'M'
WHEN Female = 1 THEN 'F'
ELSE 'Other' // optional
END;
Of course im trying to be open mind and guess you allow Male = 0 and Female = 0
otherwise you can simplify with IIF
UPDATE table_1
SET Gender = IIF ( Male = 1, 'M', 'F' );
Here is how to do it with a SELECT statement:
SELECT ID,
CASE WHEN Male = 1 THEN 'M'
ELSE 'F' END AS gender
FROM My_Table
How about select if(Male = 1, 'M', 'F') as gender?

SQL - Tags searching query

I have following table in my database:
ID name
1 x
2 x
3 y
1 y
1 z
Now I want to select only this objects (ID's) which has both 'x' and 'y' value s tag name. In this case this will be only record with ID = 1 because sought values set ('x' and 'y') is subset of this record possible names set ('x', 'y' and 'z').
How to write a SQL query?
Thanks for help :)
One method uses aggregation:
select id
from t
where name in ('x', 'y')
group by id
having count(*) = 2;
If you care about performance you might want to compare this to:
select id
from t tx join
t ty
on tx.id = ty.id and tx.name = 'x' and ty.name = 'y';
The first version is easier to generalize to more tags. Under some circumstances, the second might have better performance.

SQL Counting the number of occurence based on a subject

I find it hard to word what I am trying to achieve. I have a table that looks like this:
user char
---------
a | x
a | y
a | z
b | x
b | x
b | y
c | y
c | y
c | z
How do I write a query that would return me the following result?
user x y z
-------
a |1|1|1|
b |2|1|0|
c |0|2|1|
the numbers represent the no of occurences of chars in the original table
EDIT:
The chars values are unknown hence the solution cannot be restricted to these values. Sorry for not mentioning it sooner. I am using Oracle DB but planning to use JPQL to construct the query.
select user,
sum(case when char='x' then 1 else 0 end) as x,
sum(case when char='y' then 1 else 0 end) as y,
sum(case when char='z' then 1 else 0 end) as z
from thetable
group by user
Or, if you don't mind stacking vertically, this solution will give you a solution that works even with unknown sets of characters:
select user, char, count(*) as count
from thetable
group by user, char
This will give you:
user char count
a x 1
a y 1
a z 1
b x 2
If you want to string an unknown set of values out horizontally (as in your demo output), you're going to need to get into dynamic queries... the SQL standard is not designed to generate output with an unknown number of columns... Hope this is helpful!
Another option, using T-SQL PIVOT (SQL SERVER 2005+)
select *
from userchar as t
pivot
(
count([char]) for [char] in ([x],[y],[z])
) as p
Result:
user x y z
----------- ----------- ----------- -----------
a 1 1 1
b 2 1 0
c 0 2 1
(3 row(s) affected)
Edit ORACLE:
You can build a similar PIVOT table using ORACLE.
The tricky part is that you need the right column names in the IN ([x],[y],[z],...) statement. It shouldn't be too hard to construct the SQL query in code, getting a (SELECT DISTINCT [char] from table) and appending it to your base query.
Pivoting rows into columns dynamically in Oracle
If you don't know the exact values on which to PIVOT, you'll either need to do something procedural or mess with dynamic sql (inside an anonymous block), or use XML (in 11g).
If you want the XML approach, it would be something like:
with x as (
select 'a' as usr, 'x' as val from dual
union all
select 'a' as usr, 'y' as val from dual
union all
select 'b' as usr, 'x' as val from dual
union all
select 'b' as usr, 'x' as val from dual
union all
select 'c' as usr, 'z' as val from dual
)
select * from x
pivot XML (count(val) as val_cnt for val in (ANY))
;
Output:
USR VAL_XML
a <PivotSet><item><column name = "VAL">x</column><column name = "VAL_CNT">1</column></item><item><column name = "VAL">y</column><column name = "VAL_CNT">1</column></item></PivotSet>
b <PivotSet><item><column name = "VAL">x</column><column name = "VAL_CNT">2</column></item></PivotSet>
c <PivotSet><item><column name = "VAL">z</column><column name = "VAL_CNT">1</column></item></PivotSet>
Hope that helps

SQL using if statement in stored procedure to update a table

I have a table Students with two fields, StudentName and Grade.
I am trying to write a stored procedure to update the Grade. If the student has an A, I want to change it to B. If they have a B, I want to change it to A. If they have anything else I want to leave it alone. Here is my best attempt
create procedure sp_changegrades
if Grade = 'A' update Students set Grade = 'B'
else if Grade = 'B' update Students set Grade = 'A'
just use CASE
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A' THEN 'B'
WHEN Grade = 'B' THEN 'A'
ELSE Grade -- "If they have anything else I want to leave it alone."
END
)
or
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A'
THEN 'B'
ELSE 'A'
END
)
WHERE Grade IN ('A','B')
You can utilize a Case statement, and add a where clause so you only update the relavant rows.
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A' THEN 'B'
WHEN Grade = 'B' THEN 'A'
ELSE Grade -- "Included for Completeness, should never be utilized."
END
)
WHERE Grade in ('A','B')
you can write smth like this. In this solution you define rules of updating in join part and then updating.
create procedure sp_changegrades
as
begin
update Students set
Grade = G.Grade_New
from Students as S
inner join (values
('A', 'B'),
('B', 'A')
) as G(Grade_Old, Grade_New) on G.Grade_Old = S.Grade
end
or you can use case
create procedure sp_changegrades
as
begin
update Students set
Grade =
case Grade
when 'A' then 'B'
when 'B' then 'A'
else Grade
end
end

SQL Join Tables

Table one contains
ID|Name
1 Mary
2 John
Table two contains
ID|Color
1 Red
2 Blue
2 Green
2 Black
I want to end up with is
ID|Name|Red|Blue|Green|Black
1 Mary Y Y
2 John Y Y Y
Thanks for any help.
Thanks for the responses. I'm going to re-post this with some additional info about exactly what I'm trying to do that may complicate this. Can someone close this?
If you use T-SQL you can use PIVOT (http://msdn.microsoft.com/en-us/library/ms177410.aspx)
Here is query I used:
declare #tbl_names table(id int, name varchar(100))
declare #tbl_colors table(id int, color varchar(100))
insert into #tbl_names
select 1, 'Mary'
union
select 2, 'John'
insert into #tbl_colors
select 1, 'Red'
union
select 1, 'Blue'
union
select 2, 'Green'
union
select 2, 'Blue'
union
select 2, 'Black'
select name,
case when [Red] is not null then 'Y' else '' end as Red,
case when [Blue] is not null then 'Y' else '' end as Blue,
case when [Green] is not null then 'Y' else '' end as Green,
case when [Black] is not null then 'Y' else '' end as Black
from
(
select n.id, name, color from #tbl_names n
inner join #tbl_colors c on n.id = c.id
) as subq
pivot
(
min(id)
FOR color IN ([Red], [Blue], [Green], [Black])
) as pvt
And here is output:
John Y Y Y
Mary Y Y
I can use a CASE statement with a subquery to input the Y values.
select ID, Name,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Red') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Blue') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Green') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Black') then
'Y'
else
NULL
end
from Names N
I think you're going to have to end up with something like this :
SELECT t1.ID,
t1.Name,
CASE
WHEN red.ID IS NULL THEN ''
ELSE 'Y'
END As Red,
CASE
WHEN blue.ID IS NULL THEN ''
ELSE 'Y'
END As Blue
FROM Table1 t1
LEFT JOIN Table2 Red
ON t1.ID = Red.ID AND Red.Color = 'Red'
LEFT JOIN Table2 Blue
ON t1.ID = Blue.ID AND Blue.Color = 'Blue'
MS Sql does not support PIVOT queries like MS Access.
As other commenters have pointed out, you don't display exactly how you are linking people and colors. If you are using a linking table (person_id, color_id) then there is no way to solve this problem in standard SQL since it requires a pivot or cross-tabulation, which is not part of standard SQL.
If you are willing to add the condition that the number of colors is limited and known and design time, you could come up with a solution using one join for each color and CASE or IF functions in the SQL. But that would not be elegant and, furthermore, I wouldn't trust that condition to stay true for very long.
If you are able to come up with a different way of storing the color linking information you might have more options for producing the output you want, but a different storage technique implies some degree of denormalization of the database which could well cause other difficulties.
Otherwise, you will have to do this in a stored procedure or application code.
Contrary to what some other posters have said; I see no need for a third table. If colors are a well known enumeration in you application then you don't need a "Color" table.
What you are looking for is a PIVOT like this one.