How to find a specific pattern in some Access data? - sql

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.

Related

Filter Alphabets from Oracle table

I have input like :-
Table 1
Table 2
A
4
B
5
C
6
1
X
2
Y
3
Z
And Output muse be
Output
A
B
C
X
Y
Z
One method uses a union followed by a filter:
SELECT val
FROM
(
SELECT col1 AS val FROM yourTable
UNION ALL
SELECT col2 FROM yourTable
) t
WHERE REGEXP_LIKE(val, '^[A-Z]+$')
ORDER BY val;
If data really looks as you put it, a simple option is to use greatest function.
Sample data:
SQL> with test (col1, col2) as
2 (select 'A', '4' from dual union all
3 select 'B', '5' from dual union all
4 select 'C', '6' from dual union all
5 select '1', 'X' from dual union all
6 select '2', 'Y' from dual union all
7 select '3', 'Z' from dual
8 )
Query:
9 select greatest(col1, col2) result
10 from test;
RESULT
----------
A
B
C
X
Y
Z
6 rows selected.
SQL>

Oracle SQL query using case when, compacting null fields

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)

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

Oracle passing outer query value to inner query

This might be a simple but I need to apply the logic in other:
WITH t(col) AS (
SELECT 1 FROM dual
UNION SELECT 2 FROM dual
UNION SELECT 3 FROM dual
UNION SELECT 4 FROM dual
UNION SELECT 5 FROM dual
)
SELECT col , --- will works as usual
(SELECT col FROM t WHERE col = outer_q.col) new_col, --working as well
(
SELECT sum (latest_col)
from
(
SELECT col latest_col FROM t WHERE col = outer_q.col
UNION ALL
SELECT col FROM t WHERE col = outer_q.col
)
)newest_col -- need to get an output "4"
from t outer_q where col = 2;
An simple output like:
COL NEW_COL NEWEST_COL
---------- ---------- ----------
2 2 4
I just need to use the outer most value to the inner I used for the third column
EDITING-- sample with more data:
WITH
t(col) AS
( SELECT 1 FROM dual
UNION
SELECT 2 FROM dual
UNION
SELECT 3 FROM dual
UNION
SELECT 4 FROM dual
UNION
SELECT 5 FROM dual
),
t1(amount, col) AS
(SELECT 100 , 2 FROM dual
UNION
SELECT 200, 3 FROM dual
)
SELECT col,
(SELECT col FROM t WHERE col = outer_q.col
) new_col,
(SELECT SUM(x)
FROM
(SELECT col x FROM t
UNION ALL
SELECT amount x FROM t1
)
WHERE col = outer_q.col
) newest_col -- gives 315 as it takes whole `SUM`
FROM t outer_q
WHERE col = 2;
An output is expected like:
COL NEW_COL NEWEST_COL
---------- ---------- ----------
2 2 102
Thanks in advance for any help.
Well, you can if you refactor a but your query:
WITH t(col) AS (
SELECT 1 FROM dual
UNION SELECT 2 FROM dual
UNION SELECT 3 FROM dual
UNION SELECT 4 FROM dual
UNION SELECT 5 FROM dual
)
SELECT col,
(SELECT col FROM t WHERE col = outer_q.col) new_col,
(SELECT sum (latest_col)
from
(
SELECT col latest_col FROM t
UNION ALL
SELECT col FROM t
) x
where x.latest_col = outer_q.col
) newest_col -- need to get an output "4"
from t outer_q where col = 2;
This is possible here because outer_q is now in the where clause of the sub-query. It was used before in the sub-sub-query (the one with the UNION ALL), and this one was hiding it.
To try to make things clearer, now we have something like:
with t as (...)
select col,
(SELECT col FROM t WHERE col = outer_q.col) new_col,
(SELECT col FROM (Something more complex) WHERE ... = outer_q.col) new_col,
from t outer_q where col = 2;
So we now have the same level of "interiority".
EDIT: to answer the updated question, there is a little adaptation needed:
WITH t(col) AS
(
SELECT 1 FROM dual
UNION
SELECT 2 FROM dual
UNION
SELECT 3 FROM dual
UNION
SELECT 4 FROM dual
UNION
SELECT 5 FROM dual
),
t1(amount, col) AS
(
SELECT 100, 2 FROM dual
UNION
SELECT 200, 3 FROM dual
)
SELECT col,
(SELECT col FROM t WHERE col = outer_q.col) new_col,
(SELECT SUM(amount)
FROM
(SELECT col, col amount FROM t -- row is (1, 1), then (2, 2) etc
UNION ALL
SELECT col, amount FROM t1 -- row is (2, 100), then (3, 200) etc
)
WHERE col = outer_q.col
) newest_col -- gives 102 as it takes whole `SUM`
FROM t outer_q
WHERE col = 2;
The part to understand is in the innermost query: you want to sum both the column and the amount value, so you repeat the col value as if it was an amount.
Another way to obtain the same result (with more performance, I guess) would be to sum col and amount on the same row:
WITH t(col) AS
(
SELECT 1 FROM dual
UNION
SELECT 2 FROM dual
UNION
SELECT 3 FROM dual
UNION
SELECT 4 FROM dual
UNION
SELECT 5 FROM dual
),
t1(amount, col) AS
(
SELECT 100, 2 FROM dual
UNION
SELECT 200, 3 FROM dual
)
SELECT col,
(SELECT col FROM t WHERE col = outer_q.col) new_col,
(SELECT SUM(all_amount)
FROM
(SELECT col, col + amount all_amount FROM t1)
WHERE col = outer_q.col
) newest_col -- gives 315 as it takes whole `SUM`
FROM t outer_q
WHERE col = 2;
The inner query fails because you tried to push the outer_q.col reference two levels down. Correlated query goes only 1 level down
Reference: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1853075500346799932

Can i get the rows that isn't repeated inverted in my table?

I have a table:
1|5
2|4
3|3
4|2
5|1
(the actual table is a lot of names and what I want is to just get one set of each combination)
is there a way to get just the rows that are not repeated inverted at the end?
I just want the:
1|5
2|4
3|3
rows.. can i do this in sql?
Something like:
select distinct(case when x<y then x||'|'||y else y||'|'||x end) from table;
Should work on Oracle, in different db use equivalent of case.
Test:
select distinct(case when x<y then x||'|'||y else y||'|'||x end) from
(select 1 x,2 y from dual
union
select 1 x,2 y from dual
union
select 1 x,3 y from dual
union
select 3 x,2 y from dual
union
select 2 x,1 y from dual
);
Returns:
1|2
1|3
2|3
Would this solve your problem:
select *
from MyTable
where ColA <= ColB
Edit: Ok, if you have cases like 20, 5 then you could use this:
with allpairs as (
select ColA, ColB
from MyTable
where ColA <= ColB
union all
select ColB, ColA
from MyTable
where ColB < ColA
)
select distinct *
from allpairs