Creating view from item table and price table - sql

Let's say I have two tables. One is the orders table and the other is a price table giving the price of an item w.r.t. the number of items ordered.
The meaning of the test_price table is "up-to count items(inclusive) the cost per item is price". So 0-50 items cost 1.22 per item. 51-100 cost 1.20 per item.
table test_price
id | count | price
----+-------+-------
1 | 50 | 1.22
2 | 100 | 1.20
3 | 150 | 1.19
4 | 200 | 1.18
5 | 300 | 1.10
table test_orders
id | count
----+-------
1 | 12
2 | 50
3 | 65
4 | 155
5 | 400
So this means that order 1 for 12 items should be priced at 1.22 per item.
Order 5 should be priced at 1.10 per item.
I can get the price for a single order with
SELECT price FROM test_prices WHERE
count >= (SELECT count FROM test_orders WHERE id = 1)
ORDER BY count ASC LIMIT 1;
I would like to create a view that shows the orders with unit price and total price as columns

Join the tables on the count columns applying your conditions:
with cte as (select max(count) maxcount from test_price)
select
o.*, o.count * p.price total_price
from test_orders o inner join test_price p
on p.count = coalesce(
(select min(count) from test_price where o.count <= count),
(select maxcount from cte)
)
order by o.id;
See the demo.
Results:
| id | count | total_price |
| --- | ----- | ----------- |
| 1 | 12 | 14.64 |
| 2 | 50 | 61 |
| 3 | 65 | 78 |
| 4 | 155 | 182.9 |
| 5 | 400 | 440 |

I think your test_prices table is not quite right. I think it should be:
id | count | price
----+-------+-------
1 | 1 | 1.22
2 | 100 | 1.20
3 | 150 | 1.19
4 | 200 | 1.18
5 | 300 | 1.10
This says that for 1-99 quantity, the price is $1.22. For 100-149, the price $1.20, and so on.
With this structure, you can use a lateral join:
select o.*, p.price
from test_orders o left join lateral
(select p.*
from test_prices p
where p.count <= o.count
order by p.count desc
fetch first 1 row only
) p
on 1=1

The problem with your data is that price_table data is not all "up-to count", because the 5th row also means "up-to and beyond".
If you are able to add another row to price_table like (6, 10000, 1.10) then the solution is easy:
CREATE VIEW price_view AS
SELECT orders.id, orders.count, p.price, orders.count * p.price as total
FROM (
SELECT o.id, o.count, min(p.count) as pricebracket
FROM test_price p
LEFT JOIN test_orders o ON p.count >= o.count
GROUP BY o.id, o.count) orders
LEFT JOIN test_price p ON orders.pricebracket = p.count
ORDER BY orders.id
If you cannot get rid of this inconsistency in your data then you have to select maximum value first. So the query modifies like this:
CREATE VIEW price_view AS
WITH temptable as (SELECT max(p.count) as maxcount FROM test_price p)
SELECT orders.id, orders.count, p.price, orders.count * p.price as total
FROM (
SELECT o.id, o.count, min(p.count) as pricebracket
FROM test_price p
CROSS JOIN temptable
LEFT JOIN test_orders o ON p.count >=
(case when o.count > temptable.maxcount then temptable.maxcount else o.count end)
GROUP BY o.id, o.count) orders
LEFT JOIN test_price p ON orders.pricebracket = p.count
ORDER BY orders.id

Related

SQL: Trying to add up a total row which was calculated from 2 other columns

I'm attempting, and partly done, to find out how much stock is left in a business. I have a stock table and product price table that I'm pulling values from. The TOTAL column is quantityinstock * price which is what I need but I'd like to the get an overall total of the TOTAL.
How would I go about this? I've been searching for quite a while now to no avail!
I would like to, if possible get a TOTAL of TOTAL row adding up all of the TOTAL i.e. 3551.13 + 2941.91 + 2713.19 = VALUE.
SELECT
A.productID,
quantityInStock,
productPrice,
quantityInStock * productPrice AS TOTAL
FROM gs_stock A
JOIN gs_productprice B ON A.productID = B.productID
WHERE endDate IS NULL
GROUP BY A.productID
ORDER BY TOTAL DESC
Sample Data:
+-----------+-----------------+--------------+---------+
| productID | quantityInStock | productPrice | TOTAL |
+-----------+-----------------+--------------+---------+
| 71 | 187 | 18.99 | 3551.13 |
| 73 | 109 | 26.99 | 2941.91 |
| 74 | 181 | 14.99 | 2713.19 |
+-----------+-----------------+--------------+---------+
SELECT
A.productID,
A.quantityInStock,
B.productPrice,
A.quantityInStock * B.productPrice AS TOTAL
FROM gs_stock A
JOIN gs_productprice B on A.productID = B.productID
WHERE endDate IS NULL
GROUP BY A.productID
ORDER BY TOTAL DESC
What I understood was that you need a total value of your stock that will be as follows:
SELECT
sum(TOTAL) as TOTAL_SUM
FROM (
SELECT
A.productID,
quantityInStock,
productPrice,
quantityInStock * productPrice AS TOTAL
FROM gs_stock A
JOIN gs_productprice B ON A.productID = B.productID
WHERE endDate IS NULL
GROUP BY A.productID
) TOTAL
Added in TOTAL following the end bracket which then gave me the required calculation I was looking for!!! Cheers again, really appreciate your help!

