SQL Join Tables - sql

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.

Related

Search SQL and return true or false

I have a table that has thousands of rows in. I need to check if certain values exists in the table or not.
I want to list all the bar codes I am searching with a flag of true or false returned if there is one.
I have come up with this so far:
SELECT CASE WHEN EXISTS (
SELECT *
FROM TABLE
WHERE Coulmn in ('a','b', 'c', 'd', 'e', 'f', 'g')
)
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT) END
This however just returns a value of 1.
So in the table I have
coulmn
----------
A
B
D
E
F
G
I want to do a search that returns the following
Coulmn | Exsists
-----------------
A | True
B | True
C | False
D | True
E | True
F | True
G | True
You can use a query like the following:
SELECT t1.v,
CASE WHEN t2.col IS NOT NULL THEN 'true' ELSE 'false' END AS Exists
FROM (
SELECT 'a' AS v UNION ALL SELECT 'b' UNION ALL SELECT 'c' UNION ALL SELECT 'd'
UNION ALL SELECT 'e' UNION ALL SELECT 'f' UNION ALL SELECT 'g') AS t1
LEFT JOIN mytable AS t2 ON t1.v = t2.col
This works:
SELECT *, CASE WHEN (Column in ('1','2')) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS result_field
FROM TABLE;
NOTE: Tested in PostgreSQL
As it's written, the outer select is select case when exists () then 1 else 0 end... so it is only going to return one row. The outer select must include "Column" AND "Exists" (select column, ...) to return two columns.
A "where" clause will never return a "false" like this, though, because "column" has to be in a real table for the query to actually return it. As #jarlh says, you'll need a helper table to store the columns you're looking for:
Create table SearchColumns (SearchColumn char(1));
insert into SearchColumns (SearchColumn)
values ('A'), ('B'), ('C'), ('D'), ('E'), ('F'), ('G'), ('H')
Then you can do the If Exists to your table from that table to see which values are in or not in:
select SearchColumn, case when exists
(select * from TABLE where Table.Column = SearchColumns.SearchColumn)
then 'True' else 'False' end as ExistsStatus
from SearchColumns
I think that will get you what you want. This gets a) Only one record per column no matter how many times it occurs in your table and b) "True" and "False" for every column value you're looking for. If you really wanted a Bit, you can use 0 and 1 and the casting from the original query, but they actually show "0" and "1"; and c) this should work no matter how many values you have.
(Note, I assumed some of those were spelling errors, so I made adjustments, but they were consistent so I'm not certain).
With the help form above I created a temp table and then implemented one of the soultions shared.
CREATE TABLE #Temp
(
Barcode VARCHAR (100)
)
INSERT INTO #Temp
VALUES
(1),
(2),
(3),
(4 )
select barcode, case when exists
(select * from CIPKORHHTProductDetails where CIPKORHHTProductDetails.Barcode = #temp.barcode)
then 'True' else 'False' end as ExistsStatus
from #temp order by ExistsStatus DESC

SQL join 1 line and return NULLs for others

Is it possible to stop SQL from duplicating lines from a table when creating a JOIN to a table with more than one line?
Table 1
Car Name Colour Size
Car 1 Red big
car 2 Blue small
Car 3 Green small
Table 2
Car Name Part Number
Car 1 123456
Car 1 234567
Car 1 345678
Car 2 ABCDEFG
Car 2 BCDEFGH
Car 2 CDEFGHI
Then Join Table 1 with Table 2 on "Car Name" but only have the information once from each table,
Resulting SQL View
Car Name Colour Size Part Number
Car 1 Red big 123456
NULL NULL NULL 234567
NULL NULL NULL 345678
Car 2 Blue small ABCDEFG
NULL NULL NULL BCDEFGH
NULL NULL NULL CDEFGHI
edit: if the original "Car Name" column is duplicated this isn't a problem, that's not really made clear above because i've put NULL's under that column but i understand that's the column its joined on and that information is already on the lines of the second table, its more being able to stop the duplication of the other information that isn't in table 2
As mentioned in comments it is recommend to do this kind of data formatting on client application but still if need a sql answer then try something like this.
WITH cte
AS (SELECT RN=Row_number() over(PARTITION BY a.Car_Name ORDER BY (SELECT NULL)),
a.car_name,
a.color,
a.size,
b.part_number
FROM table1
INNER JOIN table2
ON a.car_name = b.car_name)
SELECT car_name=CASE WHEN rn = 1 THEN car_name ELSE NULL END,
color=CASE WHEN rn = 1 THEN color ELSE NULL END,
size=CASE WHEN rn = 1 THEN size ELSE NULL END,
part_number
FROM cte
You would just need to assign a ROW_NUMBER() and populate table1 fields only when row number = 1 something like this:
WITH q1
AS (
SELECT t2.*
,row_number() OVER (
PARTITION BY t2.CarName ORDER BY PartNumber
) AS rn
FROM table2 t2
)
SELECT CASE
WHEN q1.rn = 1
THEN t1.CarName
ELSE NULL
END AS CarName
,CASE
WHEN q1.rn = 1
THEN t1.Colour
ELSE NULL
END AS Colour
,CASE
WHEN q1.rn = 1
THEN t1.Size
ELSE NULL
END AS Size
,q1.PartNumber
FROM q1
INNER JOIN table1 t1 ON t1.carname = q1.carname
SQL Fiddle Demo
try,
declare #t table(CarName varchar(10),Colour varchar(10), Size varchar(10))
insert into #t (carname,Colour,Size) values
('Car 1', 'Red', 'big'),
('car 2','Blue','small'),
('car 3','Gr een','small')
declare #t1 table(CarName varchar(10),part varchar(10))
insert into #t1 (carname,part) values
('car 1','123456'),
('car 1','2345670'),
('car 1','345678'),
('car 2','ABCDEFG'),
('car 2','BcDEFGH'),
('car 2','cDEFGHI')
select t.*, t1.part from #t t right join
(select carname,part, row_number() over (partition by carname order by part) rno from #t1)
t1 on t.CarName=(case when t1.rno=1 then t1.CarName else null end)

SQL using CASE in SELECT with GROUP BY. Need CASE-value but get row-value

so basicially there is 1 question and 1 problem:
1. question - when I have like 100 columns in a table(and no key or uindex is set) and I want to join or subselect that table with itself, do I really have to write out every column name?
2. problem - the example below shows the 1. question and my actual SQL-statement problem
Example:
A.FIELD1,
(SELECT CASE WHEN B.FIELD2 = 1 THEN B.FIELD3 ELSE null FROM TABLE B WHERE A.* = B.*) AS CASEFIELD1
(SELECT CASE WHEN B.FIELD2 = 2 THEN B.FIELD4 ELSE null FROM TABLE B WHERE A.* = B.*) AS CASEFIELD2
FROM TABLE A
GROUP BY A.FIELD1
The story is: if I don't put the CASE into its own select statement then I have to put the actual rowname into the GROUP BY and the GROUP BY doesn't group the NULL-value from the CASE but the actual value from the row. And because of that I would have to either join or subselect with all columns, since there is no key and no uindex, or somehow find another solution.
DBServer is DB2.
So now to describing it just with words and no SQL:
I have "order items" which can be divided into "ZD" and "EK" (1 = ZD, 2 = EK) and can be grouped by "distributor". Even though "order items" can have one of two different "departements"(ZD, EK), the fields/rows for "ZD" and "EK" are always both filled. I need the grouping to consider the "departement" and only if the designated "departement" (ZD or EK) is changing, then I want a new group to be created.
SELECT
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END) AS ZD,
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END) AS EK,
TABLE.DISTRIBUTOR,
sum(TABLE.SOMETHING) AS SOMETHING,
FROM TABLE
GROUP BY
ZD
EK
TABLE.DISTRIBUTOR
TABLE.DEPARTEMENT
This here worked in the SELECT and ZD, EK in the GROUP BY. Only problem was, even if EK was not the designated DEPARTEMENT, it still opened a new group if it changed, because he was using the real EK value and not the NULL from the CASE, as I was already explaining up top.
And here ladies and gentleman is the solution to the problem:
SELECT
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END) AS ZD,
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END) AS EK,
TABLE.DISTRIBUTOR,
sum(TABLE.SOMETHING) AS SOMETHING,
FROM TABLE
GROUP BY
(CASE WHEN TABLE.DEPARTEMENT = 1 THEN TABLE.ZD ELSE null END),
(CASE WHEN TABLE.DEPARTEMENT = 2 THEN TABLE.EK ELSE null END),
TABLE.DISTRIBUTOR,
TABLE.DEPARTEMENT
#t-clausen.dk: Thank you!
#others: ...
Actually there is a wildcard equality test.
I am not sure why you would group by field1, that would seem impossible in your example. I tried to fit it into your question:
SELECT FIELD1,
CASE WHEN FIELD2 = 1 THEN FIELD3 END AS CASEFIELD1,
CASE WHEN FIELD2 = 2 THEN FIELD4 END AS CASEFIELD2
FROM
(
SELECT * FROM A
INTERSECT
SELECT * FROM B
) C
UNION -- results in a distinct
SELECT
A.FIELD1,
null,
null
FROM
(
SELECT * FROM A
EXCEPT
SELECT * FROM B
) C
This will fail for datatypes that are not comparable
No, there's no wildcard equality test. You'd have to list every field you want tested individually. If you don't want to test each individual field, you could use a hack such as concatenating all the fields, e.g.
WHERE (a.foo + a.bar + a.baz) = (b.foo + b.bar + b.az)
but either way, you're listing all of the fields.
I might tend to solve it something like this
WITH q as
(SELECT
Department
, (CASE WHEN DEPARTEMENT = 1 THEN ZD
WHEN DEPARTEMENT = 2 THEN EK
ELSE null
END) AS GRP
, DISTRIBUTOR
, SOMETHING
FROM mytable
)
SELECT
Department
, Grp
, Distributor
, sum(SOMETHING) AS SumTHING
FROM q
GROUP BY
DEPARTEMENT
, GRP
, DISTRIBUTOR
If you need to find all rows in TableA that match in TableB, how about INTERSECT or INTERSECT DISTINCT?
select * from A
INTERSECT DISTINCT
select * from B
However, if you only want rows from A where the entire row matches the values in a row from B, then why does your sample code take some values from A and others from B? If the row matches on all columns, then that would seem pointless. (Perhaps your question could be explained a bit more fully?)

