query to match a value in multiple ranges - sql

I have a table as below:
promo_code | minimum_order | discount
------------+-------------------+-------------
100 | 10 | 10%
100 | 20 | 15%
100 | 30 | 20%
101 | 13 | 7%
102 | 8 | 10%
102 | 12 | 14%
In another table I have the sales in quantity and the promotions they are eligible
sales | promo_eligibility | record_id
--------+-----------------------+-------------
14 | 100 | 1000
7 | 101 | 1001
25 | 102 | 1002
I need to get the discount that correspond to the sales volume and promotion...
as in the above examples:
record_id | discount | Comments
------------+---------------+--------------
1000 | 15% | (bigger than 10 and lower than 20)
1001 | 0% | (did not reached the minimum)
1002 | 14% |
The number of thresholds could vary from 0 to 3
Any ideas?
Thanks!!!

Something like this:
select
r.record_id,
isnull(d.discount, '0%')
from records r
outer apply (
select top 1 discount
from discounts d
where d.promo_code = r.promo_eligibility
and d.minimum_order <= r.sales)

Related

Divide a number into multiple bands

I've been dealing with a problem of splitting a metric into several bands. To give you some context, let's take this example where we have certain number of orders per customer. Now, a customer may order n number of products. Let's give the customer certain discount based on the number of orders. The discounts are offered based a tiered model. I'm leaving out multiple product categories to keep it simple. Here are some examples of the tables.
Orders table
Customer | order_no
----------------------------
Customer1 | 400
Customer2 | 1200
Customer3 | 40
Customer4 | 2000
Customer5 | 700
Tiered pricing table
Tier | lower_th | higer_th | price |
--------------------------------------
Tier1 | 0 | 250 | 50 |
TIer2 | 251 | 500 | 45 |
Tier3 | 501 | 1000 | 40 |
TIer4 | 1001 | 10000 | 30 |
Example1: I want to be able to charge Customer1 $50 for 250 order and $45 for the rest of 150 products out of a total of 400.
Example2: I want to be able to charge Customer5 $50 for 250 order and $45 for another 250 and $40 for the rest 200 products out of a total of 700.
How do I achieve this in PostgreSQL? My output needs to be the following for Customer1. What's the best way to split the total number of orders and join it to the pricing tiers to get the corresponding amount?
Customer | order_no | charges |
--------------------------------
Customer1 | 250 | 50 |
Customer1 | 150 | 45 |
You can think of your tiers as intervals.
Two intervals [a1, b1] and [a2, b2] intersect when
a1 <= b2 AND b1 >= a2
The number of orders is another interval that always starts at 1.
Your two intervals are: Tiers [lower_th, higer_th] and Orders [1, order_no].
The query is a simple join using this intersection expression:
SELECT *
,CASE WHEN O.order_no > T.higer_th
THEN T.higer_th - T.lower_th + 1 -- full tier
ELSE O.order_no - T.lower_th + 1
END AS SplitOrderNumbers
FROM
Orders AS O
INNER JOIN Tiers AS T
-- ON 1 <= T.higer_th AND O.order_no >= T.lower_th
ON O.order_no >= T.lower_th
ORDER BY
O.Customer
,T.lower_th
;
You don't really need the 1 <= T.higer_th part, because it is always true, so the expression becomes simple O.order_no >= T.lower_th.
Also, usually it is better to store intervals as [closed; open). It usually simplifies arithmetic, similar to why most programming languages have array indexes starting at 0, not 1. Your intervals seem to be [closed; closed]. In this case you need to set lower_th to 1, not 0 and have +1 in the calculations.
With this adjustment of the sample data this query produces the following result:
+-----------+----------+-------+----------+----------+-------+-------------------+
| Customer | order_no | Tier | lower_th | higer_th | price | SplitOrderNumbers |
+-----------+----------+-------+----------+----------+-------+-------------------+
| Customer1 | 400 | Tier1 | 1 | 250 | 50.00 | 250 |
| Customer1 | 400 | Tier2 | 251 | 500 | 45.00 | 150 |
| Customer2 | 1200 | Tier1 | 1 | 250 | 50.00 | 250 |
| Customer2 | 1200 | Tier2 | 251 | 500 | 45.00 | 250 |
| Customer2 | 1200 | Tier3 | 501 | 1000 | 40.00 | 500 |
| Customer2 | 1200 | Tier4 | 1001 | 10000 | 30.00 | 200 |
| Customer3 | 40 | Tier1 | 1 | 250 | 50.00 | 40 |
| Customer4 | 2000 | Tier1 | 1 | 250 | 50.00 | 250 |
| Customer4 | 2000 | Tier2 | 251 | 500 | 45.00 | 250 |
| Customer4 | 2000 | Tier3 | 501 | 1000 | 40.00 | 500 |
| Customer4 | 2000 | Tier4 | 1001 | 10000 | 30.00 | 1000 |
| Customer5 | 700 | Tier1 | 1 | 250 | 50.00 | 250 |
| Customer5 | 700 | Tier2 | 251 | 500 | 45.00 | 250 |
| Customer5 | 700 | Tier3 | 501 | 1000 | 40.00 | 200 |
+-----------+----------+-------+----------+----------+-------+-------------------+
For pricing data I would use a table like this to make data maintenance easier
create table pricing_data
(
high_limit int,
price numeric
);
A view will give you the intervals you need for this using a window function:
create view pricing as
select coalesce(lag(high_limit) over (order by high_limit), 0) as last_limit,
high_limit, price
from pricing_data;
This simplifies the breakout into pricing tiers:
select o.customer,
least(o.order_no - p.last_limit, p.high_limit - p.last_limit) as order_no,
p.price as charges
from orders o
join pricing p on p.last_limit < o.order_no
order by o.customer, p.price desc
;
Result:
customer order_no charges
Customer1 250 50
Customer1 150 45
Customer2 250 50
Customer2 250 45
Customer2 500 40
Customer2 200 30
Customer3 40 50
Customer4 250 50
Customer4 250 45
Customer4 500 40
Customer4 1000 30
Customer5 250 50
Customer5 250 45
Customer5 200 40
14 rows
Fiddle here.

