First question here, so please be gentle. I've been a lurker for a long time and figured now's a great time to get involved.
Using Oracle OBIEE 12c, I'm looking to create a running counter in my result dataset for instances of a group of dimensions, which reset if the group starts again. Like this - in the below example the counter would consider Cust ID and Status:
Cust ID, Order ID, Status, Counter
111, 123456, APPROVED, 1
111, 123457, APPROVED, 2
111, 123458, APPROVED, 3
111, 123459, DECLINED, 1
111, 123460, APPROVED, 1
111, 123461, APPROVED, 2
222, 123462, APPROVED, 1
222, 123463, APPROVED, 2
Any ideas? I've tried a combination of case statements and RSUM(), but I can't quite get to what I'm after and I'm looking for a fresh approach.
Thanks
I'm not sure how you want your data ordered, so that will matter. It looks like you are ordering by ORDER ID.
You can use a count with Partitioning. Though this may not work depending on how your data is ordered.
SELECT CUSTID, ORDERID, STATUS, count(*)
over (PARTITION BY CUSTID||STATUS ORDER BY ORDERID ROWS UNBOUNDED PRECEDING) COUNTER
FROM MYTEST;
You can also use LAG to check for row changes, for example:
SELECT CUSTID, ORDERID, STATUS, curr_row, prev_row
,CASE WHEN curr_row != prev_row THEN 'Change' ELSE 'Same Group' END as TRACKER
FROM (
SELECT CUSTID, ORDERID, STATUS, CUSTID||STATUS AS curr_row
,LAG(CUSTID||STATUS) OVER (ORDER BY CUSTID, ORDERID, STATUS) AS prev_row
FROM MYTEST
ORDER BY ORDERID
)
;
The above examples are using this set of test data:
Test Data
You should probably calculate the counter outside of OBIEE as part of the extract/load/transform process.
Here is an approximation using the ROW_NUMBER analytical function in Oracle.
with dataset as (
SELECT 111 AS cust_id,
123456 AS order_id,
'APPROVED' AS status,
1 AS expected_counter
FROM dual
UNION ALL
SELECT 111,123457,'APPROVED',2 FROM dual
UNION ALL
SELECT 111,123458,'APPROVED',3 FROM dual
UNION ALL
SELECT 111,123459,'DECLINED',1 FROM dual
UNION ALL
SELECT 111,123460,'APPROVED',1 FROM dual
UNION ALL
SELECT 111,123461,'APPROVED',2 FROM dual
UNION ALL
SELECT 222,1234562,'APPROVED',1 FROM dual
UNION ALL
SELECT 222,1234563,'APPROVED',2 FROM dual
)
select cust_id,order_id,status,expected_counter,row_number() over (partition by cust_id,status order by order_id asc) as counter from dataset ;
The result is:
cust_id /order_id /status / your_counter / my_counter
111 123456 APPROVED 1 1
111 123457 APPROVED 2 2
111 123458 APPROVED 3 3
111 123460 APPROVED 1 4
111 123461 APPROVED 2 5
111 123459 DECLINED 1 1
222 1234562 APPROVED 1 1
222 1234563 APPROVED 2 2
This example helps you find out that customer 111 has 5 approved orders and 1 that was declined; customer 222 has 2 approved orders. I could not match your exact counter. Hope this helps
In OBIEE analysis column formula syntax that'd be sth like
RCOUNT("Orders"."Status" by "Customers"."Cust ID")
Related
I have following table and I want to get records where promotion is mostly occurring.
For example if I got two events
FREQ_VISITOR, value= 250
HIGH_SHOPPER, value= 320
Then Promo 1 and Promo 2 should come in result. Since these 2 promos exists mostly for every trigger and their given values.
Here's one option, based on what I understood:
SQL> with test (event_name, value, promotion) as
2 (select 'freq_visitor', 250, 'promo1' from dual union all
3 select 'high_shopper', 320, 'promo2' from dual union all
4 select 'freq_visitor', 250, 'promo3' from dual union all
5 select 'high_shopper', 320, 'promo1' from dual union all
6 select 'freq_visitor', 250, 'promo2' from dual
7 ),
8 cnt_promo as
9 (select promotion, count(*) cnt
10 from test
11 group by promotion
12 ),
13 most_promos as
14 (select max(cnt) max_cnt
15 from cnt_promo
16 )
17 select c.promotion
18 from cnt_promo c join most_promos m on c.cnt = m.max_cnt;
PROMOT
------
promo1
promo2
SQL>
This is a good candidate for analytic functions.
The below code is a bit longer than the self-join approach, and flows from the inside-out instead of a more traditional top-to-bottom direction. But this approach will likely be faster, since it only reads from the table once. And this approach is easier to debug than common table expressions, since you can highlight and run different inline views and watch the result set be built.
--Promotions with the highest counts.
select promotion
from
(
--RANK the promotion counts.
select promotion, promotion_count,
rank() over (order by promotion_count desc) promotion_rank
from
(
--Count of promos per event and value.
select promotion, count(*) promotion_count
from
(
--Test data
select 'freq_visitor' event_name, 250 value, 'promo1' promotion from dual union all
select 'high_shopper' event_name, 320 value, 'promo2' promotion from dual union all
select 'freq_visitor' event_name, 250 value, 'promo3' promotion from dual union all
select 'high_shopper' event_name, 320 value, 'promo1' promotion from dual union all
select 'freq_visitor' event_name, 250 value, 'promo2' promotion from dual
) test_data
group by promotion
) add_promo_count
) add_promo_rank
where promotion_rank = 1
order by promotion;
Table: HISTORY
CUSTOMER MONTH PLAN
1 1 A
1 2 A
1 2 B
1 3 B
In this example customer 1 had plan A and changed to B on month 2. I need to remove the change from month 2 and keep only the plan the customer migrate to, as in:
CUSTOMER MONTH PLAN
1 1 A
1 2 B
1 3 B
I've tried using sys_connect_by_path:
select month, CUSTOMER, level,
sys_connect_by_path(PLAN, '/') as path
from a
start with month = 1
connect by prior MONTH = MONTH - 1
But it doesn't seem to be right. Whats an efficient way of doing it in Oracle 12c?
I'm not sure whether you understood what comments have said - it is rows 2 and 3 that are questionable because there's no way to know which one of those happened first.
Anyway, as you said that there's nothing else in that table that would help us decide, how about something like this? Compare current plan with the next plan (sorted by month) and pick rows where there's no change in plan.
SQL> with test (customer, month, plan) as
2 (select 1, 1, 'A' from dual union all
3 select 1, 2, 'A' from dual union all
4 select 1, 2, 'B' from dual union all
5 select 1, 3, 'B' from dual
6 ),
7 inter as
8 (select customer, month, plan,
9 nvl(lead(plan) over (partition by customer order by month), plan) lead_plan
10 from test
11 )
12 select customer, month, plan
13 from inter
14 where plan = lead_plan
15 order by month;
CUSTOMER MONTH PLAN
---------- ---------- -----
1 1 A
1 2 B
1 3 B
SQL>
You could use an analytic lead() call to peek at the following month, and decide whether to use the current or following month's plan:
-- CTE for your sample data
with history (customer, month, plan) as (
select 1, 1, 'A' from dual
union all select 1, 2, 'A' from dual
union all select 1, 2, 'B' from dual
union all select 1, 3, 'B' from dual
)
-- actual query
select distinct customer, month,
case
when lead(plan) over (partition by customer order by month) != plan
then lead(plan) over (partition by customer order by month)
else plan
end as plan
from history;
CUSTOMER MONTH P
---------- ---------- -
1 1 A
1 2 B
1 3 B
You could move the lead() calculation into an inline view to reduce the repetition if you prefer.
This will probably break in interesting ways if a customer changes plan on consecutive months though, or possibly if they can change more than once in one month.
hoping I might be able to get some advise regarding Oracle SQL…
I have a table roughly as follows (there are more columns, but not necessary for this example)…
LOCATION USER VALUE
1 1 10
1 2 20
1 3 30
2 4 10
2 5 10
2 6 20
1 60
2 40
100
I’ve used rollup to get subtotals.
What I need to do is get the max(value) row for each location and express the max(value) as a percentage or fraction of the subtotal for each location
ie:
LOCATION USER FRAC
1 3 0.5
2 6 0.5
I could probably solve this using my limited knowledge of select queries, but am guessing there must be a fairly quick and slick method..
Thanks in advance :)
Solution using analytic functions
(Please note the WITH MY_TABLE AS serving only as dummy datasource)
WITH MY_TABLE AS
( SELECT 1 AS LOC_ID,1 AS USER_ID, 10 AS VAL FROM DUAL
UNION
SELECT 1,2,20 FROM DUAL
UNION
SELECT 1,3,30 FROM DUAL
UNION
SELECT 2,4,10 FROM DUAL
UNION
SELECT 2,5,10 FROM DUAL
UNION
SELECT 2,6,20 FROM DUAL
)
SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC,
RANK() OVER (PARTITION BY LOC_ID ORDER BY RATIO_IN_LOC DESC) AS ORDER_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
VAL,
VAL/SUM(VAL) OVER (PARTITION BY LOC_ID) AS RATIO_IN_LOC
FROM MY_TABLE
)
)
WHERE ORDER_IN_LOC = 1
ORDER BY LOC_ID,
USER_ID;
Result
LOC_ID USER_ID RATIO_IN_LOC
1 3 0,5
2 6 0,5
with inputs ( location, person, value ) as (
select 1, 1, 10 from dual union all
select 1, 2, 20 from dual union all
select 1, 3, 30 from dual union all
select 2, 4, 10 from dual union all
select 2, 5, 10 from dual union all
select 2, 6, 20 from dual
),
prep ( location, person, value, m_value, total ) as (
select location, person, value,
max(value) over (partition by location),
sum(value) over (partition by location)
from inputs
)
select location, person, round(value/total, 2) as frac
from prep
where value = m_value;
Notes: Your table exists already? Then skip everything from "inputs" to the comma; your query should begin with with prep (...) as ( ...
I changed user to person since user is a keyword in Oracle, you shouldn't use it for table or column names (actually you can't unless you use double quotes, which is a very poor practice).
The query will output two or three or more rows per location if there are ties at the top. Presumably this is what you desire.
Output:
LOCATION PERSON FRAC
---------- ---------- ----------
1 3 .5
2 6 .5
I am relatively new to Pl Sql and have to write a procedure which does following, It has got 6-7 different queries which are as follows :
Select manager, count(*) bank from abc ..........
select manager , count(*) retail from abc .......
Now each count is for a different department , while manager may be same . So problem is that I am looking for a best approach which helps me to store
the data in separate table which has following structure:
Manager : Count Bank : Count Retail : Count xyz ...........
How do I make sure using Pl sql that each manager has right count for all the columns(bank/retai/xyz) , the first thing is how to store multiple different query result and process it . I think we can use cursor but I need to research on it , also this procedure should be high performance .
Please suggest.
Thanks
XslGuy
This is easily achievable with conditional sums. Hopefully the below will give you enough of an idea of what you need to do:
with sample_data as (select 1 id, 1 dept, 10 val from dual union all
select 2 id, 1 dept, 20 val from dual union all
select 3 id, 2 dept, 30 val from dual union all
select 4 id, 3 dept, 40 val from dual union all
select 5 id, 1 dept, 50 val from dual union all
select 6 id, 3 dept, 60 val from dual union all
select 7 id, 2 dept, 70 val from dual union all
select 8 id, 4 dept, 80 val from dual)
-- end of creating a subquery that contains some sample data. See sql below:
select sum(case when dept = 1 then val end) dept_1_total,
sum(case when dept = 2 then val end) dept_2_total,
sum(case when dept = 3 then val end) dept_3_total
from sample_data
where dept in (1, 2, 3);
DEPT_1_TOTAL DEPT_2_TOTAL DEPT_3_TOTAL
------------ ------------ ------------
80 100 100
i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.