I have the following dataset (table: stk):
S_Date Qty OOS (Out of Stock - 1 true, 0 false)
01/01/2013 0 1
02/01/2013 0 1
03/01/2013 0 1
04/01/2013 5 0
05/01/2013 0 1
06/01/2013 0 1
And what I want is:
S_Date Qty Cumulative_Days_OOS
01/01/2013 0 1
02/01/2013 0 2
03/01/2013 0 3
04/01/2013 5 0 -- No longer out of stock
05/01/2013 0 1
06/01/2013 0 2
The closest I've got so far is the following SQL:
SELECT
S_DATE, QTY,
SUM(OOS) OVER (PARTITION BY OOS ORDER BY S_DATE) CUMLATIVE_DAYS_OOS
FROM
STK
GROUP BY
S_DATE, QTY, OOS
ORDER BY
1
This gives me the following output:
S_Date Qty Cumulative_Days_OOS
01/01/2013 0 1
02/01/2013 0 2
03/01/2013 0 3
04/01/2013 5 0
05/01/2013 0 4
06/01/2013 0 5
It is close to what I want, but understandably, the sum is continued.
Is it possible to reset this cumulative sum and start it again?
I've tried searching around on stackoverflow and google, but I'm not really sure what I should be searching for.
Any help much appreciated.
You need to identify groups of consecutive days where oos = 1 or 0. This can be done by using LAG function to find when oos column changes and then summing over it.
with x (s_date,qty,oos,chg) as (
select s_date,qty,oos,
case when oos = lag(oos,1) over (order by s_date)
then 0
else 1
end
from stk
)
select s_date,qty,oos,
sum(chg) over (order by s_date) grp
from x;
output :
| S_DATE | QTY | OOS | GRP |
|--------------------------------|-----|-----|-----|
| January, 01 2013 00:00:00+0000 | 0 | 1 | 1 |
| January, 02 2013 00:00:00+0000 | 0 | 1 | 1 |
| January, 03 2013 00:00:00+0000 | 0 | 1 | 1 |
| January, 04 2013 00:00:00+0000 | 5 | 0 | 2 |
| January, 05 2013 00:00:00+0000 | 0 | 1 | 3 |
| January, 06 2013 00:00:00+0000 | 0 | 1 | 3 |
Then, you can sum over this oos, partitioned by grp column to get consecutive oos days.
with x (s_date,qty,oos,chg) as (
select s_date,qty,oos,
case when oos = lag(oos,1) over (order by s_date)
then 0
else 1
end
from stk
),
y (s_date,qty,oos,grp) as (
select s_date,qty,oos,
sum(chg) over (order by s_date)
from x
)
select s_date,qty,oos,
sum(oos) over (partition by grp order by s_date) cum_days_oos
from y;
output:
| S_DATE | QTY | OOS | CUM_DAYS_OOS |
|--------------------------------|-----|-----|--------------|
| January, 01 2013 00:00:00+0000 | 0 | 1 | 1 |
| January, 02 2013 00:00:00+0000 | 0 | 1 | 2 |
| January, 03 2013 00:00:00+0000 | 0 | 1 | 3 |
| January, 04 2013 00:00:00+0000 | 5 | 0 | 0 |
| January, 05 2013 00:00:00+0000 | 0 | 1 | 1 |
| January, 06 2013 00:00:00+0000 | 0 | 1 | 2 |
Demo at sqlfiddle.
First we need to divide rows to groups. In this case you can use the count of 0 values before current row as a group number. And then you can use SUM() OVER for these groups. To get 0 for OOS = 0 you can use CASE statement or just OOS*SUM(OOS) as soon as OOS = (0,1)
Something like this:
select T1.*,
OOS*SUM(OOS) OVER (PARTITION BY GRP ORDER BY S_DATE) CUMLATIVE_DAYS_OOS
FROM
(
select T.*,
(select count(*) from STK where S_Date<T.S_Date and OOS=0) GRP
FROM STK T
) T1
ORDER BY S_Date
SQLFiddle demo
Related
I have an Oracle query that uses DUAL to produce a list of dates in a subquery, and a case when to identify business days:
SELECT DATES
,case when to_char(DATES, 'd') in (1,7)
then 0
else 1 end as business_day
FROM (
SELECT to_date('1/1/2020','MM/DD/YYYY') + (LEVEL -1) AS DATES
FROM DUAL connect by level <=(to_date('1/1/2021','MM/DD/YYYY') - to_date('1/1/2020','MM/DD/YYYY'))
) L1
So far so good. Now when I nest this in a subquery, and add a row_number() function, all my business_day values become 0. If I remove the row_number() function, business_day goes back to normal.
SELECT L2.DATES
, L2.business_day
, row_number() OVER (PARTITION BY L2.business_day ORDER BY L2.DATES ASC) as dateindex
FROM (
SELECT DATES
,case when to_char(DATES, 'd') in (1,7)
then 0
else 1 end as business_day
FROM (
SELECT to_date('1/1/2020','MM/DD/YYYY') + (LEVEL -1) AS DATES
FROM DUAL connect by level <=(to_date('1/1/2021','MM/DD/YYYY') - to_date('1/1/2020','MM/DD/YYYY'))
) L1
) L2
Any idea how adding a new column causes another's values to change?
I suspect you aren't paying attention to the actual dates; running your code in this db<>fiddle, the first query returns:
DATES | BUSINESS_DAY
:-------- | -----------:
01-JAN-20 | 1
02-JAN-20 | 1
03-JAN-20 | 1
04-JAN-20 | 1
05-JAN-20 | 0
06-JAN-20 | 0
07-JAN-20 | 1
08-JAN-20 | 1
09-JAN-20 | 1
10-JAN-20 | 1
11-JAN-20 | 1
...
while the second returns:
DATES | BUSINESS_DAY | DATEINDEX
:-------- | -----------: | --------:
05-JAN-20 | 0 | 1
06-JAN-20 | 0 | 2
12-JAN-20 | 0 | 3
13-JAN-20 | 0 | 4
19-JAN-20 | 0 | 5
20-JAN-20 | 0 | 6
26-JAN-20 | 0 | 7
27-JAN-20 | 0 | 8
02-FEB-20 | 0 | 9
03-FEB-20 | 0 | 10
...
All the business_day values are indeed zero... or at least, if you only look at the start of the result set. If you look further down:
...
27-DEC-20 | 0 | 103
28-DEC-20 | 0 | 104
01-JAN-20 | 1 | 1
02-JAN-20 | 1 | 2
03-JAN-20 | 1 | 3
04-JAN-20 | 1 | 4
...
You don't have an order-by clause, and the analytic processing internally happens to return in an order you aren't expecting. If you add an order-by then it looks more sensible, as in this db<>fiddle:
DATES | BUSINESS_DAY | DATEINDEX
:-------- | -----------: | --------:
01-JAN-20 | 1 | 1
02-JAN-20 | 1 | 2
03-JAN-20 | 1 | 3
04-JAN-20 | 1 | 4
05-JAN-20 | 0 | 1
06-JAN-20 | 0 | 2
07-JAN-20 | 1 | 5
08-JAN-20 | 1 | 6
09-JAN-20 | 1 | 7
10-JAN-20 | 1 | 8
11-JAN-20 | 1 | 9
12-JAN-20 | 0 | 3
...
Incidentally, the 'd' format element is NLS-sensitive, so someone else running this code in a session with different settings could see different results. It would safer to do:
when to_char(DATES, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') in ('Sat', 'Sun')
Date | Account | Amount | Count max number of day continuous < 0 |
1 | 1001 | 100 | 0 |
2 | 1001 | -100 | 1 |
3 | 1001 | -100 | 2 |
4 | 1001 | 100 | 2 |
5 | 1001 | -100 | 2 |
6 | 1001 | -100 | 2 |
7 | 1001 | -100 | 3 |
8 | 1001 | -100 | 4 |
9 | 1001 | 100 | 4 |
I have sample data. I want have column "Count max number of day continuous < 0". How i can select it in database oracle
In order to find continuous periods you can use Tabibitosian method. Then use analytical count and finally max:
select date_, account, amount,
max(cnt) over (partition by account order by date_) max_overdraft_period
from (
select date_, account, amount,
count(case when amount <= 0 then 1 end)
over (partition by account, grp order by date_) cnt
from (
select date_, account, amount,
date_ - sum(case when amount <= 0 then 1 else 0 end)
over (partition by account order by date_) grp
from t ))
demo
I assumed that dates are continuous, if not then use row numbering at first.
In oracle, I have a piece of code that returns our work order pass rate. It essentially takes the number of failures (MRB) and divides by the number of work orders processed (WO) to give the rate. The problem I have is that unless there is a failure (MRB) I will not get a return for the corresponding month. If there is no MRB failure, I would like a return of 100% for that month. Here is the code I am working with. For the record, I am not a programmer, I've just picked up a bit of info along the way. I have tried adding NVL but that does not seem to help.
Select To_Char(MTH, 'Month') As "Month",WO, MRB,
Round(1 - (MRB / WO), 3) * 100 As "Pass Rate",
'98' As Goal
From
(
Select Trunc(g.START_DATE, 'Month') As mth,
Count(Distinct V_PDAYPROD_CRW1.PDAYPROD_ID) As WO,
Count(Distinct V_WF_HEADER_MRB.ID) As MRB
From GLPERIODS g
Left Join V_PDAYPROD_CRW1 On Trunc(g.START_DATE, 'Month') = Trunc(V_PDAYPROD_CRW1.PROD_DATE, 'Month')
Left Join V_WF_HEADER_MRB On Trunc(g.START_DATE, 'Month') = Trunc(V_WF_HEADER_MRB.OPEN_DATE, 'Month')
Inner Join ARINVT On V_PDAYPROD_CRW1.ARINVT_ID = ARINVT.ID
Where Extract(Year From g.START_DATE) = Extract(Year From SysDate)
And V_WF_HEADER_MRB.WF_TYPE_ID = '99'
And V_WF_HEADER_MRB.EPLANT_ID = 2
And ARINVT.EPLANT_ID = 2
Group By Trunc(g.START_DATE, 'Month'),
V_WF_HEADER_MRB.WF_TYPE_ID
)
Group By To_Char(MTH, 'Month'),WO,MRB,
Round(1 - (MRB / WO), 3) * 100,
'98',MTH
Order By MTH
The above code returns the following:
MONTH | WO | MRB | Pass Rate | GOAL
September | 60 | 1 | 98.3 | 98
December | 30 | 2 | 93.3 | 98
I would like it to return something like this:
MONTH | WO | MRB | Pass Rate | GOAL
January | 25 | 0 | 100 | 98
February | 66 | 0 | 100 | 98
March | 35 | 0 | 100 | 98
April | 22 | 0 | 100 | 98
May | 19 | 0 | 100 | 98
June | 47 | 0 | 100 | 98
July | 52 | 0 | 100 | 98
August | 55 | 0 | 100 | 98
September | 60 | 1 | 98.3 | 98
October | 39 | 0 | 100 | 98
November | 18 | 0 | 100 | 98
December | 30 | 2 | 93.3 | 98
Thank you for any help you can offer.
If you expect all the records from the main table to be returned in the result set then you should not use the filtering criteria on "v_wf_header_mrb" table in the where clause.
Remove "V_WF_HEADER_MRB.WF_TYPE_ID = '99' And V_WF_HEADER_MRB.EPLANT_ID = 2 " condition from Where clause and try.
You can use the below query instead.
SELECT
TO_CHAR(mth,'Month') AS "Month",
wo,
mrb,
round(1 - (mrb / wo),3) * 100 AS "Pass Rate",
'98' AS goal
FROM
(
SELECT
trunc(g.start_date,'Month') AS mth,
COUNT(DISTINCT v_pdayprod_crw1.pdayprod_id) AS wo,
COUNT(DISTINCT v_wf_header_mrb.id) AS mrb
FROM
glperiods g
LEFT JOIN
v_pdayprod_crw1
ON
trunc(g.start_date,'Month') = trunc(v_pdayprod_crw1.prod_date,'Month')
LEFT JOIN
v_wf_header_mrb
ON
trunc(g.start_date,'Month') = trunc(v_wf_header_mrb.open_date,'Month')
AND
v_wf_header_mrb.wf_type_id = '99'
AND
v_wf_header_mrb.eplant_id = 2
INNER JOIN
arinvt
ON
v_pdayprod_crw1.arinvt_id = arinvt.id
WHERE
EXTRACT(YEAR FROM g.start_date) = EXTRACT(YEAR FROM SYSDATE)
AND
arinvt.eplant_id = 2
GROUP BY
trunc(g.start_date,'Month'),
v_wf_header_mrb.wf_type_id
)
GROUP BY
TO_CHAR(mth,'Month'),
wo,
mrb,
round(1 - (mrb / wo),3) * 100,
'98',
mth
ORDER BY mth;
I am converting a postgres app to an Oracle app.
I came across this query:
WITH cost AS (SELECT
well_schedules.id,
generate_series(well_schedules.start_date::timestamp, well_schedules.end_date, '1 Day') AS "Date",
(well_schedules.drilling_engineering_estimate * well_schedules.well_estimated_working_interest)/((well_schedules.end_date - well_schedules.start_date) + 1) AS "Cost Per Day"
FROM
well_schedules
)
SELECT date_trunc('quarter', "Date"), COUNT("Cost Per Day"), id
FROM cost
GROUP BY id, date_trunc('quarter', "Date")
ORDER BY date_trunc('quarter', "Date")
The part I am struggling with is the generate_series line.
That line takes a start_date and end_date and lists all days between those two dates. We need that information to compile per day/week/month/quarter/year reports (or at least we assume we need that info).
Our data looks like this:
well_schedules
| id | start_date | end_date | cost |
| 1 | '2015-01-01' | '2015-03-20' | 100 |
We assume cost_per_day is equal across all days, so we'd like to generate a report that lets us look at cost_per_day, cost_per_week, cost_per_month, cost_per_year, and cost_per_quarter. cost_per_week/month/quarter/year is calculated by grouping the days by week/month/quarter/year and summing the associated cost_per_days
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE well_schedules ( id, start_date, end_date, cost ) AS
SELECT 1 , DATE '2015-01-01', DATE '2015-01-20', 100 FROM DUAL;
Query 1:
SELECT ID,
COLUMN_VALUE AS Day,
COST / ( end_date - start_date + 1 ) AS Cost_per_day
FROM well_schedules,
TABLE (
CAST(
MULTISET(
SELECT start_date + LEVEL - 1
FROM DUAL
CONNECT BY start_date + LEVEL - 1 <= end_date
)
AS SYS.ODCIDATELIST
)
)
Results:
| ID | DAY | COST_PER_DAY |
|----|---------------------------|--------------|
| 1 | January, 01 2015 00:00:00 | 5 |
| 1 | January, 02 2015 00:00:00 | 5 |
| 1 | January, 03 2015 00:00:00 | 5 |
| 1 | January, 04 2015 00:00:00 | 5 |
| 1 | January, 05 2015 00:00:00 | 5 |
| 1 | January, 06 2015 00:00:00 | 5 |
| 1 | January, 07 2015 00:00:00 | 5 |
| 1 | January, 08 2015 00:00:00 | 5 |
| 1 | January, 09 2015 00:00:00 | 5 |
| 1 | January, 10 2015 00:00:00 | 5 |
| 1 | January, 11 2015 00:00:00 | 5 |
| 1 | January, 12 2015 00:00:00 | 5 |
| 1 | January, 13 2015 00:00:00 | 5 |
| 1 | January, 14 2015 00:00:00 | 5 |
| 1 | January, 15 2015 00:00:00 | 5 |
| 1 | January, 16 2015 00:00:00 | 5 |
| 1 | January, 17 2015 00:00:00 | 5 |
| 1 | January, 18 2015 00:00:00 | 5 |
| 1 | January, 19 2015 00:00:00 | 5 |
| 1 | January, 20 2015 00:00:00 | 5 |
I will suggest the code below that consider the first and last day of the month from two dates:
Example:
Date Initial: 01/10/2014
Date Final: 12/21/2018
The code will return:
01/01/2014
02/01/2014
03/01/2014
04/01/2014
...
12/28/2018
12/29/2018
12/30/2018
12/31/2018
The Code:
SELECT
CAL.DT AS "Date"
,TO_NUMBER(TO_CHAR(CAL.DT,'DD')) AS "Day"
,TO_NUMBER(TO_CHAR(CAL.DT,'MM')) AS "Month"
,TO_NUMBER(TO_CHAR(CAL.DT,'YY')) AS "YearYY"
,TO_NUMBER(TO_CHAR(CAL.DT,'YYYY')) AS "YearYYYY"
,TO_CHAR(CAL.DT,'day') AS "Description_Day"
,TO_CHAR(CAL.DT,'dy') AS "Description_Day_Abrev"
,TO_CHAR(CAL.DT,'Month') AS "Description_Month"
,TO_CHAR(CAL.DT,'Mon') AS "Description_Month_Abrev"
,TO_CHAR(CAL.DT,'dd month yyyy') AS "Date_Text"
FROM (
SELECT
(
TO_DATE(SEQ.MM || SEQ.YYYY, 'MM/YYYY')-1
) + SEQ.NUM AS "DT"
FROM
(
SELECT RESULT NUM,
TO_CHAR(( -- Minimum Date
TO_DATE('01/01/2014', 'DD/MM/YYYY')
) , 'MM') AS "MM",
TO_CHAR(( -- Minimum Date
TO_DATE('01/01/2014', 'DD/MM/YYYY')
) , 'YYYY') AS "YYYY"
FROM
(
SELECT ROWNUM RESULT FROM DUAL CONNECT BY LEVEL <= (
(
-- Maximum Date
LAST_DAY(TO_DATE('31/12/2018', 'DD/MM/YYYY')) -- Always Last Day
-
-- Maximum Date
TRUNC(TO_DATE('01/01/2014', 'DD/MM/YYYY')) -- Always First Day of Month
) + 1 -- Because the First Day (RESULT) don't begin at zero
)
) -- How many sequences (RESULT) to generate
) SEQ
) CAL
;
I'm looking to calculate the highest basket in my set of data but I can't get my head around how I should do it.
I have data like:
OrderID | CustomerID | BasketID | ProductID | Price
1 | 1 | 1 | 221 | 10
2 | 1 | 1 | 431 | 123
3 | 1 | 2 | 761 | 44
4 | 2 | 3 | 12 | 54
5 | 2 | 3 | 102 | 78
6 | 3 | 4 | 111 | 98
7 | 3 | 4 | 41 | 45
8 | 3 | 5 | 65 | 66
9 | 4 | 6 | 32 | 47
10 | 4 | 6 | 118 | 544
Sorry if it seems quite messy.
But I can easily get the SUM with an obvious
SELECT SUM([Price]), BasketID, CustomerID FROM table
GROUP BY BasketID, CustomerID
But how can I filter the list for only the highest priced Basket ID for that CustomerID
Thanks
You can use a CTE (Common Table Expression) with the ROW_NUMBER function:
;WITH HighestPricePerCustomerAndBasket AS
(
SELECT
ID, UserID, ClassID, SchoolID, Created,
ROW_NUMBER() OVER(PARTITION BY BasketID,CustomerID ORDER BY Price DESC) AS 'RowNum'
FROM dbo.YourTable
)
SELECT
[Price], BasketID, CustomerID
FROM HighestPricePerCustomerAndBasket
WHERE RowNum = 1
This CTE "partitions" your data by BasketID,CustomerID, and for each partition, the ROW_NUMBER function hands out sequential numbers, starting at 1 and ordered by Price DESC - so the first row (highest price) gets RowNum = 1 (for each BasketID,CustomerID "partition") which is what I select from the CTE in the SELECT statement after it.
SELECT *
FROM (SELECT *,
DENSE_RANK() OVER (PARTITION BY CustomerID ORDER BY BasketTotal DESC) AS RNK
FROM (SELECT Sum(Price) AS BasketTotal,
BasketID,
CustomerID
FROM Order a
GROUP BY BasketID,
CustomerID
) a
) b
WHERE RNK = 1
I managed to conjure something up that worked.