DB2 SQL, Calculate total on orders ready to ship

I'm trying to calculate the total value or all orders where we have all items in stock required to fill the order. In the example below, I want to select only the total value of order 100 only, since there is not enough inventory to fill order 200.
+-------+------+-------------+--------------+-------+
| Order | Item | Qty Ordered | Qty In Stock | Price |
+-------+------+-------------+--------------+-------+
| 100 | A | 10 | 25 | 1.00 |
+-------+------+-------------+--------------+-------+
| 100 | B | 15 | 50 | 2.00 |
+-------+------+-------------+--------------+-------+
| 100 | C | 30 | 75 | 3.00 |
+-------+------+-------------+--------------+-------+
| 200 | A | 5 | 25 | 1.00 |
+-------+------+-------------+--------------+-------+
| 200 | B | 100 | 50 | 2.00 | * Not enough stock to fill
+-------+------+-------------+--------------+-------+
| 200 | C | 35 | 75 | 3.00 |
+-------+------+-------------+--------------+-------+
How about:
select o.id, sum(o.qty_ordered * o.price) as total_value
from orders o
where o.id not in (
select id from orders where qty_ordered > qty_in_stock
)
group by o.id

SELECTing monthly order amounts and item subtotals in a single query with two tables

I have an Orders table in the form:
| id | service_fee_cents | grand_total_cents | created_at |
|----|-------------------|-------------------|---------------|
| 1 | 1400 | 10000 | Jan 21 2018 |
| 2 | 1000 | 10000 | Feb 16 2018 |
| 3 | 500 | 10000 | March 21 2018 |
| 4 | 500 | 10000 | March 20 2018 |
And an Items table in the form
| id | order_id | title | price_cents | quantity |
|----|----------|--------|-------------|----------|
| 1 | 1 | lorem | 2000 | 2 |
| 2 | 1 | ipsum | 2030 | 1 |
| 3 | 2 | pie | 4000 | 4 |
| 4 | 3 | cheese | 6000 | 2 |
| 5 | 3 | burger | 7000 | 1 |
| 6 | 4 | custar | 1000 | 1 |
And I'm trying to run a SQL query to get a result in the form
| month | total_service_fee | total_grand_total | total_subtotal |
|-----------|-------------------|-------------------|----------------|
|2017-11-01 | 42 | 1,610 | 610 |
|2017-12-01 | 30 | 19,912 | 1,912 |
|2018-01-01 | 179 | 1,413 | 413 |
|2018-02-01 | 165 | 2,910 | 910 |
|2018-03-01 | 1,403 | 10,727 | 1,727 |
I've managed to get the first three columns using this query:
SELECT
date_trunc('month', created_at)::date AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total
FROM orders
GROUP BY month ORDER BY month
How do I get the last one? In the app, I get the sum via the following Ruby code:
order_subtotal = order.items.map{|item| item.price * item.quantity}.reduce(:+)
Which basically takes all the order's items, multiplies price by quantity and adds the results.
This should be a good start:
SELECT Date_trunc('month', created_at) :: DATE AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total,
SUM(total_subtotal) / 100 AS total_subtotals
FROM orders o
join (SELECT order_id,
SUM(price_cents * quantity) total_subtotal
FROM items i
GROUP BY order_id) i
ON o.id = i.order_id
GROUP BY month
ORDER BY month
You can get there by just joining the Orders table to the Items table and generating a SUM of subtotals by month. This may however be a somewhat expensive query to run if there are thousands of items in each order like you said.
SELECT
date_trunc('month', created_at)::date AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total,
SUM(price_cents * quantity) / 100 AS sub_total
FROM Orders o
JOIN Items i ON i.order_id = o.id
GROUP BY month ORDER BY month
http://sqlfiddle.com/#!15/555a2/1

