subtract values selecting by timestamp self join - oracle

I have the following problem:
Table1:
id, timestamp, value1,value2,value3
1, 12.01.2017 09:00:01, 234, 345, 456
2, 12.01.2017 09:00:05, 567, 678, 789
3, 12.01.2017 09:00:25, 777, 888, 999
the values are absolut values. now i need to insert this data into a new table, but as incremental values.
that means i have to find the previous record for each record and subtract the values.
but unfortunately im not getting there...
I tried a self join like this:
select
se1.timestamp,
se1.value1,
se1.value2,
se1.value3
from
table1 se1,
table1 se2
where
se1.id = se2.id
and se1.timestamp < (select max(timestamp) from table1)
order by
timestamp desc
fetch first 100 rows only;
it would be great if anyone could help me with this...
The output should look something like this:
timestamp, value1, value2, value3
12.01.2017 09:00:05, 333, 333, 333 (record from 09:00:01 subtracted)
12.01.2017 09:00:25, 210, 210, 210 (record from 09:00:05 subtracted)
I hope anyone can understand this ;)

If I understand well, you need to compute for every value (value1, value2 and value3) the difference between the value in the current row and the value in the preceding row, ordered by timestamp.
If so, you may need:
with test(id, timestamp, value1, value2, value3) as (
select 1, to_timestamp('12.01.2017 09:00:01', 'dd.mm.yyyy hh24:mi:ss'), 234, 345, 456 from dual union all
select 2, to_timestamp('12.01.2017 09:00:05', 'dd.mm.yyyy hh24:mi:ss'), 567, 678, 789 from dual union all
select 3, to_timestamp('12.01.2017 09:00:25', 'dd.mm.yyyy hh24:mi:ss'), 777, 888, 999 from dual
)
select timestamp,
value1 - lag(value1, 1, 0) over ( order by timestamp) increment1,
value2 - lag(value2, 1, 0) over ( order by timestamp) increment2,
value3 - lag(value3, 1, 0) over ( order by timestamp) increment3
from test
This uses LAG to evaluate the preceding row and make the difference with the value in the current row, thus giving:
TIMESTAMP INCREMENT1 INCREMENT2 INCREMENT3
12/01/2017 09:00:01,000000000 234 345 456
12/01/2017 09:00:05,000000000 333 333 333
12/01/2017 09:00:25,000000000 210 210 210
Notice that timestamp is an Oracle type, so it would be better not to use it as the name of a column.
Also, notice the comments by Boneist: in the first version of my answer I used something like NVL(LAG(..), 0) to handle the case where LAG(..) is null (the first row). Boneist's comment made me notice that LAG already handles a default value in case the needed preceding row does not exist, thus allowing me to avoid the NVL.

Related

Oracle - Connect By Prior

