Union merging different columns - sql

I have two tables:
Table1:
DATAID| NAME | FACTOR
1 | Ann | 1
2 | Kate | 1
3 | Piter | 1
Table2:
DATAID| NAME | FACTOR
1 | John | 2
6 | Arse | 2
3 | Garry | 2
I would like UNION those tables and get this result:
DATAID| NAME | FACTOR
1 | Ann | 1,2
2 | Kate | 1
3 | Piter | 1,2
6 | Arse | 2
So when there's 2 rows with same dataid, I would like to get 'NAME' column from Table1 and some kind of aggregated FACTOR, for example '1,2' or 3

One method uses listagg():
select dataid, name,
listagg(factor, ',') within group (order by factor) as factors
from ((select dataid, name, factor from table1 t1
) union all
(select dataid, name, factor from table2 t2
)
) t
group by dataid, name;
Note: I notice that the names are not the same for a given id. You can choose one by using aggregation functions.
Or, if you only have one row in each table, you can use a full outer join:
select coalesce(t1.dataid, t2.dataid) as dataid,
coalesce(t1.name, t2.name) as name,
trim(leading ',' from coalesce(',' || t1.factor, ',') || coalesce(',' || t2.factor, '') as factors
from t1 full outer join
t2
on t1.dataid = t2.dataid;

Something like this should work. In your actual situation you will not need the first two CTE's (the subqueries in the WITH clause I added for testing).
with
table1 ( dataid, name, factor ) as (
select 1, 'Ann' , 1 from dual union all
select 2, 'Kate' , 1 from dual union all
select 3, 'Piter', 1 from dual
),
table2 ( dataid, name, factor ) as (
select 1, 'John' , 2 from dual union all
select 6, 'Arse' , 2 from dual union all
select 3, 'Garry', 2 from dual
),
u ( dataid, name, factor, source ) as (
select dataid, name, factor, 1 from table1
union all
select dataid, name, factor, 2 from table2
),
z ( dataid, name, factor ) as (
select dataid, first_value(name) over (partition by dataid order by source),
factor
from u
)
select dataid, name,
listagg(factor, ',') within group (order by factor) as factor
from z
group by dataid, name
order by dataid
;
Output:
DATAID NAME FACTOR
------- ----- ---------
1 Ann 1,2
2 Kate 1
3 Piter 1,2
6 Arse 2
4 rows selected.

Related

How to count the number of occurrences of a attribute in a string of another table

Im struggling with a complex SQL query, I want to count how many times attribute x in table 1 shows up in a string in table 2.
so t1 has a name attribute and t2 has an participants attribute and a project attribute
ID | Name
---------
0 | Bob
1 | Bill
2 | Jill
T2
Project | Participants
-----------------
0 | Bob, Bill
1 | Bob, Jill
2 | Bob
Output
Bob 3
Bill 1
Jill 1
Participants in t2 is a string. Is there anyway to do this?
You should primarily focus on fixing your data model. Each participant to each project should be stored as a separate row rather than munged in a delimited string, possibly referencing the other table through a foreign key constraint:
project_id participant_id
0 0 -- Bob
0 1 -- Bill
1 0 -- Bob
1 2 -- Jill
2 0 -- Bob
Then you could efficiently phrase the query:
select t1.*
(select count(*) from table2 t2 where t2.participant_id = t1.id) cnt
from table1 t1
That said, given your current layout, one option uses string functions:
select t1.*,
(
select count(*)
from table2 t2
where ', ' || t2.participants || ', ' like '%, ' || t1.name || ', %'
) cnt
from table1 t1
If the participants only appear once in each project then you can use:
SELECT t1.Name,
COUNT( t2.Participants) AS cnt
FROM t1
LEFT OUTER JOIN t2
ON ( ', ' || t2.Participants || ', ' LIKE '%, ' || t1.Name || ', %' )
GROUP BY t1.Name
Which, for the sample data:
CREATE TABLE t1 ( ID, Name ) AS
SELECT 0, 'Bob' FROM DUAL UNION ALL
SELECT 1, 'Bill' FROM DUAL UNION ALL
SELECT 2, 'Jill' FROM DUAL UNION ALL
SELECT 3, 'Tim' FROM DUAL;
CREATE TABLE T2 ( Project, Participants ) AS
SELECT 0, 'Bob, Bill' FROM DUAL UNION ALL
SELECT 1, 'Bob, Jill' FROM DUAL UNION ALL
SELECT 2, 'Bob' FROM DUAL;
Outputs:
NAME | CNT
:--- | --:
Bill | 1
Jill | 1
Bob | 3
Tim | 0
db<>fiddle here

SQL theory: Filtering out duplicates in one column, picking lowest value in other column

I am trying to figure out the best way to remove rows from a result set where either the value in one column or the value in a different column has a duplicate in the result set.
Imagine the results of a query are as follows:
a_value | b_value
-----------------
1 | 1
2 | 1
2 | 2
3 | 1
4 | 3
5 | 2
6 | 4
6 | 5
What I want to do is:
Eliminate all rows that have duplicate values in a_value
Pick only 1 row for a given b_value
So I'd want the filtered results to end up like this after eliminating a_value duplicates:
a_value | b_value
-----------------
1 | 1
3 | 1
4 | 3
5 | 2
And then like this after picking only a single b_value:
a_value | b_value
-----------------
1 | 1
4 | 3
5 | 2
I'd appreciate suggestions on how to accomplish this task in an efficient way via SQL.
with
q_res ( a_value, b_value ) as (
select 1, 1 from dual union all
select 2, 1 from dual union all
select 2, 2 from dual union all
select 3, 1 from dual union all
select 4, 3 from dual union all
select 5, 2 from dual union all
select 6, 4 from dual union all
select 6, 5 from dual
)
-- end test data; solution begins below
select min(a_value) as a_value, b_value
from (
select a_value, min(b_value) as b_value
from q_res
group by a_value
having count(*) = 1
)
group by b_value
order by a_value -- ORDER BY is optional
;
A_VALUE B_VALUE
------- -------
1 1
4 3
5 2
1) In the inner query I am avoiding all duplicates which are present in a_value
column and getting all the remaining rows from input table and storing them
as t2. By joining t2 with t1 there would be full data without any dups as per
your #1 in requirement.
SELECT t1.*
FROM Table t1,
(
SELECT a_value
FROM Table
GROUP BY a_value
HAVING COUNT(*) = 1
) t2
WHERE t1.a_value = t2.a_value;
2) Once the filtered data is obtained, I am assigning rank to each row in the filtered dataset obtained in step-1 and I am selecting only rows with rank=1.
SELECT X.a_value,
X.b_value
FROM
(
SELECT t1.*,
ROW_NUMBER() OVER ( PARTITION BY t1.b_value ORDER BY t1.a_value,t1.b_value ) AS rn
FROM Table t1,
(
SELECT a_value
FROM Table
GROUP BY a_value
HAVING COUNT(*) = 1
) t2
WHERE t1.a_value = t2.a_value
) X
WHERE X.rn = 1;