How can I get the row with a min value from a group and the associated values from another tables?

So I have three tables (Products, Stock and ProductsBatches ) and I'm trying to get the batch that expires first (from ProductsBatches) for each product and some additional info from Products (like product name) and Stock (like total quantity)
I managed to get just the earliest date for each product but when I add some other columns and joins it messes it up and it gets me all sorts of dates.
PRODUCTS TABLE:
| PRODUCT ID (PK)| NAME | PRICE |
| 1 | CHEESE | 12.0 |
STOCK TABLE :
| PRODUCT ID (PFK)| TOTAL QUANTITY |
| 1 | 100 |
PRODUCTS BATCHES TABLE:
| PRODUCT ID (PFK) | BATCH (PFK) | BATCH QUANTITY | BATCH EXPIRY |
| 1 | XYZ | 50 | 2019-01-01 |
| 1 | XZZ | 50 | 2020-01-01 |
So far I've got:
| PRODUCT ID | BATCH EXPIRY |
| | |
| 1 | 2019-01-01 |
With SELECT productID,min(batchExpiry) from PRODUCTSBATCHES group by (productID)
What I need:
|PRODUCT ID| NAME |TOTAL QUANTITY|PRICE|BATCH|BATCH QUANTITY|BATCH EXPIRY|
| 1 |CHEESE| 100 |12.0 | XYZ | 50 | 2019-01-01 |
I tried this but is also not good :
SELECT DISTINCT ON (b.productID) b.productID, p.name, s.totalquantity,
p.price, b.batch, b.batchquantity, b.batchExpiry
FROM productsbatches as b
INNER JOIN (
SELECT productID,min(batchExpiry) from PRODUCTSBATCHES group by (productID)
) as exmin b.productID = exmin.productID
INNER JOIN stock as s ON b.productID = s.productID
INNER JOIN products as p ON s.productID = p.productID
Thanks.
You are close, but you forgot to join the batchExpiry column with the min batchExpiry derived by the subquery:
SELECT
p.productID, p.name, s.totalquantity,
p.price, b.batch, b.batchquantity, b.batchExpiry
FROM products as p
INNER JOIN stock as s ON p.productID = s.productID
INNER JOIN productsbatches as b ON p.productID = b.productID
INNER JOIN (
SELECT productID, min(batchExpiry) as batchExpiry
FROM PRODUCTSBATCHES
GROUP BY productID
) as exmin ON b.productID = exmin.productID AND b.batchExpiry = exmin.batchExpiry
DISTINCT ON is the right way to go. You just have to use it correctly:
SELECT DISTINCT ON (p.productID) p.productID, p.name, s.totalquantity,
p.price, b.batch, pb.batchquantity, pb.batchExpiry
FROM products p INNER JOIN
stock s
ON p.productID = s.productID INNER JOIN
productsbatches pb
ON pb.productID = p.productID
ORDER BY p.productID, pb.batchExpiry;
Basically, all you need is the ORDER BY. The subquery is not necessary.

Oracle - Count condition case

