Query Optimization - To repeat a pattern in Oracle SQL - sql

Introduction: I can do this in MS-Excel, it takes me 1 minute, but I m trying to get this in Oracle SQL
Here is my Code:
SELECT A.*, (CASE WHEN A.r = 1 then 'X1' when A.r = 2 then 'X2' when A.r = 3 then 'X3' when A.r = 4
then 'X4' when A.r = 5 then 'X2' when A.r = 6 then 'X6' end) X FROM
(
Select Rownum r
From dual
Connect By Rownum <= 6 ) A
This is the Output:
Now, what if I have to do it for 25000 numbers, meaning when (rownum <= 25000) currently I have it only for 6, Is there a better method to do this with out case statement?

If you want to repeat this pattern of 6 rows for the remaining rows, then you can do:
select t.*,
(case when mod(rownum, 6) = 5 then 'X2'
else 'X' || (mod(rownum - 1, 6) + 1)
end)
from t;

Related

How to transform and grouping in SQL

I would like to group bycontract date by transforming segment referring to its contract date like below.
contractdate segment
~2020/2/1 a
2020/2/2~2020/4/1 b
2020/4/2~ c
My desired result is to cut contractdate into segment and countthem into result tables.
If someone has opinion,please let me know.
Thanks
my table is like below.
contractdate status
2020/1/2 A
2020/4/2 B
2020/6/5 C
2020/1/2 C
2020/4/4 B
And here is my desired result.
segment A B C
a 1 0 1
b 0 0 0
c 0 2 1
Replace missing bounds with sentinels (or replace notation with weird tilde character at all), then distribute contractdate of statuses into proper ranges:
with sg (contractdate,segment) as (
select '~2020/2/1' , 'a' from dual union all
select '2020/2/2~2020/4/1', 'b' from dual union all
select '2020/4/2~' , 'c' from dual
), ssg as ( -- sanitized sg
select coalesce(to_date(regexp_replace(contractdate,'([^~]*)~([^~]*)','\1'),'YYYY/MM/DD'), date '-4712-1-1') as lowerbound -- source: https://laurentschneider.com/wordpress/2008/01/what-is-the-lowest-and-highest-possible-date-in-oracle.html
, coalesce(to_date(regexp_replace(contractdate,'([^~]*)~([^~]*)','\2'),'YYYY/MM/DD'), date '9999-01-01') as upperbound
, segment
from sg
), st (contractdate,status) as (
select '2020/1/2', 'A' from dual union all
select '2020/4/2', 'B' from dual union all
select '2020/6/5', 'C' from dual union all
select '2020/1/2', 'C' from dual union all
select '2020/4/4', 'B' from dual
)
select segment
, sum(case when status = 'A' then 1 else 0 end)
, sum(case when status = 'B' then 1 else 0 end)
, sum(case when status = 'C' then 1 else 0 end)
from ssg left join st on to_date(st.contractdate,'YYYY/MM/DD') between ssg.lowerbound and ssg.upperbound
group by segment
order by segment
Please use CTE to specify your input data, not plaintext tables. It helps to concentrate on answer instead of text formatting.

How to calculate leave-out-median using PL/SQL analytic function

