SUM Based Off A Criteria - sql

How can I SUM "Orders" when there is a value for "Spend" and also the NULL rows that comes after it going from oldest to newest?
Current Output:
Date | Product | Spend | Orders
------------------------------------------------
2017-09-18 Product A NULL 7
2017-09-11 Product A NULL 7
2017-09-04 Product A 1000.00 16
2017-08-28 Product A NULL 7
2017-08-21 Product A 2000.00 35
2017-08-14 Product A 1000.00 20
2017-08-07 Product A NULL 3
2017-07-31 Product A NULL 3
2017-07-24 Product A 1000.00 14
Desired Output:
Date | Product | Spend | Orders | SUMMED Orders
---------------------------------------------------------------
2017-09-18 Product A NULL 7 NULL
2017-09-11 Product A NULL 7 NULL
2017-09-04 Product A 1000.00 16 30 (16 + 7 + 7)
2017-08-28 Product A NULL 7 NULL
2017-08-21 Product A 2000.00 35 42 (35 + 7)
2017-08-14 Product A 1000.00 20 20 (20)
2017-08-07 Product A NULL 3 NULL
2017-07-31 Product A NULL 3 NULL
2017-07-24 Product A 1000.00 14 20 (14 + 3 + 3)
I wrote the math expression in the SUMMED Orders column to show how I came up with the new total.
Thank you.

You can assign a group to the rows by counting the number of non-null rows that are older. You can then use this group to calculate the sum:
select t.*,
sum(orders) over (partition by product, grp) as summed_orders
from (select t.*,
sum( (spend is not null)::int ) over (partition by product order by date asc) as grp
from t
) t;
This doesn't remove the first row. I'm not sure what the logic is for removing that.

select "date", product, spend, orders, sum(orders) over (order by "date") rt
from t1
order by "date" desc , spend nulls first;

Related

How to get the last day of the month without LAST_DAY() or EOMONTH()?

I have a table t with:
DATE
LOCATION
PRODUCT_ID
AMOUNT
2021-10-29
1
123
10
2021-10-30
1
123
9
2021-10-31
1
123
8
2021-10-29
1
456
100
2021-10-30
1
456
90
2021-10-31
1
456
80
2021-10-29
2
123
18
2021-10-30
2
123
17
2021-11-29
2
456
18
I need to find the AMOUNT of each PRODUCT_ID for each combination of LOCATION + PRODUCT_ID.
If a PRODUCT_ID has no entry for that day the AMOUNT is NULL.
So the result should look like:
DATE
LOCATION
PRODUCT_ID
AMOUNT
2021-10-31
1
123
8
2021-10-31
1
456
80
2021-10-31
2
123
NULL
2021-11-30
2
456
NULL
Sadly EXASOL has no LAST_DAY() or EOMONTH() function. How can I solve this?
You can get to the last day of the month using a date_trunc function in combination with date_add:
case
when t.date = date_add('day', -1, date_add('month', 1, date_trunc('month', t.date)))
then 'Y' else 'N' end as end_of_month
That being said, if you group your table for all combinations of locations and products, you will not get NULLs for products without sales on the last day of the month as shown in your output table.
When you group your data, any value that does not exist will simply not show up in your output table. If you want to force nulls to show up, you can create a new table that contains all combinations of products, locations, and hard-coded end of month dates.
Then, you can left join your old table with this new hard-coded table by date, location, and product. This method will give you the NULL values you expect.

How to get current and previous month's data in a table?

I have a table in oracle db that contains product_id (unique) , month and price data.
MONTH
PRODUCT_ID
CURRENT_PRICE
2
00011
14
2
00022
60
3
00011
10
3
00022
40
I want to write a SQL code in oracle to build up the view shown below;
MONTH
PRODUCT_ID
CURRENT_PRICE
PREVIOUS_PRICE
CHANGE_RATE
2
00011
14
NULL
NULL
2
00022
60
NULL
NULL
3
00011
10
14
40
3
00022
40
60
50
where current and previous price datas for each product is listed in a one row. How can i write it down? Thanks in advance.
Use lag():
select t.*,
lag(current_price) over (partition by product_id order by month) as prev_price,
(-100 + lag(current_price) over (partition by product_id order by month) * 100.0 / current_price) as change_rate
from t
order by month, product_id;
Here is a db<>fiddle.

