SQL How to align ranges of data points in rows? - sql

Suppose having the data set:
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
)
select * from data_table;
TITLE | X
-----------
a 1
a 3
a 4
a 5
b 1
b 2
b 3
b 6
Wee see that points related to a and b are different.
How to align values in X column so both groups have the same points, filling the gaps with NULL?
Expected result is:
TITLE | X
-----------
a 1
a NULL
a 3
a 4
a 5
a NULL
b 1
b 2
b 3
b NULL
b NULL
b 6
Straightforward solution I got is
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
),
all_points(x) AS (
select distinct x from data_table
),
all_titles(title) AS (
select distinct title from data_table
),
aligned_data(title, x) as (
select t.title, p.x from all_points p cross join all_titles t
)
select ad.title, dt.x
from aligned_data ad
left join data_table dt on dt.title = ad.title and dt.x = ad.x
order by ad.title, ad.x;
As wee see cross join in aligned_data definition is bottleneck and can kill performance on valuable data sets.
I wonder if this task could be solved more elegantly. Maybe a trick with window functions can be proposed.

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>

SQL query to find duplicates with mismatching another column

Table looks like this:
col1 | col2
A | B
A | B
D | C
D | C
E | F
E | G
what I need, is to extract col1 E.
Already tried couple variants of SELECT DISTINCT or SELECT ..., COUNT(*) with GROUP BY, but can't figure it out.
p.s. DBMS is Oracle
Such as this?
SQL> with test (col1, col2) as
2 (select 'A', 'B' from dual union all
3 select 'A', 'B' from dual union all
4 select 'D', 'C' from dual union all
5 select 'D', 'C' from dual union all
6 select 'E', 'F' from dual union all
7 select 'E', 'G' from dual
8 )
9 select col1
10 from test
11 group by col1
12 having count(distinct col2) > 1;
C
-
E
SQL>

How to get mean of exams by client with 2 tables?

I know a little bit of sql, only the basic, now I need to create a analytic query but can't do this yet.
I have 2 tables on my db oracle, client and exams:
I am tried a lot of ways to get the mean of exams by client, but no success yet.4
The result expected is:
exams = 13
clients = 6
13/6= 2.166666666...7
How can I do that?
If you have clients who have not taken any exams then you want:
SELECT AVG(COUNT(e.nu_ordem)) AS avg_exames_by_client
FROM cliente c
LEFT OUTER JOIN exames e
ON (c.id = e.id_cliente)
GROUP BY c.id;
or:
SELECT (SELECT COUNT(*) FROM exames) / (SELECT COUNT(*) FROM cliente)
AS avg_exames_by_client
FROM DUAL;
Which, for the sample data:
CREATE TABLE cliente (id PRIMARY KEY) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 6 FROM DUAL;
CREATE TABLE exames (nu_ordem PRIMARY KEY, id_cliente) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 5 FROM DUAL UNION ALL
SELECT 3, 5 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 6 FROM DUAL UNION ALL
SELECT 6, 1 FROM DUAL UNION ALL
SELECT 7, 1 FROM DUAL UNION ALL
SELECT 8, 4 FROM DUAL UNION ALL
SELECT 9, 5 FROM DUAL UNION ALL
SELECT 10, 3 FROM DUAL UNION ALL
SELECT 11, 6 FROM DUAL UNION ALL
SELECT 12, 2 FROM DUAL UNION ALL
SELECT 13, 1 FROM DUAL;
Both output:
AVG_EXAMES_BY_CLIENT
2.166666666666666667
If you then add a couple of clients but no more exams:
INSERT INTO cliente (id)
SELECT 7 FROM DUAL UNION ALL
SELECT 8 FROM DUAL
Then the average is:
AVG_EXAMES_BY_CLIENT
1.625
db<>fiddle here
You can try below formula to get the result -
SELECT COUNT(*)/COUNT(DISTINCT id_cliente)
FROM exams;

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

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