Oracle SQL query using case when, compacting null fields - sql

I have a table like this:
Items
id group old_new object
1 A O pen
2 A N house
3 B O dog
4 B O cat
5 C N mars
6 C O sun
7 C N moon
8 C o earth
I would like the select return:
Items
group new_object old_object
A house pen
B null dog
B null cat
C mars sun
C moon earth
If I try:
select id,
case when old_new = 'N' then object end as new_object,
case when old_new = 'O' then object end as old_object
from the_table
order by id;
I have 8 row with many field as null
es: last rows:
group new_object old_object
C mars null
c null sun
C moon null
c null earth
But of group C I want only 2 rows...
is not like the other query 'Oracle sql join same table ecc...' because here don't want null result

I'm going to make the assumption that Old and New records are paired in the order they appear based on the ID value. With that assumption the following query:
WITH DTA(ID, GRP, OLD_NEW, OBJECT) AS (
select 1, 'A', 'O', 'pen' from dual union all
select 2, 'A', 'N', 'house' from dual union all
select 3, 'B', 'O', 'dog' from dual union all
select 4, 'B', 'O', 'cat' from dual union all
select 5, 'C', 'N', 'mars' from dual union all
select 6, 'C', 'O', 'sun' from dual union all
select 7, 'C', 'N', 'moon' from dual union all
select 8, 'C', 'O', 'earth' from dual
), dta2 as (
select dta.*
, row_number() over (partition by GRP, old_new order by id) rn
from dta
)
select coalesce(n.grp, o.grp) grp
, n.object new_object
, o.object old_object
from (select * from dta2 where old_new = 'N') n
full join (select * from dta2 where old_new = 'O') o
on n.grp = o.grp
and n.rn = o.rn;
Aside from the sample data section (with dta) this script first uses the analytic function ROW_NUMBER() to add a sequential number partitioned by the group and old_new columns. It then performs a full outer join on two inline views of the dta2 subfactored query, one for thr old objects and one for the new objects. The result, at least for this data set is:
GRP NEW_OBJECT OLD_OBJECT
--- ---------- ----------
A house pen
B dog
B cat
C mars sun
C moon earth

In the first step assign an index (IDX) of the chnage withing your group. I'm using order by ID, but this is upon you. The important thing is that the old and new valuea are unique connected with GRP and IDX.
In next step let PIVOT work for you (I'm using the data from #Sentinel, thx!)
WITH DTA(ID, GRP, OLD_NEW, OBJECT) AS (
select 1, 'A', 'O', 'pen' from dual union all
select 2, 'A', 'N', 'house' from dual union all
select 3, 'B', 'O', 'dog' from dual union all
select 4, 'B', 'O', 'cat' from dual union all
select 5, 'C', 'N', 'mars' from dual union all
select 6, 'C', 'O', 'sun' from dual union all
select 7, 'C', 'N', 'moon' from dual union all
select 8, 'C', 'O', 'earth' from dual
), DTA2 as (
SELECT
ROW_NUMBER() OVER (PARTITION BY GRP,OLD_NEW order by ID) as IDX,
GRP, OLD_NEW, OBJECT
from DTA
)
select * from DTA2
PIVOT (max(OBJECT) OBJECT for (OLD_NEW) in
('N' as "NEW",
'O' as "OLD"
))
order by GRP;
result
IDX, GRP, NEW_OBJECT, OLD_OBJECT
1 A house pen
1 B dog
2 B cat
2 C moon earth
1 C mars sun

Here's an alternative using PIVOT to get the results:
with items as (select 1 id, 'A' grp, 'O' old_new, 'pen' obj from dual union all
select 2 id, 'A' grp, 'N' old_new, 'house' obj from dual union all
select 3 id, 'B' grp, 'O' old_new, 'dog' obj from dual union all
select 4 id, 'B' grp, 'O' old_new, 'cat' obj from dual union all
select 5 id, 'C' grp, 'N' old_new, 'mars' obj from dual union all
select 6 id, 'C' grp, 'O' old_new, 'sun' obj from dual union all
select 7 id, 'C' grp, 'N' old_new, 'moon' obj from dual union all
select 8 id, 'C' grp, 'O' old_new, 'earth' obj from dual)
-- end of mimicking your items table with data in it. See SQL below:
select grp,
new_object,
old_object
from (select grp,
old_new,
obj,
row_number() over (partition by grp, old_new order by id) rn
from items)
pivot (max(obj)
for old_new in ('N' new_object,
'O' old_object))
order by grp,
rn;
GRP NEW_OBJECT OLD_OBJECT
--- ---------- ----------
A house pen
B dog
B cat
C mars sun
C moon earth

Provided that
there's at most one new object for each old,
there's no new object without old object, and
there's at most one old object for any group (this is not true for your sample data, but in comments you indicate you're interested in such solution as well)
a simpler query may be used than for the general case:
select
old.group as group, new.object as new_object, old.object as old_object
from
(select group, object from my_table where old_new = 'O') old
left join
(select group, object from my_table where old_new = 'N') new
on (old.group = new.group)