calculating total sale with different prices

So I have a table which I am using to store data for my documents. Now I want to calculate my sale over some period and let's say I have table like this now:
| productID | price | quantity |
| 30 | 15 | 5 |
| 30 | 15 | 3 |
| 20 | 13 | 2 |
| 20 | 13 | 3 |
| 30 | 12 | 4 |
| 30 | 16 | 1 |
| 15 | 1 | 2 |
| 10 | 3 | 5 |
So I need a SQL command that will multiply the price and quantity for every row separate (because as you can see, for some products I have different prices e.g. where product id is 30) and then sum all those values.
This will give you total sales for each product
SELECT productID,
Sum(Price * quantity) AS Total_sales
FROM yourtable
GROUP BY productID

how to adjust the sum() of a group using the sum() of a subgroup()

I am trying to get the Output shown in the third table below using the tables "Assets" and "Transactions".
I am trying to group by Cmpy, Acct and AssetID and get the Sum(cost). But each cost has to be adjusted from the Transactions table before being summed. Not sure how to do it.
Table: Assets
+----------+------+---------+--------+
| Cpny | Acct | AssetID | Cost |
+----------+------+---------+--------+
| 50 | 120 | 109 | 100.00 |
| 50 | 120 | 109 | 200.00 |
| 50 | 120 | 110 | 300.00 |
| 50 | 120 | 110 | 20.00 |
| 50 | 121 | 107 | 150.00 |
| 50 | 121 | 201 | 200.00 |
+----------+------+---------+--------+
Table: Transactions
+------+---------+--------+
| Cpny | AssetID | Amt |
+------+---------+--------+
| 50 | 109 | -50.00 |
| 50 | 110 | 50.00 |
| 50 | 110 | -20.00 |
| 50 | 201 | -50.00 |
+------+---------+--------+
OUTPUT
+------+------+--------+
| Cpny | Acct | Total |
+------+------+--------+
| 50 | 120 | 600.00 |
| 50 | 121 | 300.00 |
+------+------+--------+
This one should give you an accurate answer:
SELECT a.Cpny,
a.Acct,
SUM(a.Cost + ISNULL(t.Adjustment, 0)) AS Total
FROM Assets a
LEFT JOIN (SELECT Cpny,
AssetID,
SUM(Amt) AS Adjustment
FROM Transactions
GROUP BY Cpny, AssetID) t
ON t.Cpny = a.Cpny AND t.AssetID = a.AssetID
GROUP BY a.Cpny, a.Acct
Associated SQLFiddle here.
Essentially, SUM the adjustment amounts in the transactions table, then join this to the main results list, summing the cost plus the adjustment for each asset in each account.
If the "relationship" between Acct and AssetID values are 1 to many then you could use this query (which is not so efficient):
SELECT x.Cpny,x.Acct, SUM( ISNULL(x.Total,0) + ISNULL(y.Total,0) ) AS Total
FROM
(
SELECT a.Cpny,a.Acct,a.AssetID, SUM(a.Cost) AS Total
FROM dbo.Assets a
GROUP BY a.Cpny,a.Acct,a.AssetID
) x
LEFT JOIN
(
SELECT t.Cpny,t.AssetID, SUM(t.Cost) AS Total
FROM dbo.Transactions t
GROUP BY t.Cpny,t.AssetID
) y ON x.Cpny=y.Cpny AND x.AssetID=y.AssetID
GROUP BY x.Cpny,x.Acct;