Oracle Quarter SQL Query with top three products - oracle

I am working on a small project in Oracle. I need to get the top three best-selling products and the total amounts taken on these for the year and for each of the four quarters April-June, July-September, October-December and January-March. I have already found the first part, I simply need help getting the 4 quarter totals for each product. Hope someone can help, thank you
This is the SQL commands used so far:
select * from (
select "FACTQUANTITY"."PRODUCTID" as "PRODUCTID",
"DIMPRODUCT"."PRODUCTNAME" as "PRODUCTNAME",
sum(FACTQUANTITY.QUANTITY) as "QUANTITY"
from "FACTQUANTITY" "FACTQUANTITY",
"DIMPRODUCT" "DIMPRODUCT"
where "DIMPRODUCT"."PRODUCTID"="FACTQUANTITY"."PRODUCTID"
group by FACTQUANTITY.PRODUCTID,
DIMPRODUCT.PRODUCTNAME
order by sum(FACTQUANTITY.QUANTITY) desc
)
WHERE ROWNUM <= 3;

You can start from here:
select
trunc(fact_table.date_column,'Q') as quarter,
"FACTQUANTITY"."PRODUCTID" ,
"DIMPRODUCT"."PRODUCTNAME",
sum(FACTQUANTITY.QUANTITY) as "QUANTITY"
from "FACTQUANTITY" JOIN "DIMPRODUCT"
ON "DIMPRODUCT"."PRODUCTID"="FACTQUANTITY"."PRODUCTID"
group by
FACTQUANTITY.PRODUCTID, DIMPRODUCT.PRODUCTNAME, trunc(fact_table.date_column,'Q')
;
Subsequently, you can:
with a as (<previous query>)
select *
from (
select
quarter,
productid,
productname,
quantity,
row_number() over (partition by quarter order by quantity desc) rnk
from a
)
where rnk <= 3;

SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER ( PARTITION BY quarter
ORDER BY quantity DESC, PRODUCTNAME ASC ) AS rn
FROM (
SELECT TRUNC( f.datetime, 'Q' ) AS quarter,
f.PRODUCTID,
d.PRODUCTNAME,
sum(f.QUANTITY) AS QUANTITY
FROM FACTQUANTITY f
INNER JOIN
DIMPRODUCT d
ON ( d.PRODUCTID = f.PRODUCTID )
GROUP BY TRUNC( f.datetime, 'Q' ),
FACTQUANTITY.PRODUCTID,
DIMPRODUCT.PRODUCTNAME
)
)
WHERE rn <= 3;

Related

Please help me to optimize the below mentioned script as the same table(i.e. Incident_Audit_log) has utilized multiple times?

select A.*
from Incident_Audit_log a where incident_audit_log_id in
(select top 1 incident_audit_log_id from Incident_Audit_log b
where b.incident_id=a.incident_id and b.status_did=a.status_did
and b.tracking_code_did = (select tracking_code_did
from Incident_Audit_log where update_date = (select MAX(update_date)
from Incident_Audit_log where Status_did in (103, 1035)
and incident_id = b.incident_id)
and incident_id = b.incident_id)
order by update_date asc)
I am not sure what you want to achieve but I guess that you want to extract row with new newest update and status_did equal to 13 and 1035.
In that case this should work:
select *
from (
select ROW_NUMBER() OVER(ORDER BY update_date DESC) AS rn,
*
from Incident_Audit_log
where status_did in (103, 1035)
) as SubQueryAlias
where rn = 1
In case not , provide more info.

Oracle GRoup by 7 Days

