PostgreSQL: Conditionally combine values from two rows - sql

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"

Related

postgres grouping

I am having a db names products where i wanted to select the price of each product based on the id, but the price that i stored in the table is from different sources. So i want one latest price from each of the source.
My table looks like this
id | name | source | updated_at | price
1 | ace | vanil | ... | 100
2 | vax | vanil | ... | 101
3 | tax | sunyil | ... | 200
1 | ace | sunyil | latest | 99.5
2 | vax | sunyil | latest | 100.5
3 | tax | vanil | latest | 199.5
3 | tax | vanil | ... | 220
3 | tax | vanil | ... | 211
3 | tax | vanil | ... | 205
3 | tax | sunyil | ... | 211
3 | tax | vanil | ... | 220
3 | tax | sunyil |latest_time | 220
1 | ace | sunyil | ... | 101
i want the output to be like this when my where condition is for id=3
id | name | source | updated_at | price
3 | tax | vanil | latest time| 199.5
3 | tax | sunyil | latest time| 220
i tried running the
select * from products WHERE id= '3' ORDER BY updated_at DESC LIMIT 1
but this one brings only one row irrespective of the source
could any one help me out with this. I am extremely new to postgres and sql queries. I would really appreciate your help.
It's not really clear what you want to do. If you would like to sum the price for the product with id 3 not having the text "..." in the column "updated_at", you can do this query:
SELECT id, name, source, updated_at, SUM(price) FROM products
WHERE id = 3 and updated_at != '...'
GROUP BY id, name, source, updated_at ORDER BY updated_at;
See this example and try out: db<>fiddle
Modify the query to your desires if necessary.
Using DISTINCT ON:
SELECT DISTINCT ON (id, source) *
FROM products
WHERE id = 3
ORDER BY id, source, updated_at DESC;

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'
);

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

SELECT effective price based on dates

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;

Exclude an item from a table but take it's attributes (quantity, weight, price) and add it onto an item in a new table (complete details shown)

For this example, a user can select one or more package from a checklistbox that is populated with packages. The packages info is stored in the packageHeaderTable. A Package or packages are made for a customer. PackageID is PK of this table (packageHeaderTable) and autoincrements for every new package being sold. The packageHeaderTable looks like this with sample data of 3 packages (there are hundreds):
PackageID | Weight | TotalPrice
-------------------------------
1 | 12.5 | $24.00
-------------------------------
2 | 15.0 | $26.00
-------------------------------
3 | 17.5 | $28.00
A Package hold several items. Each item has attributes such as weight, price, Quantity, Item Description. This table is called PackageLineTable. Note that ItemId is the PK of this table while PackageID is the PK, FK of this table coming from PackageHeaderTable
ItemID | PackageID | ItemDesc | Weight| Price | Quantity|
----------------------------------------------------------
1 | 1 | ball | 2.5 | 2.00 | 1
----------------------------------------------------------
2 | 1 | bat | 7.0 | 15.00 | 2
----------------------------------------------------------
3 | 1 | glove | 3.0 | 7.00 | 1
----------------------------------------------------------
1 | 2 | ball | 2.5 | 2.00 | 3
----------------------------------------------------------
2 | 2 | bat | 7.0 | 15.00 | 3
----------------------------------------------------------
3 | 2 | glove | 3.0 | 7.00 | 1
----------------------------------------------------------
1 | 3 | ball | 2.5 | 2.00 | 4
----------------------------------------------------------
2 | 3 | bat | 7.0 | 15.00 | 3
----------------------------------------------------------
3 | 3 | glove | 3.0 | 7.00 | 1
----------------------------------------------------------
Now I have to make a BallBatPackage Table and create a report that uses this table which excludes the item 'gloves' in every package but still takes the gloves weight and price and adds it onto the first ball's weight and price that is in the customer's order of packages.
BallBatReportHeaderTable (PackageId is FK from PackageLineTable)
ReportID (PK) | PackageID (PK,Fk) | Weight | TotalPrice
-------------------------------------------------------
1 | 1 | 12.5 | $24.00
-------------------------------------------------------
1 | 2 | 15.0 | $26.00
-------------------------------------------------------
Report/Table I have to create needs to look like this:
BallBatReportLineTable
ReportID(PK,FK) | ItemID (PK) | PackageID (Pk,FK) | ItemDesc | Weight| Price | Quantity|
-----------------------------------------------------------------------------------------------
1 | 1 | 1 | ball | 8.5 | 16.00 | 3
-----------------------------------------------------------------------------------------------
1 | 2 | 1 | bat | 7.0 | 15.00 | 2
-----------------------------------------------------------------------------------------------
1 | 1 | 2 | ball | 2.5 | 2.00 | 3
-----------------------------------------------------------------------------------------------
1 | 2 | 2 | bat | 7.0 | 15.00 | 3
-----------------------------------------------------------------------------------------------
Note that the two gloves that were in the customer's two packages (PackageID 1 and Package ID 2) that were ordered are gone but it's weight, price, and quantity got added onto the first ball on the list of items in the two packages.
I am having a hard time figuring out how to create this table and have it do what I need it to do with the ball and gloves. This is what I've tried so far to deal with it's quantity but it's not working right. (I am just working with Quantity for now). I have in front of me the packages the customer ordered and am checking the packages that he ordered on the chkLstPackages (checklistbox) and clicking add Packages (cmdAddPackages) to the DGVBallBatReport to basically view the table right after it gets created. I need to do this SQL update and have it work somehow but it isn't doing what I need and I'm just getting lost and confused:
Dim cmd As New SqlCommand("UPDATE BallBatLineTable SET Quantity = (BallBatLineTable.Quantity + (Select sum(Quantity) FROM PackageLineTable WHERE PackageID = #PackageID and ItemID Like 'Glove'))", con)
For i As Integer = 0 To chkLstPackages.Items.Count - 1
If chkLstShipments.GetItemCheckState(i) = CheckState.Checked Then
If isFirst = True Then
cmd.Commandtext += " Where PackageID = #PackageID" + i.toString
isFirst = False
Else
cmd.CommandText += " OR PackageID = #PackageID + i.toString
End If
cmd.Parameters.Add("#PackageID" + i.ToString, SqlDbType.Int).Value = chkLstPackages.Items(i)
End If
Next
I'm not sure why you are creating new tables for reporting things. To create reports you should just be using regular queries based off your PackageLineTable.
So for package total:
select
packageid,
sum(weight * quantity) [weight],
sum(price * quantity) [price]
from packagelinetable
group by packageid
To add the total of the "gloves" item to the first item in a package of a different type:
You can do a sub-select, that takes all rows that are "gloves" and replaces the itemdesc with another item description. After that do a group by query where everything is added together:
select
packageid,
itemdesc,
sum(weight * quantity) [weight],
sum(price * quantity) [price]
from (select
packageid,
weight,
price,
quantity,
case itemdesc
when 'glove'
then (select top 1 itemdesc from packagelinetable i where i.packageid = p.packageid and i.itemdesc <> 'glove' order by itemdesc)
else itemdesc end [itemdesc]
from packagelinetable p) as sub
group by packageid, itemdesc