Insert values from different rows into one row and multiple columns grouping by specific value of column x - oracle

Sorry if Im not clear in title, I have some language problems. I have a table (quite big) like
Person value Letter
------------------------------------------
Tom value1 A
Tom value2 T
Ann value1 F
Ann value2 R
Ann value3 Y
Jim value3 W
I would like to shorten it into:
Person value1 value2 value3
------------------------------------------
Tom A T (null)
Ann F R Y
Jim (null) (null) W
Something like listagg, but into different columns. Looks simple but I'm stuck...
edit: There are 8 values, not just 3, I wanted made it simplier, values don't repeat for the same person (but can be null/not appear))

Here's one option: the idea is to use an aggregate function (such as MIN, MAX, SUM). As your sample data contain only 3 values, that's what I did as well - you'd use 5 more lines like this (including them from line #13 onward).
Note that lines #1 - 8 represent your sample data; you already have them stored in the table so you wouldn't be typing that. Code you actually need begins at line #9.
SQL> with test (person, value, letter) as
2 (select 'Tom', 'val1', 'A' from dual union all
3 select 'Tom', 'val2', 'T' from dual union all
4 select 'Ann', 'val1', 'F' from dual union all
5 select 'Ann', 'val2', 'R' from dual union all
6 select 'Ann', 'val3', 'Y' from dual union all
7 select 'Jim', 'val3', 'W' from dual
8 )
9 select
10 person,
11 max(decode(value, 'val1', letter)) value1,
12 max(decode(value, 'val2', letter)) value2,
13 max(decode(value, 'val3', letter)) value3
14 from test
15 group by person
16 order by person;
PERSON VALUE1 VALUE2 VALUE3
------ ------ ------ ------
Ann F R Y
Jim W
Tom A T
SQL>

Related

Combine 2 queries to get data in same row in Oracle

I have created 2 queries result set is like below:
Query 1:
ID Name Value Choice
1 abc 10 x
2 def 20 x
Query 2:
ID1 Name1 Value1 Choice1
1 eft 10 y
2 bgf 20 y
I have added a full outer join and result set is like
ID Name Value Choice ID1 Name1 Value1 Choice1
1 abc 10 x null null null null
2 def 20 x null null null null
null null null null 1 eft 10 y
null null null null 2 bgf 20 y
But I need like below:
ID Name Value Choice ID1 Name1 Value1 Choice1
1 abc 10 x 1 eft 10 y
2 def 20 x 2 bgf 20 y
Not sure on what join or logic should I use.
Not sure what your FULL JOIN condition looks like. However, if you want to "get records side by side" (as stated in your comment) you could do something like ...
select *
from (
select 1 as id, 'abc' as name, 10 as value, 'x' as choice from dual union all
select 2, 'def', 20, 'x' from dual
) Q1 full outer join (
select 1 as id1, 'eft' as name1, 10 as value1, 'y' as choice1 from dual union all
select 2, 'bgf', 20, 'y' from dual
) Q2
on Q1.id = Q2.id1
;
-- result
ID NAME VALUE CHOICE ID1 NAME1 VALUE1 CHOICE1
1 abc 10 x 1 eft 10 y
2 def 20 x 2 bgf 20 y
That, of course will give you some NULLs for IDs that don't "match". Eg (same join, data different)
select *
from (
select 1 as id, 'abc' as name, 10 as value, 'x' as choice from dual union all
select 2, 'def', 20, 'x' from dual union all
select 4, '_4_', 40, 'g' from dual
) Q1 full outer join (
select 1 as id1, 'eft' as name1, 10 as value1, 'y' as choice1 from dual union all
select 2, 'bgf', 20, 'y' from dual union all
select 5, '_5_', 50, 'z' from dual
) Q2
on Q1.id = Q2.id1
;
ID NAME VALUE CHOICE ID1 NAME1 VALUE1 CHOICE1
1 abc 10 x 1 eft 10 y
2 def 20 x 2 bgf 20 y
NULL NULL NULL NULL 5 _5_ 50 z
4 _4_ 40 g NULL NULL NULL NULL
Ponder Stibbon's (thank you!) suggested solution, would probably remove some of the NULLs, but you may end up having records with different IDs in some of the resultset's rows.
select *
from (
select id, name, value, choice, rownum row_
from (
select 1 as id, 'abc' as name, 10 as value, 'x' as choice from dual union all
select 2, 'def', 20, 'x' from dual union all
select 4, '_4_', 40, 'g' from dual
)
) Q1 full join (
select id1, name1, value1, choice1, rownum row_
from (
select 1 as id1, 'eft' as name1, 10 as value1, 'y' as choice1 from dual union all
select 2, 'bgf', 20, 'y' from dual union all
select 5, '_5_', 50, 'z' from dual
)
) Q2 on Q1.row_ = Q2.row_
;
-- result
ID NAME VALUE CHOICE ROW_ ID1 NAME1 VALUE1 CHOICE1 ROW_
1 abc 10 x 1 1 eft 10 y 1
2 def 20 x 2 2 bgf 20 y 2
4 _4_ 40 g 3 5 _5_ 50 z 3
You never showed us your original two queries, but I will assume that we can just wrap them as CTEs. Given that, what you seem to need here is actually an inner join, not a full outer join:
WITH cte1 AS (
-- first query
),
cte2 AS (
-- second query
)
SELECT t1.ID, t1.Name, t1."Value", t1.Choice, t2.ID1, t2.Name1, t2.Value1, t2.Choice1
FROM cte2 t1
INNER JOIN cte2 t2
ON t1.ID = t2.ID1;

