SELECT effective price based on dates - sql

I am currently working on a supermarket database design where I have to have retrieve the effective price of the product based on the dates.
For example, price of Product A is $9.50 and it will be $10 effective from 1 July. However, this month is still June, so the price of Product A will still be the original price which is $9.50
The problem that I am facing is I am not able to select only one record from each product but all of them.
I am currently using SQL Server.
Price Table:
product_id | product_name | price | effective_date
=======================================================
1 | Product A | 8.00 | 1-5-2020
1 | Product A | 9.50 | 1-6-2020
1 | Product A | 10.00 | 1-7-2020
2 | Product B | 4.00 | 5-6-2020
2 | Product B | 4.50 | 5-7-2020
Codes:
SELECT Product.id, Product.product_name,Price_Table.price, Price_Table.effective_date,
(select top 1 price from Price_Table
where Price_Table.product_id=Product.id and getdate()>= effective_date
order by effective_date desc) 'Latest Price'
FROM Price_Table, Product
WHERE
Product.id=Price_Table.product_id
Result:
id | product_name | price | effective_date | Latest Price
----------------------------------------------------------------
1 | Product A | 8.00 | 1-5-2020 | 9.50
1 | Product A | 9.50 | 1-6-2020 | 9.50
1 | Product A | 10.00 | 1-7-2020 | 9.50
2 | Product B | 4.00 | 1-6-2020 | 4.00
2 | Product B | 4.50 | 1-7-2020 | 4.00
Expected Result:
id | product_name | price | effective_date | Latest Price
----------------------------------------------------------------
1 | Product A | 9.50 | 1-6-2020 | 9.50
2 | Product B | 4.00 | 1-6-2020 | 4.00
Based on the result, it shows all the records which is wrong. Expected result is the one result I hope to
get.
Can anyone help me with this? Thanks!

You can use cross apply:
SELECT p.*, pt.*
FROM Product p OUTER APPLY
(SELECT TOP (1) pt.*
FROM Price_Table pt
WHERE p.id = pt.product_id AND pt.effective_date <= GETDATE()
ORDER BY pt.effective_date DESC
) pt;

Related

SQL - How to get latest price based on effective date?

Fairly straight forward query that is eluding me.. how do I get the effective cost for each product based upon the latest effective date given 7-6-2020 as the effective date?
Price Table:
id | product_name | cost | effective_date
=======================================================
1 | Product A | 8.00 | 1-5-2020
1 | Product A | 9.50 | 1-6-2020
1 | Product A | 10.00 | 1-7-2020
2 | Product B | 4.00 | 5-6-2020
2 | Product B | 4.50 | 5-7-2020
Expected Result:
id | product_name | cost | effective_date
-----------------------------------------------
1 | Product A | 10.00 | 1-7-2020
2 | Product B | 4.00 | 5-6-2020
One method is a correlated subquery:
select t.*
from t
where t.effective_date = (select max(t2.effective_date)
from t t2
where t2.id = t.id and
t2.effective_date <= '2020-07-06'
);

Calculating COGS to finish stock balance report SQL

I have two source table as following: Purchase & Sale
purchase (incoming)
--------------+----------+-------+----------+--------------
inventory_id | quantity | price | Amount | timestamp
--------------+----------+-------+----------+--------------
A | 1 | $1.00 | $1.00 | 2020-02-01
B | 2 | $1.00 | $2.00 | 2020-02-02
C | 4 | $2.00 | $8.00 | 2020-02-03
--------------+----------+-------+----------+--------------
sale (outgoing)
--------------+----------+-------+----------+--------------
inventory_id | quantity | price | Revenue | timestamp
--------------+----------+-------+----------+--------------
A | 1 | $3.00 | $3.00 | 2020-02-04
B | 1 | $3.00 | $3.00 | 2020-02-05
C | 2 | $3.00 | $6.00 | 2020-02-05
C | 1 | $3.00 | $3.00 | 2020-02-07
--------------+----------+-------+----------+--------------
What I want to do is the balance report in the below format:
Balance Report (2020-02-02 to 2020-02-05)
--------------+-----------+-------------+-------------+---------------+-----------+------------+---------+------------+
inventory_id | Begin Qty | Begin Amount| Purchase Qty|Purchase Amount| Sale Qty | COGS Amount | End Qty | End Amount |
--------------+-----------+-------------+-------------+---------------+-----------+------------+---------+------------+
A | 1 | $1.00 | | | 1 | $1.00 | | |
B | | | 2 | $2.00 | 1 | $1.00 | $1.00 | $1.00 |
C | | | 4 | $8.00 | 2 | $4.00 | $2.00 | $4.00 |
--------------+-----------+-------------+-------------+---------------+-----------+------------+---------+------------+
Note: The last outgoing transaction is out of the Report time range (2-05/02/2020) then not showing on the report.
MY CURRENT SOLUTION:
Add one column 'COGS' in the Sale table
I have to calculate the COGS of all inventory_id in the current sale order
Then save the COGS to the server with other information of sale order
THE PROBLEM:
If there is any change of the past transactions in the Purchase or Sale order, the COGS column does not UPDATE.
I NEED HELP ON:
Case 1: Keep COGS Column in the Sale table
How to update the value in this column if pasts transactions changed?
Case 2: Remove COGS Column
Pls help me to write a query to calculate the Sale Amount column in the Balance Report (Moving average is ok)
It would be great if you could provide the query to calculate the Sale Amount column by FIFO, LIFO.
Thanks a lot.
I found the solution: "I need to update COGS unit price for every transaction"
Following is the main code to update COGS
You might need to change something to adapt your case.
I know it is not the best solution but for my case it is ok.
create procedure update_COGS
as
update Sale
set
Sale.price = b.Aprice
from
Sale a
left join (select code, SUM(quantity) as quantity, sum(amount) as amount, SUM(amount)/nullif(sum(quantity),0) as Aprice from Purchase group by code) b
on a.code = b.code

