Oracle - split table in indexed blocks - oracle

I have to deal with a little problem in Oracle...I have a table with 2 columns, the first column contains dates, the second one contains imports. The "import column" could have both NULL or not NULL values.
What I want to do is to order by the date column (and this is easy :) ) and then split the table in blocks of contiguous NULL or not NULL values in the "import column" adding a third column which numbers the blocks.
Example:
Date Import
01/01/2017 99.12
01/02/2017 18.19
01/03/2017 22.92
01/04/2017 28.10
01/05/2017
01/06/2017
01/07/2017
01/08/2017 33.78
01/09/2017 20.30
01/10/2017 12.33
01/11/2017
01/12/2017 1.68
this table should became
Date Import Block
01/01/2017 99.12 1
01/02/2017 18.19 1
01/03/2017 22.92 1
01/04/2017 28.10 1
01/05/2017 2
01/06/2017 2
01/07/2017 2
01/08/2017 33.78 3
01/09/2017 20.30 3
01/10/2017 12.33 3
01/11/2017 4
01/12/2017 1.68 5

You can use analytic functions like this:
select d, import, sum(state_change) over (order by d) as block
from
(
select d, import, import_state,
case when import_state = lag(import_state) over (order by d, import)
then 0 else 1 end state_change
from
(
select d, import, case when import is not null then 1 else 0 end as import_state
from t
)
);
(NB I renamed your DATE column to D as DATE is a reserved word).
Breaking it down, starting with the innermost query:
select d, import, case when import is not null then 1 else 0 end as import_state
from t
This adds a column import_state that is 1 when import is not null, 0 if it is null. This creates "blocks" but they are numbered 1,0,1,0,... instead of 1,2,3,4,...
The next part compares each import_state with that on the preceding row, to check for changes. Column state_change is 1 when there has been a change, 0 otherwise - so now the first row for each "block" has a 1 and the reset have a 0.
The outer part then simply sums the state_change values cumulatively to give the required result.
There may well be a simpler solution!

Related

Referancing value from select column in where clause : Oracle

My tables are as below
MS_ISM_ISSUE
ISSUE_ID ISSUE_DUE_DATE ISSUE_SOURCE_TYPE
I1 25-11-2018 1
I2 25-12-2018 1
I3 27-03-2019 2
MS_ISM_SOURCE_SETUP
SOURCE_ID MODULE_NAME
1 IT-Compliance
2 Risk Assessment
I have written following query.
with rs as
(select
count(ISSUE_ID) as ISSUE_COUNT, src.MODULE_NAME,
case
when ISSUE_DUE_DATE<sysdate then 'Overdue'
when ISSUE_DUE_DATE between sysdate and sysdate + 90 then 'Within 3 months'
when ISSUE_DUE_DATE>sysdate+90 then 'Beyond 90 days'
end as date_range
from MS_ISM_ISSUE issue, MS_ISM_SOURCE_SETUP src
where issue.Issue_source_type = src.source_id
group by src.MODULE_NAME, case
when ISSUE_DUE_DATE<sysdate then 'Overdue'
when ISSUE_DUE_DATE between sysdate and sysdate + 90 then 'Within 3 months'
when ISSUE_DUE_DATE>sysdate+90 then 'Beyond 90 days'
end)
select ISSUE_COUNT,MODULE_NAME, DATE_RANGE,
(select count(ISSUE_COUNT) from rs where rs.MODULE_NAME=MODULE_NAME) as total from rs;
The output of the code is as below.
ISSUE_COUNT MODULE_NAME DATE_RANGE Total
1 IT-Compliance Overdue 3
1 IT-Compliance Within 3 months 3
1 Risk Assessment Beyond 90 days 3
The result is correct till 3rd column. In 4th column what I want is, total of Issue count for given module name. Hence in above case Total column will have value as 2 for first and second row (since there are 2 Issues for IT-Compliance) and value 1 for the third row (since one issue is present for Risk Assessment).
Essentially, I want to achieve is to replace current row's MODULE_NAME in last where clause. How do I achieve this using query?
OK, this condition
where rs.MODULE_NAME=MODULE_NAME
is essentially the same as if you wrote
where MODULE_NAME = MODULE_NAME
which is simply always true (if there are no nulls in module_name).
Try using different table alias for inner query and outer query, e.g.
select count(ISSUE_COUNT) from rs rs2 where rs2.MODULE_NAME=rs.MODULE_NAME
You can also try to use analytic function here, something like
select ISSUE_COUNT,
MODULE_NAME,
DATE_RANGE,
COUNT(ISSUE_COUNT) OVER (PARTITION BY RS.MODULE_NAME) AS TOTAL
from rs
instead of your subquery

Oracle. Leave blank line on composite key level break

