Oracle month year temp table - oracle

I am trying to create a month, year temp table that I can relate to in calculations, however I am having some issues. I am unable to create global temp tables due to restrictions and have to rely on the following kind of query.
WITH Months AS
(
SELECT LEVEL -1 AS ID
FROM DUAL
CONNECT BY LEVEL <=264
)
(SELECT
ROWNUM AS MO_SYS_ID,
TO_CHAR(ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), ID), 'YYYY'||'MM') AS MO_NM,
TO_CHAR(ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), ID), 'MON') AS MO_ABBR_NM,
TO_CHAR(ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), ID), 'MONTH') AS MO_FULL_NM,
TO_CHAR(ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), ID), 'MM')AS MO_NBR,
TO_CHAR(ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), ID), 'YYYY') AS YR_NBR
from Months;
What I really need to do is have this inserted into the temp table that I can recall. I do not have any fields that I can use from other tables either unfortunately. I need it to show 264 months from 1999.
Thank you

You can calculate a date column within the table expression, like this:
WITH Months AS (
SELECT LEVEL -1 AS ID, ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), LEVEL -1) as dt
FROM DUAL
CONNECT BY LEVEL <=264
)
SELECT *
from Months
If you are attempting to create date ranges, you could do this:
WITH Months AS (
SELECT LEVEL -1 AS ID
, ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), LEVEL -1) as start_dt
, ADD_MONTHS(TO_DATE('01/01/1999', 'DD/MM/YY'), LEVEL ) as end_dt
FROM DUAL
CONNECT BY LEVEL <=264
)
SELECT *
from yourtable t
inner join Months m on t.somecol >= m.start_dt and t.somecol < m.end_dt

Related

Tracking data change on Oracle 9 without timestamps or indexing

We're building a data warehouse on BigQuery, which includes data from a old Oracle 9 transactional database (still active), which does not include any indexing or timestamps.
Using Standard SQL, I would like to analyse changes in some tables imported from this database.
Simplifying the situation, imagine we have a two versions of the same table before and after as follows:
with before as (
select
'U123' as user, 'Gum' as product, '3' as quantity
union all
select
'U456', 'Tissue', '20'
union all
select
'U123', 'Cream', '1'
)
and
with after as (
select
'U123' as user, 'Gum' as product, '3' as quantity
union all
select
'U456', 'Tissue', '20'
union all
select
'U123', 'Cream', '3'
union all
select
'U456', 'Tomato', '5'
)
So that row 4 was added and row 3 modified.
What is the correct approach to compare data and locate changes given there is no indexing nor timestamps?
So the comparative method should output:
user | product | quantity
U123 | Cream | 3
U456 | Tomato | 5
I don't even know where to start.
Below is for BigQuery Standard SQL
#standardSQL
SELECT user, product, IFNULL(a.quantity, 0) - IFNULL(b.quantity, 0) AS quantity
FROM after a
FULL OUTER JOIN before b
USING(user, product)
WHERE IFNULL(a.quantity, 0) != IFNULL(b.quantity, 0)
When applied to sample data from your question as in below example
#standardSQL
WITH before AS (
SELECT 'U123' AS user, 'Gum' AS product, 3 AS quantity UNION ALL
SELECT 'U456', 'Tissue', 20 UNION ALL
SELECT 'U123', 'Cream', 1
), after AS (
SELECT 'U123' AS user, 'Gum' AS product, 3 AS quantity UNION ALL
SELECT 'U456', 'Tissue', 20 UNION ALL
SELECT 'U123', 'Cream', 3 UNION ALL
SELECT 'U456', 'Tomato', 5
)
SELECT user, product, IFNULL(a.quantity, 0) - IFNULL(b.quantity, 0) AS quantity
FROM after a
FULL OUTER JOIN before b
USING(user, product)
WHERE IFNULL(a.quantity, 0) != IFNULL(b.quantity, 0)
output is
Row user product quantity
1 U123 Cream 2
2 U456 Tomato 5
Oracle 9 keeps track of data change at Row level with the help of SCN (System Change Number). As a result any change performed through DML (INSERT/UPDATE) is internally captured with a TIMESTAMP.
How it works?
Create a Table with ROWDEPENDENCIES Option
Use SCN_TO_TIMESTAMP(ORA_ROWSCN) Function to get the TIMETAMP of Row Changes
Example:
-- Create Table
CREATE TABLE SCNTEST(USER NUMBER, PRODUCT NUMBER, QUANTITY NUMBER) ROWDEPENDENCIES;
-- Insert Data
INSERT ...
-- Query Data
SELECT USER, PRODUCT, QUANTITY, SCN_TO_TIMESTAMP(ORA_ROWSCN) FROM SCNTEST;
You can group data on SCN_TO_TIMESTAMP(ORA_ROWSCN) value to get before and after records.

How to convert this code from oracle to redshift?