How to query the Gross Revenue group by STORE, by item's CATEGORY for each days in year? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have SALES table of each days, its has an STOREID and the ITEMID with the DATE and GROSS
| DATE | STOREID | ITEMID | GROSS |
|------------ |--------- |-------- |------- |
| 2020-07-07 | STORE1 | ITEM1 | 10000 |
| 2020-07-07 | STORE2 | ITEM1 | 25000 |
| 2020-07-06 | STORE2 | ITEM3 | 15000 |
| 2020-07-06 | STORE3 | ITEM2 | 21000 |
The PRODUCT table show the category of the items, we have 5 items with 5 categories:
| ITEMID | CATEGORY |
|-------- |---------- |
| ITEM1 | A |
| ITEM2 | B |
| ITEM3 | C |
| ITEM4 | B |
| ITEM5 | D |
How can I select the revenue by storeid, itemid of each day with all of the category (if that day only sold category A, B then category C, D will show with gross is 0). Here the example of expected result when selecting the gross of July 7 for STORE1:
| DATE | STOREID | CATEGORY | GROSS |
|------------ |--------- |---------- |------- |
| 2020-07-07 | STORE1 | A | 10000 |
| 2020-07-07 | STORE1 | B | 0 |
| 2020-07-07 | STORE1 | C | 0 |
| 2020-07-07 | STORE1 | D | 0 |
I have tried:
SELECT distinct T.DATE, T.STOREID, P.CATEGORY, ISNULL(T.GROSS,0) AS GROSS
FROM PRODUCT P LEFT JOIN (
SELECT CONVERT(DATE, DATEID) AS DATE, STOREID, P.CATEGORY, convert(numeric(10,0), sum(S.GROSS)) AS GROSS
FROM SALES S join PRODUCT P on S.ITEMID = P.ITEMID
WHERE DATEID = '2020-07-07' and STOREID = 'STORE1'
GROUP BY P.CATEGORY, DATEID, STOREID
) T
ON P.CATEGORY = T.CATEGORY
The results I get is something like this:
| DATE | STOREID | CATEGORY | GROSS |
|------------ |--------- |---------- |------- |
| 2020-07-07 | STORE1 | A | 10000 |
| 2020-07-07 | STORE1 | B | 0 |
| NULL | NULL | C | 0 |
| NULL | NULL | D | 0 |
So when I execute the Query for others STORES and others days, how can I automated specify the correct value for the NULL value (like the expected result)
Thank you guys so much for your help!
Is this homework? It looks a bit like homework, so instead of writing you a query, I will give you a strategy to think about.
You will first have to generate a cartesian product of date, and category, and store. Then find the sales that apply to each combination of {date, category, store}.
In general, generating "all of the dates you care about" is easy with a calendar table, or tally table, but in your specific case you could also generate all the dates you need for the cartesian product by querying your sales table for the distinct dates. This solution won't always work, because what if you want an output row for a date where no sales happened?
A cartesian product in sql is generated using a cross join
So, the approach you want to take:
Get a set of all the dates you need in the output
Cross join that against the set of all categories you need in the output
Cross join that against the set of all stores you need in the output
Left join that to the sales table

Using CASE in WHERE clause