I'm trying to group seven days period for each date, but this seems not to work.
select
START_DATE-7 FROM_DATES,
SUM(EVENT_B),
SUM(EVENT_B)
FROM (
select
nvl(ENT1.DATE_FIELD,ENT2.DATE_FIELD)as START_DATE,
nvl(ENT1.EVENT_A,0) as EVENT_A,
nvl(ENT2.EVENT_B,0) as EVENT_B
from
(select
DATE_FIELD, SUM(NR_EVENTS) as EVENT_A
from TABLE_A
where EVENT_NAME in ('CALL', 'EDIT','INSERT')
group by START_DATE,START_HOUR,SUBSCRIBER_TYPE,CO_ID
) ENT1
FULL OUTER JOIN
(select
DATE_FIELD,
SUM(NR_EVENTS) as EVENT_B
from TABLE_B
where EVENT_NAME in ('CALL', 'EDIT','INSERT')
group by DATE_FIELD
) ENT2
on (
ENT1.DATE_FIELD=ENT2.DATE_FIELD)
)
WHERE
START_DATE >= START_DATE-7
AND START_DATE <= START_DATE-1
gROUP BY
START_DATE-7
The output im getting:
9/8/2014 262187 280365
9/7/2014 509405 478245
9/6/2014 564663 537996
9/5/2014 661871 632689
9/4/2014 669788 638839
Its only sum for one day not 7
The output should be
2014-09-15, (sum of event from A side in a 7 days period - 2014-09-15-7 to 2014-09-15) , (sum of event from B side in a 7 days period - 2014-09-15-7 to 2014-09-15)
2014-09-14, (sum of event from A side in a 7 days period - 2014-09-14-7 to 2014-09-14) , (sum of event from B side in a 7 days period - 2014-09-14-7 to 2014-09-14)
2014-09-13, (sum of event from A side in a 7 days period - 2014-09-13-7 to 2014-09-13) , (sum of event from B side in a 7 days period - 2014-09-13-7 to 2014-09-13)
etc...
Can anyone help me out?
with event_dates as (
select d_field from table_a
union
select d_field from table_b
)
select
d_field from_date,
d_field to_date,
(
select nvl(sum(nr_events),0) from table_a
where event_name in ('CALL', 'EDIT', 'INSERT')
and d_field between e.d_field and e.d_field+6
) nr_events_a,
(
select nvl(sum(nr_events),0) from table_b
where event_name in ('CALL', 'EDIT', 'INSERT')
and d_field between e.d_field and e.d_field+6
) nr_events_b
from event_dates e;
I made SQL Fiddle based on your original post and notes from Lalit Kumar B. Answer above includes event_name field filter which was later added.
Your query is incorrect.
Firstly, your output doesn't match with the selected columns. If 1st column is D_FIELD1+7, then how is that in the output the first column values are D_FIELD1?
Secondly, the predicate AND D_FIELD1 BETWEEN D_FIELD1+7 AND D_FIELD1 is ambiguous.
Thirdly, BETWEEN will include the upper and lower bound, so you will get the window of 8 days and not 7 days.
You could use an Analytic function with an Interval e.g.
SELECT nvl(ent1.date_field, ent2.date_field) - 6 start_date
, nvl(ent1.date_field, ent2.date_field) end_date
, sum(event_a) OVER
(ORDER BY nvl(ent1.date_field, ent2.date_field)
RANGE numtodsinterval(6, 'day') PRECEDING
) sum_event_a
, sum(event_b) OVER
(ORDER BY nvl(ent1.date_field, ent2.date_field)
RANGE numtodsinterval(6, 'day') PRECEDING
) sum_event_b
FROM (SELECT date_field,
sum(nr_events) AS event_a
FROM table_a
WHERE event_name IN ('CALL', 'EDIT', 'INSERT')
GROUP BY date_field
) ent1
FULL OUTER JOIN
(SELECT date_field,
sum(nr_events) AS event_b
FROM table_b
WHERE event_name IN ('CALL', 'EDIT', 'INSERT')
GROUP BY date_field
) ent2
ON (ent1.date_field = ent2.date_field)
I suggest you change the way of get the sums, I prefer in this case analytic functions. Try something like this:
SELECT START_DATE - 7 FROM_DATES,
SUM (EVENT_A) OVER (ORDER BY START_DATE RANGE 7 PRECEDING),
SUM (EVENT_B) OVER (ORDER BY START_DATE RANGE 7 PRECEDING)
FROM (SELECT NVL (ENT1.DATE_FIELD, ENT2.DATE_FIELD) AS START_DATE,
NVL (ENT1.EVENT_A, 0) AS EVENT_A,
NVL (ENT2.EVENT_B, 0) AS EVENT_B
FROM ( SELECT DATE_FIELD, SUM (NR_EVENTS) AS EVENT_A
FROM TABLE_A
WHERE EVENT_NAME IN ('CALL', 'EDIT', 'INSERT')
GROUP BY START_DATE,
START_HOUR,
SUBSCRIBER_TYPE,
CO_ID) ENT1
FULL OUTER JOIN
( SELECT DATE_FIELD, SUM (NR_EVENTS) AS EVENT_B
FROM TABLE_B
WHERE EVENT_NAME IN ('CALL', 'EDIT', 'INSERT')
GROUP BY DATE_FIELD) ENT2
ON (ENT1.DATE_FIELD = ENT2.DATE_FIELD))

self join with max value