Interview : update table values using select statement

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.

Select records based on column priority

First of all, the title of this question is horrible, but I didn't find a better way to describe my issue.
There's probably a very easy way to do this, but I couldn't figure it out. This is very similar to this question, but I'm running on sqlite3 (iOS) so I suspect my options are much more limited.
I have a table with product records. All records have an ID (note: I'm not talking about the row ID, but rather an identification number unique to each product). Some products may have two entries in the table (both with the same ID). The only difference would be in a special column (let's say column COLOUR can be either RED or GREEN).
What I want to do is create a list of unique products based on the value of COLOUR, with priority to GREEN if both GREEN and RED records exist for the same product.
In short, if I have the following case:
id PRODUCT ID COLOUR
1 1001 GREEN
2 1002 GREEN
3 1002 RED
4 1003 RED
I would like my SELECT to return the rows 1, 2 and 4. How can I achieve this?
My current approach is to have separate tables, and do the join manually, but obviously this is a very bad idea..
Note: I've tried to use the same approach from here:
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION ALL
SELECT *
FROM xx
WHERE id not in (SELECT distinct id
FROM xx
WHERE f_COLOUR = "GREEN");
But the result I'm getting is rows 1,2,3,4 instead of just 1,2,4. What am I missing?
Edit: One more question please: how can this be made to work with a subset of records, ie. if instead of the entire table I wanted to filter some records?
For example, if I had something like SELECT * FROM table WHERE productID LIKE "1%" ... how can I retrieve each unique product, but still respecting the colour priority (GREEN>RED)?
Your query is nearly correct. Just use PRODUCTID and not ID.
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION
SELECT *
FROM xx
WHERE PRODUCTID not in
(SELECT PRODUCTID
FROM xx
WHERE f_COLOUR = "GREEN");
SQLFiddle Demo
Try this
SELECT *
FROM xx
WHERE COLOUR = 'GREEN'
UNION
SELECT *
FROM xx WHERE P_Id not in
(SELECT P_Id
FROM Persons
WHERE COLOUR = 'GREEN');
See ALSO SQL FIDDLE DEMO
I just want to offer that you can do this with a group by:
select (case when sum(case when colour = 'Green' then 1 else 0 end) > 0
then max(case when colour = 'Green' then id end)
else max(case when colour = 'Red' then id end)
end) as id,
product_id
(case when sum(case when colour = 'Green' then 1 else 0 end) > 0 then 'Green'
else 'Red'
end) as colour
from t
group by product_id
You can have it like this
WITH PriorityTable
AS
(
SELECT T.*,
ROW_NUMBER() OVER (PARTITION BY T.ID
ORDER BY PT.ColorPriority ) PriorityColumn
FROM XX AS T
INNER JOIN (
SELECT 'RED' AS f_COLOUR , 1 AS ColorPriority
UNION
SELECT 'GREEN' AS f_COLOUR , 2 AS ColorPriority
) AS PT
ON T.f_COLOUR = PT.f_COLOUR
)
SELECT * FROM PriorityTable
WHERE PriorityColumn = 1