I'm trying to write an analytic function in PL/SQL that, when applied to a column within a table, returns for each row in the table, the median of the column excluding the given row.
An example to clarify: Suppose I have a table TABLE consisting of one column X that takes on the following values:
1
2
3
4
5
I want to define an analytic function LOOM() such that:
SELECT LOOM(X)
FROM TABLE
delivers the following:
3.5
3.5
3
2.5
2.5
i.e., for each row, the median of X, excluding the given row. I've been struggling to build the desired LOOM() function.
I'm not sure if there is a "clever" way to do this. You can do the calculation with a correlated subquery.
Assuming the x values are unique -- as in your example --
with t as (
select 1 as x from dual union all
select 2 as x from dual union all
select 3 as x from dual union all
select 4 as x from dual union all
select 5 as x from dual
)
select t.*,
(select median(x)
from t t2
where t2.x <> t.x
) as loom
from t;
EDIT:
A more efficient method uses analytic functions but requires more direct calculation of the median. For instance:
with t as (
select 1 as x from dual union all
select 2 as x from dual union all
select 3 as x from dual union all
select 4 as x from dual union all
select 5 as x from dual
)
select t.*,
(case when mod(cnt, 2) = 0
then (case when x <= candidate_1 then candidate_2 else candidate_1 end)
else (case when x <= candidate_1 then (candidate_2 + candidate_3)/2
when x = candidate_2 then (candidate_1 + candidate_3)/2
else (candidate_1 + candidate_2) / 2
end)
end) as loom
from (select t.*,
max(case when seqnum = floor(cnt / 2) then x end) over () as candidate_1,
max(case when seqnum = floor(cnt / 2) + 1 then x end) over () as candidate_2,
max(case when seqnum = floor(cnt / 2) + 2 then x end) over () as candidate_3
from (select t.*,
row_number() over (order by x) as seqnum,
count(*) over () as cnt
from t
) t
) t

SQL Sum Using logic to choose Plus or Minus (Oracle)

Not sure if this is possible or not in straight SQL.
Is it possible to choose the operator for a sum using the likes of a CASE Statement or similar logic.
e.g.
select
(select 1 from dual)
(case when (select 1 from dual) = 1 then + else - end)
(select 2 from dual)
from dual
Thanks in return.
- and + cannot be results of CASE WHEN, because they are not values. -1 and +1 however are. Multiply this -1 or +1 with the desired value in order to get the positive or negative value. E.g:
select case when type = 'withdrawal' then -1 else +1 end * value as balance_change
For your example:
select
(select 1 from dual) +
(case when (select 1 from dual) = 1 then +1 else -1 end) *
(select 2 from dual)
from dual
select 1 + (case when (select 1 from dual) = 1 then +1 else -1 end) from dual
I mean:
with
sub_q as (
select 2000 val1, -1500 val2 from dual
union all
select 1000 val1, +2000 val2 from dual
)
select val1, val2, SIGN(val2) s, val1 + (case when SIGN(val2) = 1 then +val2 else -val2 end) res
from sub_q

not a single-group group function using select case statement