select matching string from another table oracle

I have a database (Oracle) Table A with some strings in one of columns, Now I want to get matching records from Table B against each column value of Table A for example,
Table A
Name
-----------
ABC
DEE
GHI
JKL
Table B
Name
-----------
ABC
DEF
GHI
JKL
MNO
PQR
Now i want that each string in Table A must be checked against Table B's column and if some string is found almost identical then it should appear against original Value as per below
Table OutPut
Name Matched
--------|----------
ABC | ABC
DEE | DEF
GHI | GHI
JKL | JKL
I have tried following query
with data as(
SELECT Name FROM TABLE_A UNION ALL
SELECT Name FROM TABLE_B
)
SELECT Name
FROM
(
SELECT t.*,utl_match.edit_distance_similarity(upper(Name),upper('DEE')) eds
FROM data t
ORDER BY eds DESC
)
WHERE rownum = 1
but problem is that using this query i can check only one record at a time and that too against a hard coded string. Is there any way to check whole column from Table A one by one against Table B and produce result in output against each string.
Not too clever (hint: performance issue, but - see if it helps. Might be OK if there aren't too many rows involved.
You need lines 21 onwards.
I set similarity to be greater than 80 - adjust it, if needed (which is very probable, as data you posted is really sample data).
SQL> WITH ta (name)
2 AS (SELECT 'ABC' FROM DUAL
3 UNION ALL
4 SELECT 'DEE' FROM DUAL
5 UNION ALL
6 SELECT 'GHI' FROM DUAL
7 UNION ALL
8 SELECT 'JKL' FROM DUAL),
9 tb (name)
10 AS (SELECT 'ABC' FROM DUAL
11 UNION ALL
12 SELECT 'DEF' FROM DUAL
13 UNION ALL
14 SELECT 'GHI' FROM DUAL
15 UNION ALL
16 SELECT 'JKL' FROM DUAL
17 UNION ALL
18 SELECT 'MNO' FROM DUAL
19 UNION ALL
20 SELECT 'PQR' FROM DUAL)
21 SELECT ta.name,
22 tb.name,
23 UTL_MATCH.jaro_winkler_similarity (ta.name, tb.name) sim
24 FROM ta, tb
25 WHERE UTL_MATCH.jaro_winkler_similarity (ta.name, tb.name) > 80
26 ;
NAM NAM SIM
--- --- ----------
ABC ABC 100
DEE DEF 82
GHI GHI 100
JKL JKL 100
SQL>

oracle calculate the average of the current and previous row values

I have the following table which contains a single column VALUE1, I would like to calculate the average between the previous row and current row in VALUE1 and represent it in a second column VALUE2 starting from the second row i.e. The first row value will not be averaged.
The result should look like
ID VALUE1 VALUE2
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
NOTE: For first row (ID = 1) I average the first row with it self.
Any help appreciated. Thanks in advance.
You can also use the AVG() analytic function with a window of the previous and current row:
WITH practicenew AS (SELECT 1 ID, 3 value1 FROM dual UNION ALL
SELECT 2 ID, 4 value1 FROM dual UNION ALL
SELECT 3 ID, 5 value1 FROM dual UNION ALL
SELECT 4 ID, 5 value1 FROM dual UNION ALL
SELECT 5 ID, 6 value1 FROM dual UNION ALL
SELECT 6 ID, 2 value1 FROM dual)
SELECT ID,
value1,
AVG(value1) OVER (ORDER BY ID
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) value2
FROM practicenew;
ID VALUE1 VALUE2
---------- ---------- ----------
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
I think you can use this query, it gives the same output you have mentioned.
Create table script:
create table practicenew (ID number, Value1 number) ;
insert into practicenew (ID, Value1) values (1, 3) ;
insert into practicenew (ID, Value1) values (2,4) ;
insert into practicenew (ID, Value1) values (3,5);
insert into practicenew (ID, Value1) values (4,5);
insert into practicenew (ID, Value1) values (5,6);
insert into practicenew (ID, Value1) values (6,2 );
Then use NVL and Lag function. Lag will bring your previous value to current row and nvl is being used for the first row, as you will have null value in the first row while using lag.
Query: select ID, value1,nvl(((lag(value1) over (order by ID) + value1)/2),value1) as Value2
from practicenew;
Output:
ID Value1 Value2
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
I hope it helps!

rank() a group of items by count(*)

I have some problems with Oracle analytic functions and need help.
Here's a generic example:
create table test (item varchar2(10), value varchar2(10));
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value2');
insert into test values ('item1','value2');
insert into test values ('item3','value2');
insert into test values ('item3','value2');
insert into test values ('item3','value2');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value2');
insert into test values ('item5','value2');
insert into test values ('item5','value2');
select item, value, count(*) c,
sum(count(*)) over () total,
sum(count(*)) over (partition by item) total_by_item,
dense_rank() over (order by count(*) desc) dense_rank
from test
group by item, value
order by 5 desc;
The result of the query is:
ITEM VALUE C TOTAL TOTAL_BY_ITEM DENSE_RANK
---------- ---------- -- ---------- -------------- ----------
item5 value1 7 20 10 1
item5 value2 3 20 10 3
item1 value2 2 20 7 4
item1 value1 5 20 7 2
item3 value2 3 20 3 3
How can I get the items ranked by TOTAL_BY_ITEM? So it would look like this:
ITEM VALUE C TOTAL TOTAL_BY_ITEM WHAT_I_NEED
---------- ---------- -- ---------- -------------- -----------
item5 value1 7 20 10 1
item5 value2 3 20 10 1
item1 value2 2 20 7 2
item1 value1 5 20 7 2
item3 value2 3 20 3 3
Is it possible to achieve this without another join or sub-query? I have a feeling that it is possible. I naturally think that it has to be something like this: dense_rank(count(*)) over (partition by item), like with analytic SUM that I use to get the 5th column, but it doesn't work.
I don't think this is what you are searching for but just for reference without a subquery you can achieve the same result using MODEL clause:
select item, value, c, total, total_by_item, what_i_need
from test
group by item, value
model
dimension by (row_number() over (order by null) d)
measures (
item, value,
count(*) c,
sum(count(*)) over () total,
sum(count(*)) over (partition by item) total_by_item,
1 what_i_need
)
rules (
what_i_need[any] = dense_rank() over (order by total_by_item[cv()] desc)
)
order by 5 desc;
I don't think you can achieve it without subquery otherwise.

Increment column A when column B changes value

I'm using PL/SQL (Oracle) and this is the result I have from a simple select query at the moment (with an order by):
Name Hour Value
---------------------------------
Peter 1 10
Peter 2 12
Peter 3 22
Joe 1 8
Joe 2 8
Joe 3 12
Richard 1 9
Richard 2 7
Matt 1 11
In SSRS, I have a complex Matrix where I want to have alternating row colors*. Therefor, I need the rownumber, but it must be 'grouped' by the Name. So this is what I want:
Name Hour Value Row_Num
--------------------------------------------
Peter 1 10 1
Peter 2 12 1
Peter 3 22 1
Joe 1 8 2
Joe 2 8 2
Joe 3 12 2
Richard 1 9 3
Richard 2 7 3
Matt 1 11 4
Notice how the Row_Num (or whatever you want to call it) only changes when the Name changes. Is this possible in PL/SQL?
*I know of the techniques to get alternating row colors in SSRS, but I'm using a matrix and have the problem mentioned by Kyle Hale in the comments to ahmad's answer to this question.
This is easy to do with the dense_rank() function in the query used to fetch the results:
select name, hour, value,
dense_rank() over (order by name) as row_num
from t;
Note: this will not necessarily assign the values in the order you have given. But each group will get a different value. If you need them in the order given, then you will need to identify the order. SQL tables are inherently unordered, so a column is needed to specify the ordering.
select name
, value
, hour
, dense_rank() over (partition by 1 order by name) as row_num
from
(
select 'Peter' name, '1' hour , '10' value from dual union all
select 'Peter', '2', '12' from dual union all
select 'Peter', '3', '22' from dual union all
select 'Joe', '1', '8' from dual union all
select 'Joe', '2', '8' from dual union all
select 'Joe', '3', '12' from dual union all
select 'Richard','1', '9' from dual union all
select 'Richard','2', '7' from dual union all
select 'Matt', '1', '11' from dual
)
Joe 8 2 1
Joe 12 3 1
Joe 8 1 1
Matt 11 1 2
Peter 22 3 3
Peter 12 2 3
Peter 10 1 3
Richard 9 1 4
Richard 7 2 4

Resources