Subtracting rows depending on values of another column

I have two tables purchase, I want to subtract purchase date. depending on Customer ID, there are repeating customer ID's, so I want to subtract purchase date of Customer ID 105 and 105, 108 and 108 etc.
I have the following code, but it is subtracting each purchase date from the next purchase date
SELECT DATEDIFF(DAY,P1.PURCHASEDATE,P2.PURCHASEDATE) AS "diff in days since last purchase"
FROM Purchases P1
JOIN Purchases P2
ON P1.CustomerID= P2.CustomerID
Try adding to your ON a not equal: P1.PURCHASEID <> P2.PURCHASEID , meaning something like this:
SELECT DATEDIFF(DAY,P1.PURCHASEDATE,P2.PURCHASEDATE) AS "diff in days"
FROM Purchases P1
JOIN Purchases P2
(ON P1.CustomerID= P2.CustomerID and P1.PURCHASEID <> P2.PURCHASEID )
You can use OUTER APPLY:
;WITH Purchases AS (
SELECT *
FROM (VALUES
(1,'2012-08-15',1,105,'a510'),
(2,'2012-08-15',2,102,'a510'),
(3,'2012-08-15',3,103,'a506'),
(4,'2012-08-16',1,105,'a510'),
(5,'2012-08-17',5,106,'a507'),
(6,'2012-08-17',5,107,'a509'),
(7,'2012-08-18',4,108,'a502'),
(8,'2012-08-19',2,108,'a510'),
(9,'2012-08-19',3,109,'a502'),
(10,'2012-08-20',3,110,'a503')
) as t(PurchaseID,PurchaseDate,Qty,CustomerID,ProductID)
)
SELECT p1.*,
DATEDIFF(DAY,P2.PurchaseDate,P1.PurchaseDate) as ddiff
FROM Purchases p1
OUTER APPLY (
SELECT TOP 1 *
FROM Purchases
WHERE p1.CustomerID = CustomerID
AND PurchaseDate < p1.PurchaseDate
ORDER BY PurchaseDate DESC
) p2
Will output:
PurchaseID PurchaseDate Qty CustomerID ProductID ddiff
1 2012-08-15 1 105 a510 NULL
2 2012-08-15 2 102 a510 NULL
3 2012-08-15 3 103 a506 NULL
4 2012-08-16 1 105 a510 1
5 2012-08-17 5 106 a507 NULL
6 2012-08-17 5 107 a509 NULL
7 2012-08-18 4 108 a502 NULL
8 2012-08-19 2 108 a510 1
9 2012-08-19 3 109 a502 NULL
10 2012-08-20 3 110 a503 NULL
Also you can use LAG (SQL Server 2012 and up):
SELECT *,
DATEDIFF(DAY,LAG(PurchaseDate,1,NULL) OVER (PARTITION BY CustomerID ORDER BY PurchaseDate),PurchaseDate) as ddiff
FROM Purchases

How to join tables based on the dates

COMMISSION table
PRODUCT_ID DATE COMMISSION
1 20110101 27.00
1 20120101 28.00
1 20130705 30.00
2 20110101 17.00
2 20120501 16.00
2 20130101 18.00
...
ORDER table
PRODUCT_ID DATE PRICE
1 20110405 2500
2 20130402 3000
2 20130101 1900
Desired output
PRODUCT_ID DATE PRICE COMMISSION
1 20110405 2500 27.00
2 20130402 3000 16.00
2 20130101 1900 18.00
Commission table records commission % based on the product id and date.
Order table is basically a record of orders placed on a particular date,
I'd like to join two tables and bring the appropriate commission based on the date of the order. For example, you can see that the first order's commission is 27.00 as the date for the product_id 1 falls between 20110101 and 20120101.
How do I do this? Seems like a simple 1 to n relationship but I can't figure it out.
Try
SELECT o.*,
(
SELECT TOP 1 commission
FROM commission
WHERE product_id = o.product_id
AND date <= o.date
ORDER BY date DESC
) commission
FROM [order] o
Here is SQLFiddle demo

Filtering within an window function (over ... partition by)?