I am writing below query which divides the two select query and calculate the percentage. But i am getting an error as not a single-group group function
select CASE WHEN COUNT(*) = 0 THEN 0 ELSE round((r.cnt / o.cnt)*100,3) END from
(Select count(*) as cnt from O2_CDR_HEADER WHERE STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)) r cross join
(Select count(*) as cnt from O2_CDR_HEADER WHERE DATE_CREATED > (SYSDATE - 1)) o;
You don't need to use joins. If I were you, I'd do:
select case when count(*) = 0 then 0
else round(100 * count(case when status not in (0, 1) then 1 end) / count(*), 3)
end non_0_or_1_status_percentage
from o2_cdr_header
where date_created > sysdate - 1;
Here's a simple demo:
with t as (select 1 status from dual union all
select 2 status from dual union all
select 3 status from dual union all
select 2 status from dual union all
select 4 status from dual union all
select 5 status from dual union all
select 6 status from dual union all
select 7 status from dual union all
select 1 status from dual union all
select 0 status from dual union all
select 1 status from dual)
select case when count(*) = 0 then 0
else round(100 * count(case when status not in (0, 1) then 1 end) / count(*), 3)
end col1
from t
where 1=0;
COL1
----------
0
And just in case you aren't sure that doing the filtering of the count in the case statement returns the same as when you filter in the where clause, here's a demo that proves it:
with t as (select 1 status from dual union all
select 2 status from dual union all
select 3 status from dual union all
select 2 status from dual union all
select 4 status from dual union all
select 5 status from dual union all
select 6 status from dual union all
select 7 status from dual union all
select 1 status from dual union all
select 0 status from dual union all
select 1 status from dual)
select 'using case statement' how_count_filtered,
count(case when status not in (0, 1) then 1 end) cnt
from t
union all
select 'using where clause' how_count_filtered,
count(*) cnt
from t
where status not in (0, 1);
HOW_COUNT_FILTERED CNT
-------------------- ----------
using case statement 7
using where clause 7
You are referencing an aggregate function (COUNT(*)) and an individual column expression (r.cnt and o.cnt) in the same SELECT query. This is not valid SQL unless a GROUP BY clause is added for the relevant individual columns.
It would be easier to provide a valid alternative it you could clarify what you'd like this query to return (given a sample schema and set of data). As a guess, I'd say you can simply substitute COUNT(*) with o.cnt to avoid the division by 0 issue. If there's some other logic expected to be present here, you'd need to clarify what that is.
It looks like you want to get a percentage of status not in 0,1, or 0 if there is no results.
Maybe this is what you want for the first line?
SELECT CASE WHEN (R.CNT = 0 AND O.CNT = 0) THEN 0 ELSE ROUND((R.CNT *100.0 / O.CNT),3) END
You don't need a cross join. Select the counts and do a division later on.
select case when ocnt > 0 then round((rcnt / ocnt)*100,3)
else 0 end
from
(
select
CASE WHEN STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)
THEN COUNT(*) END as rcnt,
CASE WHEN DATE_CREATED > (SYSDATE - 1)
THEN COUNT(*) END as ocnt
from O2_CDR_HEADER
group by status, date_created
) t
Boneist's answer is fine, but I would write it as:
select coalesce(round(100 * avg(case when status not in (0, 1) then 1.0 else 0
end), 3), 0) as non_0_or_1_status_percentage
from o2_cdr_header
where date_created > sysdate - 1;
Here is the answer which works perfectly for me
select CASE WHEN (o.cnt = 0) THEN 0 ELSE round((r.cnt / o.cnt)*100,3) END from
(Select count(*) as cnt from O2_CDR_HEADER WHERE STATUS NOT IN(0,1) and DATE_CREATED > (SYSDATE - 1)) r cross join
(Select count(*) as cnt from O2_CDR_HEADER WHERE DATE_CREATED > (SYSDATE - 1)) o

Group the result from DATEDIFF function in SQL

I have this query :
SELECT DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay
FROM wssjobm , sysbrxces WHERE wj_br = zu_br AND zu_user = 'mbs'
AND wj_date >= '2013/04/01' AND wj_date <= '2013/04/30'
AND wj_status = 'D' AND wj_donedate <= '2013/04/30'
The eg. result :
tmpDay
1
11
5
1
7
2
12
10
2
2
How can i group by the result and count it using query to be like this :
tmpDay count
1 2
2 3
5 1
7 1
10 1
11 1
12 1
thanks!
Extra Question :
May i get the result like this :
tmpDayGroup Count
1- 4 5
5- 8 2
11 -12 3
Try:
SELECT
tmpDay,
COUNT(*) [Count]
FROM
YourTable
Group By tmpDay
For the given example, query should be like:
SELECT
DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay,
COUNT(*) [Count]
FROM
wssjobm , sysbrxces
WHERE
wj_br = zu_br AND
zu_user = 'mbs' AND
wj_date >= '2013/04/01' AND
wj_date <= '2013/04/30' AND
wj_status = 'D' AND
wj_donedate <= '2013/04/30'
GROUP BY DATEDIFF(DAY,wj_date,wj_donedate)
For grouping the result, try:
;with T as(
select '1' FrmD, '4' ToD union all
select '5' FrmD, '8' ToD union all
select '9' FrmD, '12' ToD
)
select
T.FrmD +'-'+ T.ToD tmpDayGroup,
COUNT(*) [Count]
from T Left Join (
SELECT
DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay
FROM
wssjobm , sysbrxces
WHERE
wj_br = zu_br AND
zu_user = 'mbs' AND
wj_date >= '2013/04/01' AND
wj_date <= '2013/04/30' AND
wj_status = 'D' AND
wj_donedate <= '2013/04/30'
) T2 on T2.tmpDay between T.FrmD and T.ToD
group by T.FrmD, T.ToD