In Oracle, how to select specific row while aggregating all rows

I have a requirement that I need to both aggregate all rows by id, and find 1 specific row among the rows of the same id. It's like 2 SQL queries, but I want to make it in 1 SQL query. I'm using Oracle database.
for example,table t1 whose data looks like:
id | name | num
----- -------- -------
1 | 'a' | 1
2 | 'b' | 3
2 | 'c' | 6
2 | 'd' | 6
I want to aggregate the data by the id, find the 'name' with the highest 'count', and sum all count of the id to 'total_count'.
There are 2 rows with same num, pick up the first one.
id | highest_num | name_of_highest_num | total_num | avg_num
----- ------------- --------------------- ------------ -------------------
1 | 1 | 'a' | 1 | 1
2 | 6 | 'c' | 15 | 5
Can I get this result by 1 Oracle SQL query?
Thanks in advance for any replies.
Oracle Setup:
CREATE TABLE table_name ( id, name, num ) AS
SELECT 1, 'a', 1 FROM DUAL UNION ALL
SELECT 2, 'b', 3 FROM DUAL UNION ALL
SELECT 2, 'c', 6 FROM DUAL UNION ALL
SELECT 2, 'd', 6 FROM DUAL;
Query:
SELECT id,
MAX( num ) AS highest_num,
MAX( name ) KEEP ( DENSE_RANK LAST ORDER BY num ) AS name_of_highest_num,
SUM( num ) AS total_num,
AVG( num ) AS avg_num
FROM table_name
GROUP BY id
Output:
ID HIGHEST_NUM NAME_OF_HIGHEST_NUM TOTAL_NUM AVG_NUM
-- ----------- ------------------- --------- -------
1 1 a 1 1
2 6 d 15 5
Here's one option using row_number in a subquery with conditional aggregation:
select id,
max(num) as highest_num,
max(case when rn = 1 then name end) as name_of_highest_num,
sum(num) as total_num,
avg(num) as avg_num
from (
select id, name, num,
row_number() over (partition by id order by num desc) rn
from a
) t
group by id
SQL Fiddle Demo
Sounds like you want to use some analytic functions. Something like this should work
select id,
num highest_num,
name name_of_highest_num,
total total_num,
average avg_num
from (select id,
num,
name,
rank() over (partition by id
order by num desc, name asc) rnk,
sum(num) over (partition by id) total,
avg(num) over (partition by id) average
from table t1)
where rnk = 1

How to select distinct records from individual column in sql table