Related

Rank and partition query in SQL

I have a table in AS400 as below
Type Values Status
A 1 Y
A 2 N
A 3 Y
A 4 Y
A 5 N
B 2 Y
B 7 N
C 3 Y
C 5 N
C 4 Y
C 6 Y
C 7 Y
C 1 Y
D 3 Y
D 5 Y
E 7 N
E 4 N
E 3 Y
E 6 N
E 7 Y
E 8 N
What I need is Top 2 of each type that have a status Y. I.e. the result should be something like A 1 , A 3, B 2 , C3, C4, D3, D5, E3, E7.
The query I have used is this
SELECT type,
REFERENCES
FROM (
SELECT type,
REFERENCES,
STATUS,
rank() OVER (PARTITION BY Type ORDER BY REFERENCES DESC) AS Rank
FROM Tables
) a
WHERE rank <= 2
AND Type IN (A,B,C,D,E)
AND STATUS = Y;
The issue here is it doesn't filter out the status beforehand. It picks up the top 2, then filters out with Y. So the result looks something like A1 instead of A1 and A3, because it has first picked A1 and A2 , and then filtered out A2 .
Where do I insert the Status=y to get a more accurate result.
I am a novice in SQL so if theres a better way to write the above query as well, I am fine with that.
This outta do it. Use the with clause to feed the filtered result into a new query which then you can rank.
with testtable (type, value, status) as (
select 'A', 1, 'Y' from dual union all
select 'A', 2, 'N' from dual union all
select 'A', 3, 'Y' from dual union all
select 'A', 4, 'Y' from dual union all
select 'A', 5, 'N' from dual union all
select 'B', 2, 'Y' from dual union all
select 'B', 7, 'N' from dual union all
select 'C', 3, 'Y' from dual union all
select 'C', 5, 'N' from dual union all
select 'C', 4, 'Y' from dual union all
select 'C', 6, 'Y' from dual union all
select 'C', 7, 'Y' from dual union all
select 'C', 1, 'Y' from dual union all
select 'D', 3, 'Y' from dual union all
select 'D', 5, 'Y' from dual union all
select 'E', 7, 'N' from dual union all
select 'E', 4, 'N' from dual union all
select 'E', 3, 'Y' from dual union all
select 'E', 6, 'N' from dual union all
select 'E', 7, 'Y' from dual union all
select 'E', 8, 'N' from dual
)
, ys as (
select
*
from testtable
where STATUS = 'Y'
)
, yrank as (
select
type,
value,
status,
rank() over(partition by type order by value) Y_RANK
from ys
)
select
*
from yrank
where Y_RANK <= 2

Update Oracle SQL - table with values from duplicates

i hope that somebody could help. I need to update a table from a select with duplicates.
ID;CLASS;VALUE;NEW
1;a;a3;
1;b;s6;
1;c;b99;
2;a;s3;
2;b;r6;
2;c;b99;
3;a;s5;
4;a;r6;
4;b;a3;
Look at my example table, there is a colum NEW which i have to update. In the example the column NEW was filled manually.
Here is the goal (as shown in table col NEW):
1.find duplicates via ID (HAVING COUNT(*) >1 or something like that)
UPDATE TABLE SET NEW=
CLASS || '_' || VALUE
WHERE CLASS='a' or 'b'
Easy for you?
Thx in advance
The logic behind is not completely clear; this could be a way.
setup:
create table yourTable(id, class, value, new) as
(
select 1, 'a', 'a3', cast (null as varchar2(10)) from dual union all
select 1, 'b', 's6', null from dual union all
select 1, 'c', 'b99', null from dual union all
select 2, 'a', 's3', null from dual union all
select 2, 'b', 'r6', null from dual union all
select 2, 'c', 'b99', null from dual union all
select 3, 'a', 's5', null from dual union all
select 4, 'a', 'r6', null from dual union all
select 4, 'b', 'a3', null from dual
)
query:
merge into yourTable t1
using (
select listagg(value, '_') within group (order by class) as new,
id
from yourTable
where class in ('a', 'b')
group by id
having count(distinct class) = 2
) t2
on ( t1.id = t2.id
and t1.class in ('a', 'b')
)
when matched then
update set t1.new = t2.new
result:
SQL> select *
2 from yourTable;
ID C VAL NEW
---------- - --- ----------
1 a a3 a3_s6
1 b s6 a3_s6
1 c b99
2 a s3 s3_r6
2 b r6 s3_r6
2 c b99
3 a s5
4 a r6 r6_a3
4 b a3 r6_a3
9 rows selected.