I am trying to use a sum() over (partition by) but filter within that summing. My use case is summing trailing twelve months up to a single month's entry for each product, so:
ITEM MONTH SALES
Item A 1/1/2011 2
Item A 2/1/2011 5
Item A 3/1/2011 3
Item A 4/1/2011 7
Item A 5/1/2011 12
Item A 6/1/2011 8
Item A 7/1/2011 9
Item A 8/1/2011 15
Item A 9/1/2011 6
Item A 10/1/2011 7
Item A 11/1/2011 12
Item A 12/1/2011 1
Item A 1/1/2012 3
Item A 2/1/2012 4
Item A 3/1/2012 5
Item A 4/1/2012 6
Item A 5/1/2012 4
Item A 6/1/2012 8
Item A 7/1/2012 9
Item A 8/1/2012 12
Item A 9/1/2012 14
Item A 10/1/2012 8
Item A 11/1/2012 12
Item A 12/1/2012 16
Would then return:
ITEM MONTH_BEGIN SALES TTM SALES
Item A 1/1/2012 3 87
Item A 2/1/2012 4 88
Item A 3/1/2012 5 87
Item A 4/1/2012 6 89
Where the TTM SALES for 1/1/12 is the sum of 1/1/11-12/1/11
The bellow query shows how I would do it with Oracle Analytic Functions:
SELECT
"ITEM",
TO_CHAR("MONTH", 'MM/DD/YYYY') AS "MONTH_BEGIN",
"SALES",
SUM("SALES") OVER (
PARTITION BY
"ITEM"
ORDER BY
"MONTH"
RANGE BETWEEN
INTERVAL '12' MONTH PRECEDING
AND
INTERVAL '1' MONTH PRECEDING
) AS "TTM_SALES"
FROM
"SALES"
ORDER BY
"MONTH";
Working SQLFiddle demo
This will compute the sum function over a window that starts 12 months before the month of the current row and ends 1 month before it.
I assumed that you do not need to filter anything in the where clause. If you do, be careful with it. Quoting the Oracle documentation:
Analytic functions are the last set of operations performed in a query
except for the final ORDER BY clause. All joins and all WHERE, GROUP
BY, and HAVING clauses are completed before the analytic functions are
processed.
So lets say that you want to display results only for the first quarter of 2012; if you try to do so by filtering in the where clause, it will affect the cumulative results of TTM_SALES as well (outputing null, 3, 7 and 12).
The bottom line here is: If you need to filter out rows within the window of the analytic function, move the analytic function to a subquery and filter in the outer query as per #peterm answer:
SELECT
"X"."ITEM",
TO_CHAR("X"."MONTH", 'MM/DD/YYYY') AS "MONTH_BEGIN",
"X"."SALES",
"X"."TTM_SALES"
FROM
(
SELECT
"ITEM",
"MONTH",
"SALES",
SUM("SALES") OVER (
PARTITION BY
"ITEM"
ORDER BY
"MONTH"
RANGE BETWEEN
INTERVAL '12' MONTH PRECEDING
AND
INTERVAL '1' MONTH PRECEDING
) AS "TTM_SALES"
FROM
"SALES"
ORDER BY
"MONTH"
) "X"
WHERE
EXTRACT(MONTH FROM "X"."MONTH") BETWEEN 1 AND 4
AND EXTRACT(YEAR FROM "X"."MONTH") = 2012;
If you're open to anything other than an analytic SUM() then here is a possible solution with a simple correlated subquery
SELECT s.item, s.month month_begin, s.sales,
(SELECT SUM(sales) FROM sales
WHERE month BETWEEN DATEADD(month, -12, s.month)
AND DATEADD(month, -1, s.month)) ttm_sales
FROM sales s
WHERE s.month BETWEEN '20120101' AND '20121201'
Sample output:
| ITEM | MONTH_BEGIN | SALES | TTM_SALES |
-----------------------------------------------------------------
| Item A | January, 01 2012 00:00:00+0000 | 3 | 87 |
| Item A | February, 01 2012 00:00:00+0000 | 4 | 88 |
| Item A | March, 01 2012 00:00:00+0000 | 5 | 87 |
| Item A | April, 01 2012 00:00:00+0000 | 6 | 89 |
...
Here is SQLFiddle demo