I am have a table with 500k transactions. I want to fetch the last balance for a particular date. So I have have returned a query like below.
SELECT curr_balance
FROM transaction_details
WHERE acct_num = '10'
AND is_deleted = 'N'
AND ( value_date, srl_num ) IN(
SELECT MAX( value_date ), MAX( srl_num )
FROM transaction_details
WHERE TO_DATE( value_date, 'dd/mm/yyyy' )
<= TO_DATE( ADD_MONTHS( '05-APR-2012', 1 ), 'dd/mm/yyyy' )
AND acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y' )
AND ver_status = 'Y'
This has to be executed for incrementing of 12 months to find the last balance for each particular month. But this query is having more cpu cost, 12 times it is taking huge time. How to remodify the above query to get the results in faster way. Whether this can be broken into two part in PL/SQL to achieve the performance. ?
Try:
select * from(
SELECT value_date, srl_num, curr_balance
FROM transaction_details
WHERE acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y'
row_number() over (partition by trunc(value_date - interval '5' day,'MM')
order by srl_num desc
) as rnk
)
where rnk = 1;
You'll get a report with the ballance on last srl_num on each month in your table.
The benefit is that your approach scans the table 24 times for 12 months report and my approach scans the table once.
The analytic function gets the rank of record in current month(partition by clause) ordering the rows in the month after srl_num.
You don't have to query your table twice. Try using analytic functions
SELECT t.curr_balance
-- , any other column you want as long it is in the subselect.
FROM (
SELECT
trans.curr_balance
, trans.value_date
-- any other column you want
, trans.srl_num
, MAX(trans.srl_num) OVER(PARTITION BY trans.value_date, trans.srl_num) max_srl_num
, MAX(trans.value_date) OVER(PARTITION BY trans.value_date, trans.srl_num) max_date
FROM transaction_details trans
WHERE TO_DATE( value_date, 'dd/mm/yyyy' ) <= TO_DATE( ADD_MONTHS( '01-APR-2012', 1 ), 'dd/mm/yyyy' )
AND acct_num = '10'
AND is_deleted = 'N'
AND ver_status = 'Y'
) t
WHERE t.max_date = t.value_date
AND t.max_srl_num = t.srl_num
A couple of thoughts.
Why do you have TO_DATE( value_date...? Isn't your data type DATE? this might be breaking your index if you have one in that column.
Note that (this is a wild guess) if your srl_num is not the highest for the latest date, you will have incorrect results and might not return any rows.

Can one not reference a CTE twice in a query in Oracle?

I have a chain of CTEs, and at the end I want to select from the last one twice. Oracle lets me do either of the selects but not a union of both of them (even if I do "select * from (select union select)". The only thing I could narrow down the problem to was referring to "runinfo" in two selects, but that in and of itself is not the problem as Shannon shows.
WITH lastTen AS (
SELECT id
FROM (SELECT autobuild_id, id, rank() OVER (PARTITION BY autobuild_id ORDER BY id DESC) as rank
FROM runs
WHERE status='FINISHED' AND type='FULL' AND build_failed in ('n', 'N'))
WHERE rank <= 10
),
recentAvg AS (
SELECT autobuild_id, avg(elapsed) avgtime
FROM runs
JOIN lastTen ON (runs.id = lastTen.id)
GROUP BY autobuild_id
),
runinfo AS (
SELECT autobuildid, runid, changelist, status, age
FROM (
SELECT runs.autobuild_id autobuildid, runs.id runid, changelist, runs.status status, runs.create_date, a.avgtime,
CASE WHEN status = 'RUNNING' THEN TO_NUMBER(sysdate - start_date)*86400 -- in seconds to compare to elapsed
WHEN status = 'TO BE' THEN TO_NUMBER(sysdate - create_date) -- in days
END AS age
FROM runs
LEFT JOIN recentAvg a ON (runs.autobuild_id = a.autobuild_id)
)
WHERE (status = 'RUNNING' AND age > avgtime * 1.5)
OR (status = 'TO BE' AND age > 1)
ORDER BY autobuildid, runid DESC
),
running AS (
SELECT autobuilds.id, name, runid, changelist, runinfo.status, age
FROM runinfo
JOIN autobuilds ON (runinfo.autobuildId=autobuilds.id)
WHERE runinfo.status='RUNNING'
),
tobe AS (
SELECT autobuildid, name, runid, changelist, status, age
FROM (SELECT autobuildid, name, runid, changelist, runinfo.status, age, RANK() OVER (PARTITION BY autobuildid ORDER BY runid DESC) AS rank
FROM runinfo
JOIN autobuilds ON (runinfo.autobuildid=autobuilds.id)
WHERE runinfo.status='TO BE')
WHERE rank=1
)
SELECT * FROM running
UNION ALL
SELECT * FROM tobe
What is the simpliest query that doesn't work for you?
What is the complete error message?
Oracle does allow a CTE to be referenced twice, but I am not understanding what you are attempting:
SQL> with a as (select * from dual)
2 , b as (select * from a)
3 , c as (select * from b)
4 select *
5 from b, c
6 where b.dummy = c.dummy;
D D
- -
X X

How do I get rowcount of a cte in a separate dataset?

I have identified a way to get fast paged results from the database using CTEs and the Row_Number function, as follows...
DECLARE #PageSize INT = 1
DECLARE #PageNumber INT = 2
DECLARE #Customer TABLE (
ID INT IDENTITY(1, 1),
Name VARCHAR(10),
age INT,
employed BIT)
INSERT INTO #Customer
(name,age,employed)
SELECT 'bob',21,1
UNION ALL
SELECT 'fred',33,1
UNION ALL
SELECT 'joe',29,1
UNION ALL
SELECT 'sam',16,1
UNION ALL
SELECT 'arthur',17,0;
WITH cteCustomers
AS ( SELECT
id,
Row_Number( ) OVER(ORDER BY Age DESC) AS Row
FROM #Customer
WHERE employed = 1
/*Imagine I've joined to loads more tables with a really complex where clause*/
)
SELECT
name,
age,
Total = ( SELECT
Count( id )
FROM cteCustomers )
FROM cteCustomers
INNER JOIN #Customer cust
/*This is where I choose the columns I want to read, it returns really fast!*/
ON cust.id = cteCustomers.id
WHERE row BETWEEN ( #PageSize * #PageNumber - 1 ) AND ( #PageSize * ( #PageNumber ) )
ORDER BY row ASC
Using this technique the returned results is really really fast even on complex joins and filters.
To perform paging I need to know the Total Rows returned by the full CTE. I have "Bodged" this by putting a column that holds it
Total = ( SELECT
Count( id )
FROM cteCustomers )
Is there a better way to return the total in a different result set without bodging it into a column? Because it's a CTE I can't seem to get it into a second result set.
Without using a temp table first, I'd use a CROSS JOIN to reduce the risk of row by row evaluation on the COUNT
To get total row, this needs to happen separately to the WHERE
WITH cteCustomers
AS ( SELECT
id,
Row_Number( ) OVER(ORDER BY Age DESC) AS Row
FROM #Customer
WHERE employed = 1
/*Imagine I've joined to loads more tables with a really complex where clause*/
)
SELECT
name,
age,
Total
FROM cteCustomers
INNER JOIN #Customer cust
/*This is where I choose the columns I want to read, it returns really fast!*/
ON cust.id = cteCustomers.id
CROSS JOIN
(SELECT Count( *) AS Total FROM cteCustomers ) foo
WHERE row BETWEEN ( #PageSize * #PageNumber - 1 ) AND ( #PageSize * ( #PageNumber ) )
ORDER BY row ASC
However, this isn't guaranteed to give accurate results as demonstrated here:
can I get count() and rows from one sql query in sql server?
Edit: after a few comments.
How to avoid a CROSS JOIN
WITH cteCustomers
AS ( SELECT
id,
Row_Number( ) OVER(ORDER BY Age DESC) AS Row,
COUNT(*) OVER () AS Total --the magic for this edit
FROM #Customer
WHERE employed = 1
/*Imagine I've joined to loads more tables with a really complex where clause*/
)
SELECT
name,
age,
Total
FROM cteCustomers
INNER JOIN #Customer cust
/*This is where I choose the columns I want to read, it returns really fast!*/
ON cust.id = cteCustomers.id
WHERE row BETWEEN ( #PageSize * #PageNumber - 1 ) AND ( #PageSize * ( #PageNumber ) )
ORDER BY row ASC
Note: YMMV for performance depending on 2005 or 2008, Service pack etc
Edit 2:
SQL Server Central shows another technique where you have reverse ROW_NUMBER. Looks useful
#Digiguru
OMG, this really is the wholy grail!
WITH cteCustomers
AS ( SELECT id,
Row_Number() OVER(ORDER BY Age DESC) AS Row,
Row_Number() OVER(ORDER BY id ASC)
+ Row_Number() OVER(ORDER BY id DESC) - 1 AS Total /*<- voodoo here*/
FROM #Customer
WHERE employed = 1
/*Imagine I've joined to loads more tables with a really complex where clause*/
)
SELECT name, age, Total
/*This is where I choose the columns I want to read, it returns really fast!*/
FROM cteCustomers
INNER JOIN #Customer cust
ON cust.id = cteCustomers.id
WHERE row BETWEEN ( #PageSize * #PageNumber - 1 ) AND ( #PageSize * ( #PageNumber ) )
ORDER BY row ASC
So obvious now.

Resources