T-SQL ORDER BY base on MIN of a group's column

Hi take the following data as an example
id | value
----------
A | 3
A | 9
B | 7
B | 2
C | 4
C | 5
I want to list out all the data base on the min value of each id group, so that the expected output is
id | value
----------
B | 2
B | 7
A | 3
A | 9
C | 4
C | 5
i.e. min of group A is 3, group B is 2, group C is 4, so group B first and then the rest of group B in ascending order. Next group A and then group C
I tried this but thats not what I want
SELECT * FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
GROUP BY id, value
ORDER BY MIN(value)
Please help! Thank you
SELECT * FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
ORDER BY MIN(value) OVER(PARTITION BY id), id, value
OVER Clause (Transact-SQL)
Add the over() clause to your query output and you can see what it does for you.
SELECT *,
MIN(value) OVER(PARTITION BY id) OrderedBy FROM (
SELECT 'A' AS id, '3' AS value
UNION SELECT 'A', '9' UNION SELECT 'B', '7' UNION SELECT 'B', '2'
UNION SELECT 'C', '4' UNION SELECT 'C', '5') data
ORDER BY MIN(value) OVER(PARTITION BY id), id, value
Result:
id value OrderedBy
---- ----- ---------
B 2 2
B 7 2
A 3 3
A 9 3
C 4 4
C 5 4

Calculating data point which have Precision of 99%

We have a table which have millions of entry. The table have two columns, now there is correlation between X and Y when X is beyond a value, Y tends to be B (However it is not always true, its a trend not a certainty).
Here i want to find the threshold value for X, i.e(X1) such that at least 99% of the value which are less than X1 are B.
It can be done using code easily. But is there a SQL query which can do the computation.
For the below dataset expected is 6 because below 6 more than 99% is 'B' and there is no bigger value of X for which more than 99% is 'B'. However if I change it to precision of 90% then it will become 12 because if X<12 more than 90% of the values are 'B' and there is no bigger value of X for which it holds true
So we need to find the biggest value X1 such that at least 99% of the value lesser than X1 are 'B'.
X Y
------
2 B
3 B
3 B
4 B
5 B
5 B
5 B
6 G
7 B
7 B
7 B
8 B
8 B
8 B
12 G
12 G
12 G
12 G
12 G
12 G
12 G
12 G
13 G
13 G
13 B
13 G
13 G
13 G
13 G
13 G
14 B
14 G
14 G
Ok, I think this accomplishes what you want to do, but it will not work for the data volume you are mentioning. I'm posting it anyway in case it can help someone else provide an answer.
This may be one of those cases where the most efficient way is to use a cursor with sorted data.
Oracle has some builting functions for correlation analysis but I've never worked with it so I don't know how they work.
select max(x)
from (select x
,y
,num_less
,num_b
,num_b / nullif(num_less,0) as percent_b
from (select x
,y
,(select count(*) from table b where b.x<a.x) as num_less
,(select count(*) from table b where b.x<a.x and b.y = 'B') as num_b
from table a
)
where num_b / nullif(num_less,0) >= 0.99
);
The inner select does the following:
For every value of X
Count the nr of values < X
Count the nr of 'B'
The next SELECT computes the ratio of B's and filter only the rows where the ratio is above the threshold. The outer just picks the max(x) from those remaining rows.
Edit:
The non-scalable part in the above query is the semi-cartesian self-joins.
This is mostly inspired from the previous answer, which had some flaws.
select max(next_x) from
(
select
count(case when y='B' then 1 end) over (order by x) correct,
count(case when y='G' then 1 end) over (order by x) wrong,
lead(x) over (order by x) next_x
from table_name
)
where correct/(correct + wrong) > 0.99
Sample data:
create table table_name(x number, y varchar2(1));
insert into table_name
select 2, 'B' from dual union all
select 3, 'B' from dual union all
select 3, 'B' from dual union all
select 4, 'B' from dual union all
select 5, 'B' from dual union all
select 5, 'B' from dual union all
select 5, 'B' from dual union all
select 6, 'G' from dual union all
select 7, 'B' from dual union all
select 7, 'B' from dual union all
select 7, 'B' from dual union all
select 8, 'B' from dual union all
select 8, 'B' from dual union all
select 8, 'B' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 12, 'G' from dual union all
select 13, 'G' from dual union all
select 13, 'G' from dual union all
select 13, 'B' from dual union all
select 13, 'G' from dual union all
select 13, 'G' from dual union all
select 13, 'G' from dual union all
select 13, 'G' from dual union all
select 13, 'G' from dual union all
select 14, 'B' from dual union all
select 14, 'G' from dual union all
select 14, 'G' from dual;
Give a try with this and share the results:
Assuming table name as table_name and columns as x and y
with TAB AS (
select (count(x) over (PARTITION BY Y order by x rows between unbounded preceding and current row))/
(COUNT(case when y='B' then 1 end) OVER (PARTITION BY Y)) * 100 CC, x, y
from table_name)
select x,y from (SELECT min(cc) over (partition by y) min_cc, x, cc, y
FROM TAB
where cc >= 99)
where min_cc = cc