I am facing a problem with a query. My goal is to get all product names, unit of mass, quantity and number of pallets they are located in. The problem is with number of pallets of item.
QUERY:
SELECT "Product_Name"
, "Unit_of_Mass"
, SUM("Quantity_Per_UOM")
, Count(*) as "Number_Of_Pallet"
FROM
(select p.prod_desc as "Product_Name"
, s.quantity as "Quantity_Per_UOM"
, u.description as "Unit_of_Mass"
, s.container_id
, s.product_id
from wms_stock s
join wms_product p on p.product_id = s.product_id
join wms_uom u on p.uom_base_id = u.uom_id
)
group by "Product_Name", "Unit_of_Mass"
It almost works. The problem is I need to do some condition in Count(*) (that's what I think should be done). In table wms_stock I got product_id and container_id, and when in some row they are same it should count the number of pallets as 1 but still add the quantities.
So from first select:
Product_Name | Quantity | UnitOfMass | ContainerId | ProductId
A | 2 | kg | 10 | 11
A | 1 | kg | 10 | 11
B | 2 | kg | 11 | 12
I should get result
Product_Name | Quantity_Per_UOM | UnitOfMass | Number_Of_Pallet
A | 3 | kg | 1
B | 2 | kg | 1
You can try below condition in your select list -
COUNT(DISTINCT ContainerId || ProductId)
Just for your information, || is not an operator rather it is concatenation operator in Oracle. So i have just concat both the columns and picked up the distinct from them.

PostgreSQL Referencing Outer Query in Subquery

I have two Postgres tables (really, more than that, but simplified for the purpose of the question) - one a record of products that have been ordered by customers, and another a historical record of prices per customer and a date they went into effect. Something like this:
'orders' table
customer_id | timestamp | quantity
------------+---------------------+---------
1 | 2015-09-29 16:01:01 | 5
1 | 2015-10-23 14:33:36 | 3
2 | 2015-10-19 09:43:02 | 7
1 | 2015-11-16 15:08:32 | 2
'prices' table
customer_id | effective_time | price
------------+---------------------+-------
1 | 2015-01-01 00:00:00 | 15.00
1 | 2015-10-01 00:00:00 | 12.00
2 | 2015-01-01 00:00:00 | 14.00
I'm trying to create a query that will return every order and its unit price for that customer at the time of the order, like this:
desired result
customer_id | quantity | price
------------+----------+------
1 | 5 | 15.00
1 | 3 | 12.00
2 | 7 | 14.00
1 | 2 | 12.00
This is essentially what I want, but I know that you can't reference an outer query inside an inner query, and I'm having trouble figuring out how to re-factor:
SELECT
o.customer_id,
o.quantity,
p.price
FROM orders o
INNER JOIN (
SELECT price
FROM prices x
WHERE x.customer_id = o.customer_id
AND x.effective_time <= o.timestamp
ORDER BY x.effective_time DESC
LIMIT 1
) p
;
Can anyone suggest the best way to make this work?
Instead of joining an inline view based on the prices table, you can perform a subquery in the SELECT list:
SELECT customer_id, quantity, (
SELECT price
FROM prices p
WHERE
p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
ORDER BY p.effective_time DESC
LIMIT 1
) AS price
FROM orders o
That does rely on a correlated subquery, which could be bad for performance, but with the way your data are structured I doubt there's a substantially better alternative.
You dont need the subquery, just a plain inner join will do (this assumes there are no duplicate effective_times per customer):
SELECT o.customer_id, o.quantity
,p.price
FROM orders o
JOIN prices p ON p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
AND NOT EXISTS ( SELECT * FROM prices nx
WHERE nx.customer_id = o.customer_id
AND nx.effective_time <= o.timestamp
AND nx.effective_time > p.effective_time
)
;

Select row that has max total value SQL Server

I have the following scheme (2 tables):
Customer (Id, Name) and
Sale (Id, CustomerId, Date, Sum)
How to select the following data ?
1) Best customer of all time (Customer, which has Max Total value in the Sum column)
For example, I have 2 tables (Customers and Sales respectively):
id CustomerName
---|--------------
1 | First
2 | Second
3 | Third
id CustomerId datetime Sum
---|----------|------------|-----
1 | 1 | 04/06/2013 | 50
2 | 2 | 04/06/2013 | 60
3 | 3 | 04/07/2013 | 30
4 | 1 | 03/07/2013 | 50
5 | 1 | 03/08/2013 | 50
6 | 2 | 03/08/2013 | 30
7 | 3 | 24/09/2013 | 20
Desired result:
CustomerName TotalSum
------------|--------
First | 150
2) Best customer of each month in the current year (the same as previous but for each month in the current year)
Thanks.
Try this for the best customer of all times
SELECT Top 1 WITH TIES c.CustomerName, SUM(s.SUM) AS TotalSum
FROM Customer c JOIN Sales s ON s.CustomerId = c.CustomerId
GROUP BY c.CustomerId, c.CustomerName
ORDER BY SUM(s.SUM) DESC
One option is to use RANK() combined with the SUM aggregate. This will get you the overall values.
select customername, sumtotal
from (
select c.customername,
sum(s.sum) sumtotal,
rank() over (order by sum(s.sum) desc) rnk
from customer c
join sales s on c.id = s.customerid
group by c.id, c.customername
) t
where rnk = 1
SQL Fiddle Demo
Grouping this by month and year should be trivial at that point.