Select an included values - oracle

I'm using Oracle SQL and i need help with a query. Hope it's not too much easy one. I did't find an answer for it in Google.
I have a table that need to be aggregated by ID column and then to select only the records that two values are included in a certain table (and both of them).
Table for example
ID | Value
1 | Y
1 | N
2 | N
2 | N
2 | Y
3 | Y
3 | Y
4 | Y
5 | Y
5 | N
5 | Y
5 | N
The output table need to include only the IDs that both Y and N are included in Value table. Output:
ID
1
2
5

Another solution that groups by the ID and uses HAVING to return only those with > 1 DISTINCT values:
with v_data(id, value) as (
select 1, 'Y' from dual union all
select 1, 'N' from dual union all
select 2, 'Y' from dual)
select id
from v_data
group by id
having count(distinct value) > 1

select distinct a.id
from your_table a inner join your_table b on a.id = b.id and a.value != b.value;

Related

converting multiple comma separated columns into rows

I have an Oracle table which holds comma separated values in many columns. For example :
Id Column1 Column2
1 A,B,C H
2 D,E J,K
3 F L,M,N
I want to split all the columns into rows and the output should be this :
ID Column1 Column2
1 A H
1 B H
1 C H
2 D J
2 D K
2 E J
2 E K
3 F L
3 F M
3 F N
I found some suggestions which uses regexp_substr and connect by but it deals with only one column that has comma separated values. I have tried sub-query method also where I will be dealing with one column at a time in inner query and send the inner query output as input it outer query, this takes more time and the columns that hold comma separated values are more. So I cannot use the sub-query method.
For one column you can CROSS JOIN a TABLE() collection expression containing a correlated sub-query that uses a hierarchical query to split the column value up into separate strings. For two columns, you just do the same thing for the second column and the CROSS JOIN takes care of ensuring that each delimited value in column1 is paired with each delimited value in column2.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Id, Column1, Column2 ) AS
SELECT 1, 'A,B,C', 'H' FROM DUAL UNION ALL
SELECT 2, 'D,E', 'J,K' FROM DUAL UNION ALL
SELECT 3, 'F', 'L,M,N' FROM DUAL;
Query 1:
SELECT t.id,
c1.COLUMN_VALUE AS c1,
c2.COLUMN_VALUE AS c2
FROM table_name t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column1, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column1, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c1
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column2, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column2, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c2
Results:
| ID | C1 | C2 |
|----|----|----|
| 1 | A | H |
| 1 | B | H |
| 1 | C | H |
| 2 | D | J |
| 2 | D | K |
| 2 | E | J |
| 2 | E | K |
| 3 | F | L |
| 3 | F | M |
| 3 | F | N |
Below will give you an idea about to how to convert the comma separated string into rows. You can use this logic to satisfy your need.
select regexp_substr('a,b,c,v,f', '[^,]+',1,level)
from dual
connect by level <= regexp_count('a,b,c,v,f', ',') + 1;

Select onle group that have positivo and negative number in plsql

I have a table that have date grouped.
I need to select only groups that have positive and negative value inside.
For example:
id value1
2 7
2 8
2 -1
3 3
3 4
4 -1
4 -2
5 7
5 -5
the result should be
id value1
2 7
2 8
2 -1
5 7
5 -5
because the group with id 3 just have positive number and the group with id 4 just have negative number.
any idea how can I do it using case (when then) in a select or using if else inside a function. Or any other idea?
Try this.
select id,value1 FROM
(
select t.*,
count( DISTINCT SIGN (value1 ) ) OVER (PARTITION BY id ) n
from yourtable t
) WHERE n = 2
;
The Sign() function gives 1 for positive and -1 for negative numbers.
DEMO
If you group by the ID, you can use the aggregate functions MIN and MAX to find out if there are both positive and negative values. You need to decide how to treat 0 though... I have treated it as positive below :)
with your_table as(
-- Your example data here, this is not really part of the solution
select 2 as id, 7 as value1 from dual union all
select 2 as id, 8 as value1 from dual union all
select 2 as id, -1 as value1 from dual union all
select 3 as id, 3 as value1 from dual union all
select 3 as id, 4 as value1 from dual union all
select 4 as id, -1 as value1 from dual union all
select 4 as id, -2 as value1 from dual union all
select 5 as id, 7 as value1 from dual union all
select 5 as id, -5 as value1 from dual
)
select *
from your_table
where id in(select id
from your_table
group by id
having min(value1) < 0
and max(value1) >= 0);

Oracle: prioritizing results based on column’s value

I have a data-set in which there are duplicate IDs in the first column. I'm hoping to obtain a single row of data for each ID based on the second column's value. The data looks like so:
ID Info_Source Prior?
A 1 Y
A 3 N
A 2 Y
B 1 N
B 1 N
B 2 Y
C 2 N
C 3 Y
C 1 N
Specifically the criteria would call for prioritizing based on the second column's value (3 highest priority; then 1; and lastly 2): if the 'Info_Source' column has a value of 3, return that row; if there is no 3 in the second column for a given ID, look for a 1 and if found return that row; and finally if there is no 3 or 1 associated with the ID, search for 2 and return that row for the ID.
The desired results would be a single row for each ID, and the resulting data would be:
ID Info_Source Prior?
A 3 N
B 1 N
C 3 Y
row_number() over() usually solves these needs nicely and efficiently e.g.
select ID, Info_Source, Prior
from (
select ID, Info_Source, Prior
, row_number() over(partition by id order by Info_source DESC) as rn
)
where rn = 1
For prioritizing the second column's value (3 ; then 1, then 2) use a case expression to alter the raw value into an order that you need.
select ID, Info_Source, Prior
from (
select ID, Info_Source, Prior
, row_number() over(partition by id
order by case when Info_source = 3 then 3
when Infor_source = 1 then 2
else 1 end DESC) as rn
)
where rn = 1

