Combining two columns data into one using SQL Server 2008 R2 - sql

I have a table:
create table test
(
cola varchar(10),
colb varchar(10)
)
With some records:
cola colb
------------
A B
C D
E F
G H
I want to show the result in the following format:
columnName
----------
A
C
E
G
B
D
F
H
Attempt:
select cola+colb as columnName from test;
Gives me:
columnName
------------
AB
CD
EF
GH

You need to use the UNION operation for this task. Here's your required query.
select cola as columnName
from test
union
select colb as columnName
from test
NOTE: UNION will keep only one record in case of duplicate values between cola and colb. If you want to see the duplicate values repeated in your result, use the UNION ALL operation instead.

Related

Where clause between union all in sql?

I have a query that vertically expands data by using Union condition. Below are the 2 sample tables:
create table #temp1(_row_ord int,CID int,_data varchar(10))
insert #temp1
values
(1,1001,'text1'),
(2,1001,'text2'),
(4,1002,'text1'),
(5,1002,'text2')
create table #temp2(_row_ord int,CID int,_data varchar(10))
insert #temp2
values
(1,1001,'sample1'),
(2,1001,'sample2'),
(4,1002,'sample1'),
(5,1002,'sample2')
--My query
select * from #temp1
union
select * from #temp2 where CID in (select CID from #temp1)
order by _row_ord,CID
drop table #temp1,#temp2
So my current output is:
I want to group the details of every client together for which I am unable to use 'where' clause across Union condition.
My desired output:
Any help?! Order by is also not helping me.
I can imagine you want all of the rows for a CID sorted by _row_ord from the first table before the ones from the second table. And the CID should be the outermost sort criteria.
If that's right, you can select literals from your tables. Let the literal for the first table be less than that of the second table. Then first sort by CID, then that literal and finally by _row_ord.
SELECT cid,
_data
FROM (SELECT 1 s,
_row_ord,
cid,
_data
FROM #temp1
UNION ALL
SELECT 2 s,
_row_ord,
cid,
_data
FROM #temp2) x
ORDER BY cid,
s,
_row_ord;
db<>fiddle
If I correctly understand your need, you need the output to be sorted the way that #temp1 rows appear before #temp2 rows for each cid value.
What you could do is generate additional column ordnum assigning values for each table, just for sorting purposes, and then get rid of it in the outer select statement.
select cid, _data
from (
select 1 as ordnum, *
from #temp1
union all
select 2 as ordnum, *
from #temp2 t2
where exists (
select 1
from #temp1 t1
where t1.cid = t2.cid
)
) q
order by cid, ordnum
I have also rewritten your where condition for an equivalent which should work faster using exists operator.
Live DEMO - click me!
Output
cid _data
1001 text1
1001 text2
1001 sample1
1001 sample2
1002 text1
1002 text2
1002 sample1
1002 sample2
Use With. here is my first try with your sql
create table #temp1(_row_ord int,CID int,_data varchar(10))
insert #temp1
values
(1,1001,'text1'),
(2,1001,'text2'),
(4,1002,'text1'),
(5,1002,'text2')
create table #temp2(_row_ord int,CID int,_data varchar(10))
insert #temp2
values
(1,1001,'sample1'),
(2,1001,'sample2'),
(4,1002,'sample1'),
(5,1002,'sample2');
WITH result( _row_ord, CID,_data) AS
(
--My query
select * from #temp1
union
select * from #temp2 where CID in (select CID from #temp1)
)
select * from tmp order by CID ,_data
drop table #temp1,#temp2
result
_row_ord CID _data
1 1001 sample1
2 1001 sample2
1 1001 text1
2 1001 text2
4 1002 sample1
5 1002 sample2
4 1002 text1
5 1002 text2
Union is placed between two result set blocks and forms a single result set block. If you want a where clause on a particular block you can put it:
select a from a where a = 1
union
select z from z
select a from a
union
select z from z where z = 1
select a from a where a = 1
union
select z from z where z = 1
The first query in a union defines column names in the output. You can wrap an output in brackets, alias it and do a where on the whole lot:
select * from
(
select a as newname from a where a = 1
union
select z from z where z = 2
) o
where o.newname = 3
It is important to note that a.a and z.z will combine into a new column, o.newname. As a result, saying where o.newname will filter on all rows from both a and z (the rows from z are also stacked into the newname column). The outer query knows only about o.newname, it knows nothing of a or z
Side note, the query above produces nothing because we know that only rows where a.a is 1 and z.z is 2 are output by the union as o.newname. This o.newname is then filtered to only output rows that are 3, but no rows are 3
select * from
(
select a as newname from a
union
select z from z
) o
where o.newname = 3
This query will pick up any rows in a or z where a.a is 3 or z.z is 3, thanks to the filtering of the resulting union

SQL group data (find data family)

Please help me, I need to find out a SQL solution for grouping data using SQL Server database.
I'm pretty sure that it could be done in one SQL request but I can't see the trick.
Let' see the problem :
I have a two columns table (please see below an example). I just want to add a new column containing a number or a string which indicates the group
BEFORE :
Col1 | Col2
-----+-----
A | B
B | C
D | E
F | G
G | H
I | I
J | U
AFTER TRANSFORMATION :
Col1 | Col2 | Group
-----+------+------
A | B | 1
B | C | 1
D | E | 2
F | G | 3
G | H | 3
I | I | 4
J | U | 5
In other words: A, B, C are in the same group; D and E too; F, G, H in group 3 ....
Do you have any lookup table to get this group mapping?
Or if you just have a logic defined to decide a group, i would recommend to add a UDF which will return group for supplied values.
SELECT Col1,Col2,GetGroupID(Col1,Col2) AS Group
FROM Table
Your UDF will be something like following
CREATE FUNCTION GetGroupID
(
-- Add the parameters for the function here
#Col1 varchar(10),
#Col2 varchar(10)
)
RETURNS int
AS
BEGIN
DECLARE #groupID int
IF (#Col1="A" AND #Co2 = "B") OR (#Col1="B" AND #Co2 = "C")
BEGIN
SET #groupID = 1
END
IF #Col1="D" AND #Co2 = "E"
BEGIN
SET #groupID = 2
END
-- You can write saveral conditions in the same manner.
return #groupID
END
However, in case you have this mapping defined somewhere in another table, let us know the structure of the table and we can then update the query to join with that table instead of using UDF.
Considering the performance of the query, if the amount of data is huge in your table , it is recommended to have these mappings to one fix table and Join that table in query. Using UDF may harm performance if data amount is huge.
There is absolutely no need for a UDF here. Regardless of whether you are looking to update the table with a new column or simply pull out the data with the grouping applied, you will be best off using a set based solution, ie: create and join to a table.
I am assuming here that you don't have messy data, such as a row with Col1 = 'A' and Col2 = 'F'.
If you are able to add new tables permanently you can use the following to create your lookup table:
create table Col1Groups(Col1 nvarchar(10), GroupNum int);
insert into Col1Groups(Col1,GroupNum) values ('A',1),('B',1),('C',1),('D',2),('E',2),('F',3),('G',3),('H',3);
and then join to it:
select t.Col1
,t.Col2
,g.GroupNum
from Table t
inner join Col1Groups g
on t.Col1 = g.Col1
If you can't, you can just create a derived table via a CTE:
with Col1Groups as
(
select Col1
,GroupNum
from (values('A',1),('B',1),('C',1),('D',2),('E',2),('F',3),('G',3),('H',3)) as x(Col1,GroupNum)
)
select t.Col1
,t.Col2
,g.GroupNum
from Table t
inner join Col1Groups g
on t.Col1 = g.Col1
You get the first rows per group with
select col1, col2 from mytable where col1 not in (select col2 from mytable) or col1 = col2;
We can give these rows numbers with
rank() over (order by col1) as grp
Now we must iterate through the rows to find the ones belonging to those first ones, then those belonging to these, etc. A recursive query.
with cte(col1, col2, grp) as
(
select col1, col2, rank() over (order by col1) as grp
from mytable where col1 not in (select col2 from mytable) or col1 = col2
union all
select mytable.col1, mytable.col2, cte.grp
from cte
join mytable on mytable.col1 = cte.col2
where mytable.col1 <> mytable.col2
)
select * from cte
order by grp, col1;
Additional answer for a more flexible approach
Originally you asked for chains A|B -> B|C, F|G -> G|H etc., but in your comment to my other answer you introduced forks like A|B -> B|C, B|D and I've adjusted my answer.
If you want to go one step further and introduce net-like relations such as A|B -> B|C, D|C, we can no longer follow chains forward only (in the example D belongs to the A group, because though A doesn't lead to D directly, it leads to C and D also leads to C. Here is a way to solve this:
Get all letters from the table (no matter whether in col1 or col2). Then for each of them find related letters (again no matter whether in col1 or col2). And for these again find related letters and so on. That will give you complete groups. But duplicates (as D is in the A group, A is in the D group also), which you can get rid of by simply taking the smallest (or greatest) group key per letter. Then join the Groups to the table.
The query:
with cte(col, grp) as
(
select col, rownum as grp from
(select col1 as col from mytable union select col2 from mytable)
union all
select case when mytable.col1 = cte.col then mytable.col2 else mytable.col1 end, cte.grp
from cte
join mytable on cte.col in (mytable.col1, mytable.col2)
where mytable.col1 <> mytable.col2
)
cycle col set is_cycle to 'y' default 'n'
select mytable.col1, mytable.col2, x.grp
from mytable
join (select col, min(grp) as grp from cte group by col) x on x.col = mytable.col1
order by grp, col;

Oracle SQL - return record only if colB is the same for all of colA

I have a table like the following ( there is of course other data in the table):
Col A Col B
1 Red
1 Red
2 Blue
2 Green
3 Black
I am trying to return a value for Col A only when ALL the Col B values match, otherwise return null.
This will be used as part of another sql statement that will be passing the Col A value, ie
Select * from Table where Col A = 1
I need to return the value in Col B. The correct result in the above table would be Red,Black
any ideas ?
how about this?
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table t( id number, color varchar2(20));
insert into t values(1,'RED');
insert into t values(1,'RED');
insert into t values(2,'BLUE');
insert into t values(2,'GREEN');
insert into t values(3,'BLACK');
Query 1:
select color from t where id in (
select id
from t
group by id having min(color) = max(color) )
group by color
Results:
| COLOR |
|-------|
| RED |
| BLACK |
If you just want the values in A (rather than each row), then use group by:
select a
from table t
group by a
having min(b) = max(b);
Note: this ignores NULL values. If you want to treat them as an additional value, then add another condition:
select a
from table t
group by a
having min(b) = max(b) and count(*) = count(b);
It is also tempting to use count(distinct). In general, though, count(distinct) requires more processing effort than a min() and a max().
You can use a case statement.
select cola,
case when max(colb) = min(colb) and count(*) = count(colb) then max(colb)
end as colb
from tablename
group by cola
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table t( id number, color varchar2(20));
insert into t values(1,'RED');
insert into t values(1,'RED');
insert into t values(2,'BLUE');
insert into t values(2,'GREEN');
insert into t values(3,'BLACK');
Query 1:
select id
from t
group by id having min(color) = max(color)
Results:
| ID |
|----|
| 1 |
| 3 |
hope this is what you were looking for.. :)

Update grouped records with sequence in oracle

I have following structure of data in Oracle table
COL1 COL2 COL2 GRP_ID
A A B 1
A A B 1
A A C 2
A A B 1
A D E 3
A D E 3
F G H 4
F G H 4
Basically each unique combination of col1, col2 and col3 has different value in GRP_ID column.
I need to replace value in GRP_ID column with database sequence value such that (assuming next value of sequence is 235678):
COL1 COL2 COL2 GRP_ID
A A B 235678
A A B 235678
A A C 235679
A A B 235678
A D E 235680
A D E 235680
F G H 235681
F G H 235681
There are millions of records in the table so I do not want to go through a loop. Reason to use database sequence is that the number provided by sequence will be exposed to customer and therefore it should not repeat when next communication is sent to customer.
Is there a way to do this through SQL?
Thanks!
Don't know if is the fastest way but it works.
CREATE FUNCTION NEXT
RETURN NUMBER IS
v_nextval NUMBER;
BEGIN
v_nextval := NEW_SEQUENCE.nextval;
RETURN(v_nextval);
END;
/
UPDATE EXAMPLE
SET EXAMPLE.GroupID =
(
SELECT G.GroupID FROM
(
SELECT B.Column1, B.Column2, B.Column3, MY_SCHEMA.NEXT() AS GroupID
FROM EXAMPLE B
GROUP BY B.Column1, B.Column2, B.Column3
) G
Where G.Column1 = EXAMPLE.Column1 AND G.Column2 = EXAMPLE.Column2
AND G.Column3 = EXAMPLE.Column3);
SELECT *
FROM EXAMPLE
Basically you have to do a distinct or group by to get all the different groups that you have and use the sequence in a function or you will get the sequence number is not allowed here error from oracle and then do an update.
See complete example on sqlfiddle
http://sqlfiddle.com/#!4/bf261/8
How about:
begin transaction
Get next sequence in a temp var
Disable sequence
update tablename set grp_ID = grp_ID + tmpVar-1
Select into tempVar max(grp_ID) from tableName
Re-seed sequence based value now in tempvar+1
Re-enable sequence
commit and end transaction
But i'm confused by something when you add additional records later, they can't simply do sequence.nextval because it's only needed when col1,col2,col3 doesn't already exist, otherwise it should use the group_ID for that unique combination... just seems odd, doable but odd.

SQL Query to retrieve rows having conditional values in a column

I don't know if this question has been asked previously also. If so please direct me to the link.
I have a table that has three columns name, type and date. Type can only be 4 values A, B, C and D
I want to fetch all those records which are of type A, B or C but the condition is that it should only fetch if the same name also has a type of D.
e.g. lets consider this table
Name type Date
abc A 5/7
abc B 6/7
abc D 7/7
xyz A 5/7
xyz D 6/7
lmn A 5/7
lmn B 6/7
lmn C 7/7
So the deal here I need the following result set
ABC 5/7
ABC 6/7
XYZ 5/7
Because ABC and XYZ has a type D the other records of ABC and XYZ are shown. Since lmn does not have a type D it's not included in the result set.
To test if a record exist, you can simply use where exists :
select * from mytable t1 where exists (
select * from mytable t2 where t1.Name=t2.Name and t2.type="D"
);
That's probably self explanatory but here's a reference : http://dev.mysql.com/doc/refman/5.0/en/exists-and-not-exists-subqueries.html
If you want to exclude the D records, you do this :
select * from mytable t1 where t1.type<>"D" and exists (
select * from mytable t2 where t1.Name=t2.Name and t2.type="D"
);
Try this:
SELECT Name, Date
FROM MyTable as mt
WHERE type != 'D'
AND EXISTS
(
SELECT * FROM MyTable as mt2
WHERE mt2.type = 'D' and mt2.name = mt.name
)
You are selecting all records where type is not equal to D and that have a record with a matching name where type IS equal to D
create view all_D as select name from your_table where type=D
select * from your_table where type<>D and name in (select * from all_D)
You could even make it such that instead of having that view you jut put that query in the brackets after "not in"