I want to retrieve distinct rows from each column in this sql table
My table
1 Apple
2 Banana
3 Apple
2 Apple
1 Orange
I want the result like this:
1 Apple
2 Banana
3 Orange
Please help me with this
You can get the distinct names by doing:
select distinct name
from table t;
You can add the first column by doing:
select row_number() over (order by name) as id, name
from (select distinct name
from table t
) t;
Most databases support the ANSI standard row number. You haven't tagged this with the database, so that is the most general solution.
EDIT:
Oh, you want two columns each with values. I would approach this as a full outer join:
select nu.num, na.name
from (select num, row_number() over (order by num) as seqnum
from table
group by num
) nu full outer join
(select name, row_number() over (order by name) as seqnum
from table t
group by name
) na
on nu.seqnum = na.seqnum;
Each subquery enumerates the values in each column. The full outer join makes sure that you have values even when they are missing on one side or the other.
Please try it
select *,row=rank() over(order by name) from (SELECT distinct name FROM abc) as cte
or
with cte as
(
SELECT distinct name FROM abc
)
select *,row=rank() over(order by name) from cte
Output
| row | Name |
|-----------|----------|
| 1 | Apple |
| 2 | Banana |
| 3 | Orange |
Proof of concept.
Tested on Oracle 11.2
WITH
MyTable (firstName, lastName) AS (
SELECT '1', 'Apple' FROM DUAL UNION ALL
SELECT '2', 'Banana' FROM DUAL UNION ALL
SELECT '3', 'Apple' FROM DUAL UNION ALL
SELECT '2', 'Apple' FROM DUAL UNION ALL
SELECT '4', 'Apple' FROM DUAL UNION ALL
SELECT '1', 'Orange' FROM DUAL),
Ranked AS (
SELECT DISTINCT
firstName
, DENSE_RANK() OVER (ORDER BY firstName) AS fnRnk
, lastName
, DENSE_RANK() OVER (ORDER BY lastName) AS lnRnk
FROM MyTable
)
SELECT
DISTINCT R1.firstName, R2.lastName FROM Ranked R1 FULL OUTER JOIN Ranked R2 ON R1.fnRnk = R2.lnRnk ORDER BY lastName NULLS LAST, firstName
;
Returns
| FIRSTNAME | LASTNAME |
|-----------|----------|
| 1 | Apple |
| 2 | Banana |
| 3 | Orange |
| 4 | (null) |
Added a row to the original data to demonstrate columns of different length.
SQL Fiddle

tSQL UNPIVOT of comma concatenated column into multiple rows

I have a table that has a value column. The value could be one value or it could be multiple values separated with a comma:
id | assess_id | question_key | item_value
---+-----------+--------------+-----------
1 | 859 | Cust_A_1 | 1,5
2 | 859 | Cust_B_1 | 2
I need to unpivot the data based on the item_value to look like this:
id | assess_id | question_key | item_value
---+-----------+--------------+-----------
1 | 859 | Cust_A_1 | 1
1 | 859 | Cust_A_1 | 5
2 | 859 | Cust_B_1 | 2
How does one do that in tSQL on SQL Server 2012?
We have a user defined function that we use for stuff like this that we called "split_delimiter":
CREATE FUNCTION [dbo].[split_delimiter](#delimited_string VARCHAR(8000), #delimiter_type CHAR(1))
RETURNS TABLE AS
RETURN
WITH cte10(num) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,cte100(num) AS
(
SELECT 1
FROM cte10 t1, cte10 t2
)
,cte10000(num) AS
(
SELECT 1
FROM cte100 t1, cte100 t2
)
,cte1(num) AS
(
SELECT TOP (ISNULL(DATALENGTH(#delimited_string),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM cte10000
)
,cte2(num) AS
(
SELECT 1
UNION ALL
SELECT t.num+1
FROM cte1 t
WHERE SUBSTRING(#delimited_string,t.num,1) = #delimiter_type
)
,cte3(num,[len]) AS
(
SELECT t.num
,ISNULL(NULLIF(CHARINDEX(#delimiter_type,#delimited_string,t.num),0)-t.num,8000)
FROM cte2 t
)
SELECT delimited_item_num = ROW_NUMBER() OVER(ORDER BY t.num)
,delimited_value = SUBSTRING(#delimited_string, t.num, t.[len])
FROM cte3 t;
GO
It will take a varchar value up to 8000 characters and will return a table with the delimited elements broken into rows. In your example, you'll want to use an outer apply to turn those delimited values into separate rows:
SELECT my_table.id, my_table.assess_id, question_key, my_table.delimited_items.item_value
FROM my_table
OUTER APPLY(
SELECT delimited_value AS item_value
FROM my_database.dbo.split_delimiter(my_table.item_value, ',')
) AS delimited_items