oracle sql compare result two subselects

Let's say I have two Oracle SQL tables for my invoices. INV_HEAD for adress, date, ...
Then I have INV_POS for every position of the invoice.
INV_HEAD
--------
id
date
adr_id
total
INV_POS
---------
id
he_id
pos
art_id
quantity
price
I can list all the invoices with
SELECT he.id, he.date, po.art_id, po.quantity, po.price
FROM INV_HEAD he
JOIN INV_POS po on po.he_id = he.id
Now I want to find invoices with the same positions, not necessarily in the same order. How can I do this?
As a result I only need the INV_HEAD.id of all invoices with the same positions.
Here is same sample data:
id | he_id | pos | art_id | quantity | price
1 | 1 | 1 | 1000 | 5 | 100.00
2 | 1 | 2 | 2000 | 10 | 5000.00
3 | 2 | 1 | 2500 | 2 | 1250.00
4 | 3 | 1 | 2000 | 10 | 5000.00
5 | 3 | 2 | 1000 | 5 | 100.00
Invoice with he_id 1 and 3 have the same positions.
You can use analytic function LISTAGG to concatenate id with same position
SELECT p.pos, LISTAGG(h.id, ', ') WITHIN GROUP (ORDER BY p.pos) "Id"
FROM inv_head h, inv_pos p
where h.id=p.he_id
group by p.pos;
You will get following results
POS Id
1 | 1, 2, 3
2 | 1, 3
I don't see the reason to join on inv_head, however I sticked to your original query (probably you are having some intention in this).
You want something like (note that the next query does not work, because we cannot compare sets using =):
SELECT SELECT DISTINCT H1.ID, H2.ID
FROM INV_HEAD H1, INV_HEAD H2
WHERE H1.ID <> H2.ID AND
(SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H1.ID) =
(SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H2.ID)
But we can rethink the problem: A = B also means that A-B UNION B-A is an empty set.
So instead of A = B you can use NOT EXISTS((A MINUS B) UNION (B MINUS A))
where A is (SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H1.ID) and B is (SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H2.ID)
So your query is:
SELECT DISTINCT H1.HE_ID, H2.HE_ID
FROM INV_HEAD H1, INV_HEAD H2
WHERE H1.ID <> H2.ID
AND NOT EXISTS(
((SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H1.ID)
MINUS
(SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H2.ID))
UNION
((SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H2.ID)
MINUS
(SELECT ART_ID, QUANTITY, PRICE FROM INV_POS WHERE HE_ID = H1.ID)));
This query creates pairs of invoices that have the same positions (note that if two invoices has no positions they are considered equals).
The condition H1.ID <> H2.ID avoids pairs like (1, 1) or (3, 3). But if you have the pair (1,3) you will also have the symmetric (3,1). If you want to avoid symmetry then use H1.ID < H2.ID or H1.ID > H2.ID instead.
If you want to know the invoices with the same position than a given invoice with ID = X then use WHERE H1.ID = X AND H1.ID <> H2.ID AND.. (use <>, never use < or > in this case).

update row without considering space between two words

I am looking to replace text in column Text with format(A,B) where text contains A and B only and ignoring spaces between A and B.
Here is test data
Id Text
1 A B //should be replaced with format(A,B).
2 A B //should be replaced with format(A,B).
3 A B //should be replaced with format(A,B).
4 A 1 B //shouldn't be replaced.
5 A B 1 //should be replaced with format(A,B) 1.
I think I have to do something like
UPDATE test SET text = REPLACE(text, 'A[wild char for space]B', 'format(A,B)');
but how should I compare only for space? like % will compare everything.
You can use Oracle Regex fro this
UPDATE test SET text = REGEXP_REPLACE(testcol, '(A[:blank:]B)', 'format(A,B)')
Oracle Regex
Just a hint:
SQL> with t as (
2 select 'A B' col from dual union all
3 select 'A B' from dual union all
4 select 'A B' from dual union all
5 select 'AB' from dual union all
6 select 'A 1 B' from dual union all
7 select 'A B 1' from dual
8 )
9 select regexp_replace(t.col, 'A[[:space:]]*B', 'format(A,B)') from t
10 /
REGEXP_REPLACE(T.COL,'A[[:SPACE:]]*B','FORMAT(A,B)')
--------------------------------------------------------------------------------
format(A,B)
format(A,B)
format(A,B)
format(A,B)
A 1 B
format(A,B) 1
Sql for your scenario:
with tab(Id,Text) as
(select 1,'A B' from dual union all -- //should be replaced with format(A,B).
select 2,'B C' from dual union all -- //should be replaced with format(A,B).
select 3,'A B' from dual union all -- //should be replaced with format(A,B).
select 4,'A 2 B' from dual union all -- //shouldn't be replaced.
select 5,'Y Z 1' from dual) -- //should be replaced with format(A,B) 1.
select id,
text,
regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')') format
from tab;
output:
| ID | TEXT | FORMAT |
|----|-------------|---------------|
| 1 | A B | format(A,B) |
| 2 | B C | format(B,C) |
| 3 | A B | format(A,B) |
| 4 | A 2 B | A 2 B |
| 5 | Y Z 1 | format(Y,Z) 1 |
And the update statement becomes
UPDATE test SET text = regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')');

Resources