How to find a specific pattern in some Access data?

I have ex 17 rows and 2 columns in my tabel. Like this:
ColA ColB
---- ----
X 1
X 2
X 3
X a
Y 1
Y 2
Y a
Z 4
Z 4
Z b
Q 1
Q 2
Q 3
Q a
W 4
W b
W 5
Is there a way to look for a pattern in colB of 1,2,3,a for the same value of ColA?
That would give me at output of X and Q.
Your sample data shows distinct rows. In that case, you can use this GROUP BY query.
SELECT y.ColA
FROM YourTable AS y
WHERE y.ColB In ('1','2','3','a')
GROUP BY y.ColA
HAVING Count(*) = 4;
If your actual data might include duplicate rows, you can start with SELECT DISTINCT in a subquery before applying the GROUP BY.
SELECT sub.ColA
FROM
(
SELECT DISTINCT y.ColA, y.ColB
FROM YourTable AS y
WHERE y.ColB In ('1','2','3','a')
) AS sub
GROUP BY sub.ColA
HAVING Count(*) = 4;
(I've assumed that your table is named [PatternData].)
If you use Allen Browne's ConcatRelated function you can create a query to "string together" all of the [ColB] values for each distinct value of [ColA] like this...
SELECT
ColA,
ConcatRelated("ColB", "PatternData", "ColA=""" & ColA & """" , "ColB", "") AS ColB_values
FROM (SELECT DISTINCT ColA FROM PatternData)
...returning...
ColA ColB_values
---- -----------
Q 123a
W 45b
X 123a
Y 12a
Z 44b
Then you can use the above query as the basis for a query to find the [ColA] values with the desired pattern
SELECT ColA
FROM
(
SELECT
ColA,
ConcatRelated("ColB", "PatternData", "ColA=""" & ColA & """" , "ColB", "") AS ColB_values
FROM (SELECT DISTINCT ColA FROM PatternData)
)
WHERE ColB_values = "123a"
...returning...
ColA
----
Q
X
Below is one possible solution:
WITH
data AS (
SELECT 'X' cola, '1' colb FROM dual
UNION ALL SELECT 'X' cola, '2' FROM dual
UNION ALL SELECT 'X', '3' FROM dual
UNION ALL SELECT 'X', 'a' FROM dual
UNION ALL SELECT 'Y', '1' FROM dual
UNION ALL SELECT 'Y', '2' FROM dual
UNION ALL SELECT 'Y', 'a' FROM dual
UNION ALL SELECT 'Z', '4' FROM dual
UNION ALL SELECT 'Z', '4' FROM dual
UNION ALL SELECT 'Z', 'b' FROM dual
UNION ALL SELECT 'Q', '1' FROM dual
UNION ALL SELECT 'Q', '2' FROM dual
UNION ALL SELECT 'Q', '3' FROM dual
UNION ALL SELECT 'Q', 'a' FROM dual
UNION ALL SELECT 'W', '4' FROM dual
UNION ALL SELECT 'W', '5' FROM dual
UNION ALL SELECT 'W', 'b' FROM dual
),
data_agg AS (
SELECT cola, listagg(colb) WITHIN GROUP (ORDER BY colb) AS agg_colb
FROM data
GROUP BY cola
)
SELECT cola
FROM data_agg da
WHERE EXISTS (SELECT 1
FROM data_agg
WHERE cola != da.cola
AND agg_colb = da.agg_colb
)
;
Edit: ops, for some reason I thought you were using Oracle... Hopefully, you'll be able to modify above query to be able to use it.