Search code examples
sqloracleoracle-analytics

Improving Complicated SQL Query with Oracle Analytics


I'm dealing with this particularly complicated (to me at least, if you're a math geek, don't you judge me!) issue.

I've essentially got two things a record of what current stock levels are and a list of all movements of stock in or out of a store. I'm trying to combine the two parts to give me the ability to understand the stock levels of a particular object at any point in the last year.

The first part of SQL combines all stock movements over the last year for all objects in a specific store and the current stock level:

SELECT OBJINCDE, 
       STOREINCDE, 
       TRUNC(STKMVTDTE) AS MOVEMENT_DATE, 
       --This CASE statement tells me if the stock was moved in or out
       CASE WHEN STKMVTINCDE IN (1, 2, 3, 5, 6, 8, 9, 11)  THEN 1 ELSE -1 END AS MOVEMENT

    FROM H_STK

    WHERE TRUNC(STKMVTDTE, 'MM') >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12)  --in the last year
    AND STOREINCDE = 615  --A particular store

UNION ALL

--This statement looks at current levels and combines it with movements as a movement in on the date that the statement was run

SELECT OBJINCDE, 
       STOREINCDE, 
       TRUNC(SYSDATE) AS MOVEMENT_DATE, 
       STKQTY AS MOVEMENT                             

    FROM P_OBJSTORE                                

    WHERE (STKBRKQTY > 0 OR STKMAXQTY > 0)  --This just only picks objects that have a maximum or minimum listed don't judge the stock system either, I can't change that
    AND STOREINCDE = 615

So that returns essentially a list of all stock movements and their date which I then use in this statement:

SELECT TO_CHAR(y.EACH_DAY, 'DD/MM/YYYY') AS EACH_DAY,
       x.OBJINCDE AS OBJINCDE,
       NVL(x.MOVEMENT, 0) AS MOVEMENT,
       SUM(NVL(x.MOVEMENT, 0)) OVER ( ORDER BY y.EACH_DAY DESC) AS STOCK_LEVEL  --Oracle analytics to put together a running total

FROM (SELECT OBJINCDE, MOVEMENT_DATE, SUM(MOVEMENT) AS MOVEMENT             
          FROM W_MIN_MAX_MOVEMENTS  
          WHERE OBJINCDE = 14419  --This is my selection of a particular object
          GROUP BY OBJINCDE, MOVEMENT_DATE
          HAVING SUM(MOVEMENT) <> 0
          ORDER BY MOVEMENT_DATE) x,
     (SELECT TRUNC(SYSDATE) - 365 + LEVEL AS EACH_DAY  --Just brings in each day for the last 365 days
          FROM DUAL
          WHERE ROWNUM <= 365
          CONNECT BY LEVEL = ROWNUM) y

WHERE x.MOVEMENT_DATE (+) = y.EACH_DAY

ORDER BY y.EACH_DAY DESC

So after that I have a few issues that I can't seem to wrap my head around.

First - In the second statement it returns a list of 365 days, the movement of a selected object on that day and it's historical stock level, I can't seem to get the object ID to appear in every row.

Second - I would love to be able to run this so that I got 365 days of movements for every object with it s corresponding stock level for that day. I think this would have to include a better understanding of Oracle Analytics than I currently posses.

Any help would be greatly appreciated.


Solution

  • You need a list of objects, I'll use a distinct on the stock view but you probably have something better, another parent table probably:

    SELECT to_char(cal.each_day, 'dd/mm/yyyy') AS each_day,
           obj.objincde AS objincde,
           nvl(sto.movement, 0) AS movement,
           SUM(nvl(sto.movement, 0)) over(ORDER BY cal.each_day DESC) 
             AS stock_level --oracle analytics to put together a running total
      FROM (SELECT DISTINCT objincde
              FROM w_min_max_movements
             WHERE objincde = 14419 --this is my selection of a particular object
            ) obj
     CROSS JOIN (SELECT trunc(SYSDATE) - 365 + LEVEL 
                        AS each_day --just brings in each day for the last 365 days
                   FROM dual
                  WHERE rownum <= 365
                 CONNECT BY LEVEL = rownum) cal
      LEFT JOIN (SELECT objincde, movement_date, SUM(movement) AS movement
                   FROM w_min_max_movements
                  GROUP BY objincde, movement_date
                 HAVING SUM(movement) <> 0) sto 
             ON sto.movement_date = cal.each_day
            AND sto.objincde = obj.objincde 
     ORDER BY cal.each_day DESC
    

    This will make your object id appear on every lines.

    By the way you should try to work with ANSI joins from now on: the old style joins are harder to read, have extra limitations and are only supported for legacy code. All new code should use the ANSI joins.

    Now if you want to show all dates for all objects, just remove the WHERE clause and add a PARTITION clause to your analytic function:

    SELECT to_char(cal.each_day, 'dd/mm/yyyy') AS each_day,
           obj.objincde AS objincde,
           nvl(sto.movement, 0) AS movement,
           SUM(nvl(sto.movement, 0)) 
             over(PARTITION BY obj.objincde ORDER BY cal.each_day DESC) 
             AS stock_level --oracle analytics to put together a running total
      FROM (SELECT DISTINCT objincde
              FROM w_min_max_movements) obj
     CROSS JOIN (SELECT trunc(SYSDATE) - 365 + LEVEL 
                        AS each_day --just brings in each day for the last 365 days
                   FROM dual
                  WHERE rownum <= 365
                 CONNECT BY LEVEL = rownum) cal
      LEFT JOIN (SELECT objincde, movement_date, SUM(movement) AS movement
                   FROM w_min_max_movements
                  GROUP BY objincde, movement_date
                 HAVING SUM(movement) <> 0) sto 
             ON sto.movement_date = cal.each_day
            AND sto.objincde = obj.objincde 
     ORDER BY cal.each_day DESC