I have 2 tables that needs to be joined based on couple of parameters. One of the parameters is year. One table contains the current year but another year doesn't contain current year, so it must use the latest year and matched with other parameters.
Example
Product
-------------------------------------------------------------------------------
| product_id | category_id | sub_category_id | product_year | amount |
-------------------------------------------------------------------------------
| 504 | I | U | 2020 | 400 |
| 510 | I | U | 2019 | 100 |
| 528 | I | U | 2019 | 150 |
| 540 | I | U | 2018 | 1000 |
Discount
-----------------------------------------------------------------------------
| discount_year | category_id | sub_category_id | discount |
-----------------------------------------------------------------------------
| 2018 | I | U | 0.15 |
| 2017 | I | U | 0.35 |
| 2016 | I | U | 0.50 |
Output
-----------------------------------------------------------------------------
| product_id | category_id | sub_category_id | product_year | discount_year |
-----------------------------------------------------------------------------
| 504 | I | U | 2020 | 2018 |
| 510 | I | U | 2019 | 2018 |
| 528 | I | U | 2019 | 2018 |
| 540 | I | U | 2018 | 2017 |
The discount is always gotten from one year behind but if those rates aren't available, then it would keep going back a year until available.
I have tried the following:
SELECT
product_year, a.product_id, a.category_id, a.sub_category_id,
discount_year, amount, discount
FROM
Product a
INNER JOIN
Discount b ON a.category_id = b.category_id
AND a.sub_category_id = b.sub_category_id
AND product_ year = CASE
WHEN discount_year + 1 = product_year
THEN discount_year + 1
WHEN discount_year + 2 = product_year
THEN discount_year + 2
WHEN discount_year + 3 = product_year
THEN discount_year + 3
END
WHERE
product = 540
This return the following output:
--------------------------------------------------------------------------------------------------
| product_year | product_id | category_id | sub_category_id | discount_year | amount | discount |
--------------------------------------------------------------------------------------------------
| 2016 | 540 | I | U | 2017 | 1000 | 0.50 |
| 2017 | 540 | I | U | 2017 | 1000 | 0.35 |
Any help will be appreciated.
You can use OUTER APPLY and a subquery. In the subquery select the row with the maximum discount_year, that is less the product_year using TOP and ORDER BY.
SELECT p.product_year,
p.product_id,
p.category_id,
p.sub_category_id,
d.discount_year,
p.amount,
d.discount
FROM product p
OUTER APPLY (SELECT TOP 1
*
FROM discount d
WHERE d.category_id = p.category_id
AND d.sub_category_id = p.sub_category_id
AND d.discount_year < p.product_year
ORDER BY d.discount_year DESC) d;
instead of a CASE expression you can use a sub-query to select the TOP 1 related
discount_year that is less than your product_year, ORDER BY discount_year ASC.
Create a product to discount mapping using a CTE first. This contains the discount year pulled from discount table for every product year in the product table and corresponding product_id. Following this, you can easily join with relevant tables to get results and eliminate any nulls as needed
Simplified query.
;WITH disc_prod_mapper
AS
(
SELECT product_id, product_year,(SELECT MAX(discount_year) FROM #Discount b WHERE discount_year < product_year AND a.category_id = b.category_id AND a.sub_category_id = b.sub_category_id ) AS discount_year
FROM Product a
)
SELECT a.product_year, c.discount_year, a.amount, c.discount
FROM Product a
LEFT JOIN disc_prod_mapper b ON a.product_id = b.product_id
LEFT JOIN Discount c ON b.discount_year = c.discount_year

PostgreSQL: Conditionally combine values from two rows

I have inherited a table structure that I have queried to result in the following table:
---------------------------------------------------
| purchase | item | price | category |
---------------------------------------------------
| 1 | widget A | 20.00 | product |
| 1 | widget B | 50.00 | product |
| 2 | widget A | 20.00 | product |
| 3 | widget A | 20.00 | product |
| 3 | promo | 30.00 | product |
| 3 | widget B | 0.00 | bundle |
---------------------------------------------------
And I am trying to extract a specific report that combines the bundled product into the result of the promo item (as they are related), such that it looks like this:
----------------------------------------------------------
| purchase | item | price | category |
----------------------------------------------------------
| 1 | widget A | 20.00 | product |
| 1 | widget B | 50.00 | product |
| 2 | widget A | 20.00 | product |
| 3 | widget A | 20.00 | product |
| 3 | promo - widget B | 30.00 | product |
----------------------------------------------------------
For any item that has a category of 'bundle', the price will be 0.00 and there will be a corresponding promo item as part of that same purchase. For now, I can assume that there is only one bundle product per purchase, but a solution that can concat multiple bundle items to one promo (eg. 'promo - widget B, widget C) would be even better.
Please let me know if I can clarify anything.
This assume only one bundle for purchase. Otherwise you need something to make the pairing.
First you create a field category_type so can group the bundle together.
Then use array_agg to put them together
Finally join the bundles with the normal products.
SQL DEMO
WITH cte as (
SELECT *, CASE WHEN "item" = 'promo' OR "category" = 'bundle'
THEN 'bundle'
ELSE 'product'
END as category_type
FROM purchases
), bundle as (
SELECT purchase,
array_to_string(array_agg(item ), ' - ') as "item",
MAX(price) as price,
MAX(category) as category
FROM cte
WHERE category_type = 'bundle'
GROUP BY purchase
)
SELECT * FROM bundle
UNION ALL
SELECT "purchase", "item", "price", "category"
FROM cte
WHERE category_type <> 'bundle'
ORDER BY "purchase"