I know I have to use CONNECT BY PRIOR in this query, but I'm not sure how to implement it.
We have customers who purchase monthly subscriptions, and those get auto-renewed each month. We have a log table which can show what your current order ID is, and what your previous order ID is. So, table records could like like this:
CUSTOMER ID: 1 ORDER ID: 123 PREV_ORDER_ID: STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 456 PREV_ORDER_ID: 123 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 789 PREV_ORDER_ID: 456 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 888 PREV_ORDER_ID: 789 STATUS: Complete
CUSTOMER ID: 1 ORDER ID: 999 PREV_ORDER_ID: 888 STATUS: Active
I am looking to count how many customers have had at least 13 months of consecutive subscriptions, with no gaps with the most recent subscription will have an "Active" status. If there is a break in subscriptions, the PREV_ORDER_ID will be NULL.
Hoping to do this in a query, and not having to write an anonymous block for it.
Many thanks!
You could do something like this (using your actual table and column names instead of the with clause from the query, which you should delete). Note that the hierarchical recursion starts from the end (from 'Active' status) and proceeds backwards; in my query I stop it at level 4 since I didn't feel like writing enough rows to get to level 13. Of course, you will have to replace 4 with 13 in the where clause.
with
test_data (customer_id, order_id, prev_order_id, status) as (
select 1, 123, null, 'Complete' from dual union all
select 1, 456, 123, 'Complete' from dual union all
select 1, 789, 456, 'Complete' from dual union all
select 1, 888, 789, 'Complete' from dual union all
select 1, 999, 888, 'Active' from dual union all
select 2, 100, null, 'Complete' from dual union all
select 2, 200, 100, 'Active' from dual union all
select 5, 105, null, 'Complete' from dual union all
select 5, 106, 105, 'Complete' from dual union all
select 5, 205, null, 'Complete' from dual union all
select 5, 206, 205, 'Active' from dual
)
select customer_id
from test_data
where level = 4
start with status = 'Active'
connect by customer_id = prior customer_id and order_id = prior prev_order_id
;
CUSTOMER_ID
-----------
1
More data and tests are needed.
Maybe it will help you
CREATE TABLE CUSTOMER_LOG
(
CUSTOMER_ID number(5),
ORDER_ID number(5),
PREV_ORDER_ID number(5),
STATUS VARCHAR(50)
);
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,123,NULL, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,456,123, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,789,456, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,888,789, 'Complete');
INSERT INTO CUSTOMER_LOG(CUSTOMER_ID,ORDER_ID,PREV_ORDER_ID,STATUS) values (1,999,888, 'Active');
Select
l.*,
(
select count(*)
from CUSTOMER_LOG s
where s.customer_id=1
start with s.ORDER_ID=l.ORDER_ID
connect by s.ORDER_ID= prior s.PREV_ORDER_ID
) QTDE
from CUSTOMER_LOG l
where l.status='Active'

Oracle: find the largest number within one string