I am trying to implement the same in redshift and i am finding it little difficult to do that. Since redshift is in top of postgresql engine, if any one can do it in postgresql it would be really helpfull. Basically the code gets the count for previous two month at column level. If there is no count for exact previous month then it gives 0.
This is my code:
with abc(dateval,cnt) as(
select 201908, 100 from dual union
select 201907, 200 from dual union
select 201906, 300 from dual union
select 201904, 600 from dual)
select dateval, cnt,
last_value(cnt) over (order by dateval
range between interval '1' month preceding
and interval '1' month preceding ) m1,
last_value(cnt) over (order by dateval
range between interval '2' month preceding
and interval '2' month preceding ) m2
from (select to_date(dateval, 'yyyymm') dateval, cnt from abc)
I get error in over by clause. I tried to give cast('1 month' as interval) but still its failing. Can someone please help me with this windows function.
expected output:
Regards
This is how I would do it. In Redshift there's no easy way to generate sequences, do I select row_number() from an arbitrary table to create a sequence:
with abc(dateval,cnt) as(
select 201908, 100 union
select 201907, 200 union
select 201906, 300 union
select 201904, 600),
cal(date) as (
select
add_months(
'20190101'::date,
row_number() over () - 1
) as date
from <an arbitrary table to generate a sequence of rows> limit 10
),
with_lag as (
select
dateval,
cnt,
lag(cnt, 1) over (order by date) as m1,
lag(cnt, 2) over (order by date) as m2
from abc right join cal on to_date(dateval, 'YYYYMM') = date
)
select * from with_lag
where dateval is not null
order by dateval

How can I update a column with PL\SQL by using a calculated value