I want the output of my SQL to leave a blank line when any of three columns (a b or c below) change.
So if the table had columns a b c and d, you end up with a report like this:
a b c d #<- column names
-------
1 a a a #<- V - data itself
1 a a b
#<- level break here because 1 a b is different from 1 a a
1 a b c
From Googling, I have seen BREAK might solve this.
But from what I can make out this pertains to one column.
I then thought what if I have a computed column.
a is numeric, b & c are alphanumeric data types.. so I guess I could possibly use CONCAT plus TO_CHAR..
I am wondering if anyone can give me some pointers?
Cheers.
You can break on more than one column:
SQL> break on a skip 1 duplicates on b skip 1 duplicates on c skip 1 duplicates
SQL> select * from your_table
A B C D
---------- - - -
1 a a a
1 a a b
1 a b c
3 rows selected.
If you issue a plain break it shows what is set:
SQL > break
break on a skip 1 dup
on b skip 1 dup
on c skip 1 dup
Another option is to generate an extra column expression of your composite key, break on that, and set it not to display:
SQL> break on composite skip 1
SQ>> column composite noprint
SQL> select t.*, a||':'||b||':'||c as composite from your_table t;
A B C D
---------- - - -
1 a a a
1 a a b
1 a b c
3 rows selected.
which has the advantage of not showing multiple blank lines if more than one column changes at the same time.
I've separated the values with a colon; the idea of that is to use a character that doesn't appear in the values themselves, to avoid an accidental clash. If any of the columns could actually have a colon then pick something else more obscure.

Using oracle loop to concatanete strings

I have someting like this
id day descrition
1 1 hi
1 1 today
1 1 is a beautifull
1 1 day
1 2 exemplo
1 2 for
1 2 this case
I need to do a funtion that for each day concatenate the descrtiomn colunm and return the result like this
id day descrition
1 1 hi today is a beautifull thay
1 2 exemplo for this case
Anny ideia about how can i do this usisng a loop in a function in oracle
You need a way of determining which order the values should be aggregated. The snippet below will rely on the implicit order in which Oracle reads the rows from the datafiles - if you have row movement enabled then you may get inconsistent results as the rows can be read in different orders as they are relocated in the underlying datafiles.
SELECT LISTAGG( description, ' ' ) WITHIN GROUP ( ORDER BY ROWNUM ) AS description
FROM your_table
GROUP BY id, day
It would be better to have another column that stores the order within each day.

How to select two max value from different records that has same ID for every records in table

i have problem with this case, i have log table that has many same ID with diferent condition. i want to select two max condition from this. i've tried but it just show one record only, not every record in table.
Here's my records table:
order_id seq status____________________
1256 2 4
1256 1 2
1257 0 2
1257 3 1
Here my code:
WITH t AS(
SELECT x.order_id
,MAX(y.seq) AS seq2
,MAX(y.extern_order_status) AS status
FROM t_order_demand x
JOIN t_order_log y
ON x.order_id = y.order_id
where x.order_id like '%12%'
GROUP BY x.order_id)
SELECT *
FROM t
WHERE (t.seq2 || t.status) IN (SELECT MAX(tt.seq2 || tt.status) FROM t tt);
this query works, but sometime it gave wrong value or just show some records, not every records.
i want the result is like this:
order_id seq2 status____________________
1256 2 4
1257 3 2
I think you just want an aggregation:
select d.order_id, max(l.seq2) as seq2, max(l.status) as status
from t_order_demand d join
t_order_log l
on d.order_id = l.order_id
where d.order_id like '%12%'
group by d.order_id;
I'm not sure what your final where clause is supposed to do, but it appears to do unnecessary filtering, compared to what you want.

How to identify duplicate rows having value within data range in oracle

I want to identify rows where value in Name column is same and value in start/ end column lies within range of Start and End value of another row...
for eg. for me row with ID value 4 & 1 are duplicate because they have same value in Name column and value in start column 26 of ID 4 lies within start & End values of ID 1 (24-56)
ID Name Start End
1 Adam 24 56
2 Max 1 5
3 Neil 6 4
4 Adam 26 30
You can use EXISTS for this:
select *
from yourtable y
where exists (
select 1
from yourtable y2
where y.id <> y2.id
and y.name = y2.name
and (y2.startfield between y.startfield and y.endfield
or
y.startfield between y2.startfield and y2.endfield))
SQL Fiddle Demo
I wasn't completely sure from your question if the end range had to be included as well. If so, you'll need to add that to the where criteria:
select *
from yourtable y
where exists (
select 1
from yourtable y2
where y.id <> y2.id
and y.name = y2.name
and ((y2.startfield > y.startfield and y2.endfield < y.endfield)
or
(y.startfield > y2.startfield and y.endfield < y2.endfield)))

Resources