I have some strings within a column of a table like asdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd. I am wondering how may I extract the largest number within such a string in each row. For example, here the largest number is 188 (out of 188, 98 and 78).
Since the numbers I'm interested in are always right after AB, I was thinking about using regexp_substr. Unfortunately, I'm not sure how to have it output multiple rows so that I can use max clause. PLSQL language would be great as well. Please show me a simple example if you have an idea. Thank you in advance!
You could tokenize the string into all its number components, and then find the maximum:
select max(to_number(
regexp_substr('sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd', '(\d+)', 1, level))
) as max_value
from dual
connect by regexp_substr('sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd', '(\d+)', 1, level)
is not null;
MAX_VALUE
----------
188
or
select max(to_number(
regexp_substr('sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd', '(\d+)', 1, level, null, 1))
) as max_value
from dual
connect by level <= regexp_count('sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd', '\d+');
MAX_VALUE
----------
188
If you need to get values from multiple rows you need the connect-by to match the IDs, and also need to include a reference to a non-deterministic function to prevent looping; with two values in a CTE:
with your_table (id, str) as (
select 1, 'sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd' from dual
union all select 2, '123abc456abc78d9' from dual
)
select id, max(to_number(regexp_substr(str, '(\d+)', 1, level, null, 1))) as max_value
from your_table
connect by prior id = id
and prior dbms_random.value is not null
and level <= regexp_count(str, '\d+')
group by id;
ID MAX_VALUE
---------- ----------
1 188
2 456
Alternatively (to Alex' answer), if there are multiple rows:
SQL> with your_table (id, str) as (
2 select 1, 'sdfAB98:(hjkl,)AB188(uiop)uuuAB78:jknd' from dual
3 union all select 2, '123abc456abc78d9' from dual
4 )
5 select id, max(to_number(regexp_substr(str, '\d+', 1, column_value))) max_num
6 from your_table,
7 table(cast(multiset(select level from dual
8 connect by level <= regexp_count(str, '\d+')
9 ) as sys.odcinumberlist))
10 group by id;
ID MAX_NUM
---------- ----------
1 188
2 456
SQL>

how to combine multiple rows into one row in table in oracle and store result in other table

sample data: table consists of 96000 rows and i want to combine them into 16000 rows
row 1:1255467861 40825825 IDF+0149502016010615210300000396000026+0000651+ 00000000000000+|
row 2:000000000+0000000+0000000+000000677+02 YY 0444410100000 00 0001000000054+10001EB4200002+00000+0000052+0000000+0000000+|
row 3:00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+|
row 4:0000000+00000 00000+00000+0000000+0000000+0000000+00 004 1 000000000000 0000000000 M5|
row 5: 00000000 +00000000000000000000000000000000000000+00000000001011 Y 000000000+|
row 6:0000000+0000000+0000000+AB0002210000000000FIABMM81 15067195 0000000000009403228870|
Assuming that you know what the criteria (column1, column2 in the example) for grouping your rows is, I suggest you to use aggregate functions functions such as SUM(), MAX(), and COUNT() to aggregate data, Group by the criteria, and insert into the target table.
E.G.
insert into my_target_table
Select column1, column2, sum(column3), max(column4), count(1) as column5
from my_source_table
group by column_1, column2
Assuming "row 1, row 2, row 3" has some meaning, you must have another column somewhere that shows the order. In my setup below, I assume you have an "id" (not necessarily consecutive numbers starting from 1; it can be number, or it can be date, etc. - for illustration I use some random numbers, they can also be fractional, or negative etc. - I build "row numbers" form that). If you don't have ANY kind of ordering, in the "order by" clause for the row_number() function you can simply "order by 1". However, I assume you also want to capture the minimum value of the "id" from each group of six (again, the id may be a date or timestamp or whatever).
The strategy is to create "row numbers". It works better if everything starts from 0, so I subtract 1 from the row_number() values. Then group by trunc(rn/6) and order by mod(rn, 6).
To concatenate the strings use listagg. I assume you wanted the strings concatenated with NO separator between them; if you do want them separated, for example, by #, then change the second argument to listagg from '' to '#'.
Query (including setup data, I shortened the input strings and I added a few more to show how everything works):
with test_data(id, input_str) as (
select 1, '12554861 40825825 IDF+0140000000+|' from dual union all
select 2, '0000000+00052+0000000+0000000+|' from dual union all
select 4, '00000 000+00000+0+00000+000+0000000+|' from dual union all
select 9, '00000+0000000+000000000000 M5|' from dual union all
select 14, '000000 +000000000011 Y 000000000+|' from dual union all
select 23, '000000IABMM81 15067195 0000003228870|' from dual union all
select 31, '125508825 IDF+0143333300000+|' from dual union all
select 32, '0000+052+0000AXZC000+0000000+|' from dual union all
select 37, '000MMDOQ000+0+00000+0000000+0000000+|' from dual union all
select 45, '000000 +00000000001011 Y 000000000+|' from dual union all
select 46, '00000+0000FIAB1 15067195 000CCV70|' from dual
),
has_rn (id, input_str, rn) as (
select id, input_str, row_number() over (order by id) - 1
from test_data
)
select min(id) as id,
listagg(input_str, '') within group (order by mod(rn, 6)) as output_str
from has_rn
group by trunc(rn/6);
Output (using the sample inputs in the test_data cte):
ID OUTPUT_STR
-- -------------------------------------------------------------------------------------------------------------------
1 12554861 40825825 IDF+0140000000+|0000000+00052+0000000+0000000+|00000 000+00000+0+00000+000+0000000+|00000+0000000+000000000000 M5|000000 +000000000011 Y 000000000+|000000IABMM81 15067195 0000003228870|
31 125508825 IDF+0143333300000+|0000+052+0000AXZC000+0000000+|000MMDOQ000+0+00000+0000000+0000000+|000000 +00000000001011 Y 000000000+|00000+0000FIAB1 15067195 000CCV70|

Oracle- Multiple rows in single row with adding column of duplicate value

Id Field_id Field_value
------------------------------
1 10 'A'
1 11 'B'
1 12 'C'
I want to make rows like
Id Field_id Field_value data_1 data_2
--------------------------------------
1 10 'A' 'B' 'C'
Pl help.
Take a look at this:
with t (Id, Field_id, Field_value) as (
select 1, 10, 'A' from dual union all
select 1, 11, 'B' from dual union all
select 1, 12, 'C' from dual
)
select FIELD_ID1, "10_FIELD_ID", "11_FIELD_ID","12_FIELD_ID"
from (select id, field_id, min(field_id) over() field_id1, field_value from t)
pivot (
max(field_value) field_id
for field_id in (10, 11, 12)
)
FIELD_ID1 10_FIELD_ID 11_FIELD_ID 12_FIELD_ID
---------------------------------------------------
10 A B C
Read more about pivot here
Normally, people look for PIVOT query, however, your input data is not at all duplicate as already mentioned in comments.
What you could do is, using subquery factoring, select id, 10 as field_id, field_value, thus making field_it static value as 10. Then use LISTAGG. But, the grouped rows would be a single column as delimeter seperated values.
I hope your requirement is exactly what you stated and not a typo or a mistake while posting.

sum of multiple columns in ORACLE

I have a table that has 97 columns, I want to sum 96 columns.
select sum(col1+col2+col3+.....+col96)
from tableA where meter_id=x;
I do not want to give all 96 column names, what is the best way to do it?
Regards,
RR
There is no way to avoid writing each column name. All you can do is curse the stupid data modeller and get busy with cut'n'paste.
In the case where there are a significant number of columns, I would look at using the data dictionary tables to help create the query by using a query like the one below:
Select column_name || '+' as column_name_list
From user_tab_columns
Where table_name = 'TABLEA'
Order by column_id
It doesn't change the world but does simplify writing one query.
You could create a virtual column that adds up your 96 columns, something like:
alter table TABLEA add (MY_TOTAL_COL NUMBER GENERATED ALWAYS AS (col1+col2+col3...) VIRTUAL);
Then your query can simply do sum(my_total_col).
You might be best to sum the columns and then put the result in Excel to do the sum of sums. Otherwise this query should do what you need:
SELECT SUM(TOTAL_SUM) FROM (
SELECT SUM(column1) AS TOTAL_SUM FROM your_table
UNION
SELECT SUM(column2) AS TOTAL_SUM FROM your_table
UNION
SELECT SUM(column3) AS TOTAL_SUM FROM your_table
);
SELECT A.consol_key,
A.amt_lcy,
B.amt_lcy,
C.amt_lcy
FROM categ A,
spec B,
stmt C;
SELECT Sum(total_sum)
FROM (SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM categ
UNION
SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM spec
UNION
SELECT Sum(amt_lcy) AS TOTAL_SUM
FROM stmt)
WHERE table_id NOT IN (SELECT table_id
FROM categ
WHERE txn_code = 'COR'
AND system_id <> 'AA');
It could be possible:
Using Can an SQL procedure return a table?
and the answer of Mike Meyers you could write a stored procedure using dynamic sql
sumcolumns(columnfilter,tablename,whereclause)
and use it something like
select *
from table(sumcolumns('column_name <> ''col97''','tableA','meter_id=x'))
Try using UNPIVOT as per example below (still need to specify the column list as others have noted):
with tableA as /* prototype tableA just for example */
(
select 1 meter_id, 101 col1, 10 col2, 20 col3, 30 col4, NULL col5, 101 col11, 10 col12, 20 col13, 30 col14, NULL col15, 101 col21, 10 col22, 20 col23, 30 col24, NULL col25 from dual union
select 2, 102, 40, NULL, 50, NULL, 102, 40, NULL, 50, NULL, 102, 40, NULL, 50, NULL from dual union
select 3, 103, 60, 70, 80, 90, 103, 60, 70, 80, 90, 103, 60, 70, 80, 90 from dual union
select 4, 104, 100, NULL, NULL, NULL, 104, 100, NULL, NULL, NULL, 104, 100, NULL, NULL, NULL from dual
)
, unpivoted_tableA as /* UNPIVOT tableA columns into rows */
(
select *
from tableA
unpivot include nulls
(
col_value for col_ in
(COL1,COL2,COL3,COL4,COL5,COL11,COL12,COL13,COL14,COL15,COL21,COL22,COL23,COL24,COL25)
)
)
/* main query - Sum of all columns that were unpivoted to rows */
select meter_id, sum(col_value)
from unpivoted_tableA
group by meter_id
;

Resources