I created a dummy database for learning purposes, and I purposefully created some duplicated records in one of the tables. In every case I want to flag one of the duplicated records as Latest='Y', and the other record as 'N', and for every single record the Latest flag would be 'Y'.
I tried to use PlSQL to go through all of my records, but when I try to use the previously calculated value (which would tell that its a duplicated record) it says that:
ORA-06550: line 20, column 17:
PLS-00201: identifier 'COUNTER' must be declared
Here is the statement I try to use:
DECLARE
CURSOR cur
IS
SELECT order_id, order_date, person_id,
amount, successfull_order, country_id, latest, ROWCOUNT AS COUNTER
FROM (SELECT order_id,
order_date,
person_id,
amount,
successfull_order,
country_id,
latest,
ROW_NUMBER () OVER (PARTITION BY order_id, order_date,
person_id, amount, successfull_order, country_id
ORDER BY order_id, order_date,
person_id, amount, successfull_order, country_id) ROWCOUNT
FROM orders) orders
FOR UPDATE OF orders.latest;
rec cur%ROWTYPE;
BEGIN
FOR rec IN cur
LOOP
IF MOD (COUNTER, 2) = 0
THEN
UPDATE orders
SET latest = 'N'
WHERE CURRENT OF cur;
ELSE
UPDATE orders
SET latest = 'Y'
WHERE CURRENT OF cur;
END IF;
END LOOP;
END;
I am new to PlSQL so I tried to modify the statements I found here:
http://www.adp-gmbh.ch/ora/plsql/cursors/for_update.html
What should I change in my statement, or should I use a different approach?
Thanks for your answers in advance!
Botond
Your refer the ROWNUM as COUNTER in your cursor.
While fetching, you should be accessing it from the cursor reference like MOD (rec.COUNTER, 2)
You need to declare the variable COUNTER and then you need to maintain (ie increment) it in your loop.
I suspect that you example is just for learning PL/SQL. However be aware that it's often much more performant to do things with a single SQL statement, as opposed to using cursor loops.
Your issue is that COUNTER is an attribute of the cursor record rec and not a PL/SQL variable. So:
IF MOD (COUNTER, 2) = 0
Should be:
IF MOD (rec.COUNTER, 2) = 0
However, you do not need to use PL/SQL or cursors, it can be done in a single MERGE statement:
Oracle Setup:
CREATE TABLE orders ( order_id, order_date, latest ) AS
SELECT 1, DATE '2017-01-01', CAST( NULL AS CHAR(1) ) FROM DUAL UNION ALL
SELECT 1, DATE '2017-01-02', NULL FROM DUAL UNION ALL
SELECT 1, DATE '2017-01-03', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2017-01-04', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2017-01-01', NULL FROM DUAL UNION ALL
SELECT 3, DATE '2017-01-06', NULL FROM DUAL;
Update Statement:
MERGE INTO orders dst
USING ( SELECT ROW_NUMBER() OVER ( PARTITION BY order_id
ORDER BY order_date DESC ) AS rn
FROM orders
) src
ON ( src.ROWID = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET latest = CASE src.rn WHEN 1 THEN 'Y' ELSE 'N' END;
Output:
SELECT * FROM orders;
ORDER_ID ORDER_DATE LATEST
-------- ---------- ------
1 2017-01-01 N
1 2017-01-02 N
1 2017-01-03 Y
2 2017-01-04 Y
2 2017-01-01 N
3 2017-01-06 Y

Query to find row till which sum less than an amount

I have an account where interest is debited corresponding to each account as below
amount Date
2 01-01-2012
5 02-01-2012
2 05-01-2012
1 07-01-2012
If the total credit in the account is 8. Ineed a query to find till what dates interest the credit amount can adjust.
Here the query should give output as 02-01-2012(2+5 < 8). I know this can be handled through cursor. But is there any method to write this as a single query in ORACLE.
SELECT pdate
FROM (
SELECT t.*,
LAG(date) OVER (ORDER BY date) AS pdate
8 - SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS diff
FROM mytable t
ORDER BY
date
)
WHERE diff < 0
AND rownum = 1
Not knowing the structure of your table, here's a guess:
SELECT date from your_table
GROUP BY AMOUNT
HAVING SUM(AMOUNT) < 8
Note: this is LESS THAN 8. Change the conditional as appropriate.
Doesn't do the (2+5)<8 thing yet:
select max(cum_sum), max(date)
from (
select date,
sum(amount) over (order by date) cum_sum
) where cum_sum < 8

Finding a count of rows in an arbitrary date range using Oracle

The question I need to answer is this "What is the maximum number of page requests we have ever received in a 60 minute period?"
I have a table that looks similar to this:
date_page_requested date;
page varchar(80);
I'm looking for the MAX count of rows in any 60 minute timeslice.
I thought analytic functions might get me there but so far I'm drawing a blank.
I would love a pointer in the right direction.
You have some options in the answer that will work, here is one that uses Oracle's "Windowing Functions with Logical Offset" feature instead of joins or correlated subqueries.
First the test table:
Wrote file afiedt.buf
1 create table t pctfree 0 nologging as
2 select date '2011-09-15' + level / (24 * 4) as date_page_requested
3 from dual
4* connect by level <= (24 * 4)
SQL> /
Table created.
SQL> insert into t values (to_date('2011-09-15 11:11:11', 'YYYY-MM-DD HH24:Mi:SS'));
1 row created.
SQL> commit;
Commit complete.
T now contains a row every quarter hour for a day with one additional row at 11:11:11 AM. The query preceeds in three steps. Step 1 is to, for every row, get the number of rows that come within the next hour after the time of the row:
1 with x as (select date_page_requested
2 , count(*) over (order by date_page_requested
3 range between current row
4 and interval '1' hour following) as hour_count
5 from t)
Then assign the ordering by hour_count:
6 , y as (select date_page_requested
7 , hour_count
8 , row_number() over (order by hour_count desc, date_page_requested asc) as rn
9 from x)
And finally select the earliest row that has the greatest number of following rows.
10 select to_char(date_page_requested, 'YYYY-MM-DD HH24:Mi:SS')
11 , hour_count
12 from y
13* where rn = 1
If multiple 60 minute windows tie in hour count, the above will only give you the first window.
This should give you what you need, the first row returned should have
the hour with the highest number of pages.
select number_of_pages
,hour_requested
from (select to_char(date_page_requested,'dd/mm/yyyy hh') hour_requested
,count(*) number_of_pages
from pages
group by to_char(date_page_requested,'dd/mm/yyyy hh')) p
order by number_of_pages
How about something like this?
SELECT TOP 1
ranges.date_start,
COUNT(data.page) AS Tally
FROM (SELECT DISTINCT
date_page_requested AS date_start,
DATEADD(HOUR,1,date_page_requested) AS date_end
FROM #Table) ranges
JOIN #Table data
ON data.date_page_requested >= ranges.date_start
AND data.date_page_requested < ranges.date_end
GROUP BY ranges.date_start
ORDER BY Tally DESC
For PostgreSQL, I'd first probably write something like this for a "window" aligned on the minute. You don't need OLAP windowing functions for this.
select w.ts,
date_trunc('minute', w.ts) as hour_start,
date_trunc('minute', w.ts) + interval '1' hour as hour_end,
(select count(*)
from weblog
where ts between date_trunc('minute', w.ts) and
(date_trunc('minute', w.ts) + interval '1' hour) ) as num_pages
from weblog w
group by ts, hour_start, hour_end
order by num_pages desc
Oracle also has a trunc() function, but I'm not sure of the format. I'll either look it up in a minute, or leave to see a friend's burlesque show.
WITH ranges AS
( SELECT
date_page_requested AS StartDate,
date_page_requested + (1/24) AS EndDate,
ROWNUMBER() OVER(ORDER BY date_page_requested) AS RowNo
FROM
#Table
)
SELECT
a.StartDate AS StartDate,
MAX(b.RowNo) - a.RowNo + 1 AS Tally
FROM
ranges a
JOIN
ranges b
ON a.StartDate <= b.StartDate
AND b.StartDate < a.EndDate
GROUP BY a.StartDate
, a.RowNo
ORDER BY Tally DESC
or:
WITH ranges AS
( SELECT
date_page_requested AS StartDate,
date_page_requested + (1/24) AS EndDate,
ROWNUMBER() OVER(ORDER BY date_page_requested) AS RowNo
FROM
#Table
)
SELECT
a.StartDate AS StartDate,
( SELECT MIN(b.RowNo) - a.RowNo
FROM ranges b
WHERE b.StartDate > a.EndDate
) AS Tally
FROM
ranges a
ORDER BY Tally DESC

Resources