Getting complex cross sale details in PostgreSQL - postgresql

I have an issue where i need to find out the details of the revenue that got from a cross sale product.
For example, I want to know which all are the category combinations a customer buy with a selected category.Also the amount spend by that customer on other categories other than the selected category.
Below is a sample Query which will give me the combinations and the revenue, Using a filter clause i can get the revenue of the category with and without the selected category if the combination count is 2 category, But when there more than 2 category in combinations i need to get the info how much each category cost, How can i get it?
Database : PostgreSQL Version 11
WITH filter_2 as
(
SELECT DISTINCT
o.order_number
, o.client_email
, COALESCE(category,'') AS category_name
, SUM(revenue) as revenue
FROM
orders o
INNER JOIN distinct_orders dos on dos.client_email=o.client_email
LEFT JOIN items i ON i.order_id = o.order_id
LEFT JOIN products p ON p.product_id = i.product_id
WHERE o.state ='Done'
GROUP BY o.order_number
, o.client_email, COALESCE(category,'')
)
,result_set_1 as
(
SELECT
f.order_number
,f.client_email
,string_agg(DISTINCT COALESCE(category,''), ' , ' ORDER BY COALESCE(category,'')) as cat_level_3_name
,COUNT(DISTINCT category) as prod_count
,SUM(revenue) revenue
FROM filter_2 f
GROUP BY f.order_number,f.client_email
)
SELECT
COUNT(DISTINCT order_number) as order_count,
COUNT(DISTINCT client_email) as customer_count
,category
,SUM(revenue) as revenue
FROM result_set_1 r
WHERE category IS NOT NULL
GROUP BY category
ORDER BY order_count DESC
Sample Input
Order_number client_email category Revenue
"819214" "olx#gmail.com" "A Tea" 290.00
"819214" "olx#gamil.com" "B Tea" 10.00
"608759" "lixxx#gmail.com" "A Tea" 15.00
"608759" "lixxx#yahoo.com" "B Tea" 20.00
"608759" "lixxx#gmail.com" "C Tea" 400.00
"237070" "news#gmail.com" "A Tea" 60.0
"237070" "news#gmail.com" "B Tea" 10.0
"508759" "chink#gmail.com" "A Tea" 15.00
"508759" "chink#gmail.com" "B Tea" 25.00
"508759" "chink#gmail.com" "C Tea" 45.00
"578759" "xxxx#gmail.com" "A Tea" 15.00
"588759" "xyyy#gmail.com" "A Tea" 15.00
"598759" "vtyy#gmail.com" "A Tea" 15.00
Expected Output
So if i want to know which all combination categories the customer bought up along with "A Tea",
Combinations Customer count Combination count Revenue Split UP
A Tea 7 3 ( Or 7) A Tee = 45 or (425)
A Tea, B Tea 2 2 A Tea = 350 B Tea = 20
A Tea, B Tea, C Tea 2 2 A Tea= 30 B Tea=45 C Tea = 445

Related

How to get a ratio of male to female medal winners where a single name can show up multiple times?

Say I have the following table:
Name
Sex
Medal
John
M
Gold
John
M
Silver
Chris
M
Bronze
Ana
F
Null
Isobel
F
Bronze
I would like to get the ratio of Male to Female medal winners; in this case, I need to get the number 2 (John and Chris won medals, And won a medal). I don't know how to do this.
What I have is simply listing the number of distinct medal winners, grouped by gender:
SELECT "Sex", COUNT( DISTINCT "Name" ) AS number_of_medal_winners
FROM table
WHERE "Medal" IS NOT NULL
GROUP BY "Sex";
which results in
Sex
number_of_medal_winners
F
1
M
2
Use a case expression to filter what is counted to build your ratio:
SELECT COUNT(CASE WHEN sex = "M" THEN name END)/COUNT(CASE WHEN sex = 'F' THEN name END) as ratio_of_male_medal_winners_to_female
FROM yourtable
WHERE Medal IS NOT NULL

INNER JOIN change SUM result

CREATE TABLE beauty.customer_payments
(
customer_id integer,
date date,
amount numeric(10,2),
CONSTRAINT customer_payments_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES beauty.customers (customer_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE beauty.sales
(
product_id integer,
customer_id integer,
sell_date date NOT NULL,
qty integer NOT NULL,
sell_price numeric(10,2) NOT NULL,
expiry_date date NOT NULL,
CONSTRAINT sales_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES beauty.customers (customer_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT sales_product_id_fkey FOREIGN KEY (product_id)
REFERENCES beauty.products (product_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
Balance of payments beauty.customer_payments for customer_id=6 is 0
SELECT * FROM beauty.customer_payments
WHERE customer_id=6;
customer_id
date
amount
6
2020-11-14
75.00
6
2020-11-14
-75.00
SELECT * FROM beauty.sales
WHERE customer_id=6;
product_id
customer_id
sell_date
qty
sell_price
expiry_date
76
6
2020-11-14
1
75.00
2022-03-03
83
6
2020-11-14
1
10.00
2022-06-23
85
6
2020-11-14
1
10.00
2022-06-23
44
6
2020-11-14
1
12.00
2022-06-23
41
6
2020-11-14
1
15.00
2022-03-26
96
6
2020-11-14
1
75.00
2022-03-15
28
6
2020-11-14
1
4.00
2022-01-22
33
6
2020-11-14
1
4.00
2023-01-23
37
6
2020-11-14
1
4.00
2023-01-23
40
6
2020-11-14
1
4.00
2023-08-13
(10 rows)
SELECT customer_id, SUM(qty * sell_price) AS purchased
FROM beauty.sales
WHERE customer_id=6
GROUP BY customer_id;
customer_id
purchased
6
213.00
SELECT s.customer_id,
SUM(qty * sell_price) AS purchased,
SUM(cp.amount) AS paid,
SUM(qty * sell_price - cp.amount) AS balance
FROM beauty.sales s
INNER JOIN beauty.customer_payments cp
ON cp.customer_id = s.customer_id
WHERE s.customer_id=6
GROUP BY s.customer_id;
customer_id
purchased
paid
balance
6
1065.00
0.00
1065.00
Please advise WHY after adding JOIN (INNER, LEFT, RIGHT) calculation goes wrong and how to solve this multi-calculation issue?
As I see similar question all of them based on not cross-tables calculations like SUM(qty * sell_price - cp.amount)
Delete payments
`
DELETE FROM beauty.customer_payments
WHERE customer_id=6;`
Add new ZERO payment
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', 0);
customer_id
purchased
paid
balance
6
213.00
0.00
213.00
Add payment 10
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', 10);
SELECT * FROM beauty.customer_payments WHERE customer_id=6;
customer_id
date
amount
6
2020-11-17
0.00
6
2020-11-17
10.00
SELECT s.customer_id,
.......
INNER JOIN beauty.customer_payments cp
.......
customer_id
purchased
paid
balance
6
426.00
100.00
326.00
Correct payment with negative amount
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', -10);
SELECT * FROM beauty.customer_payments WHERE customer_id=6;
customer_id
date
amount
6
2020-11-17
0.00
6
2020-11-17
10.00
6
2020-11-17
-10.00
SELECT s.customer_id,
.......
INNER JOIN beauty.customer_payments cp
.......
customer_id
purchased
paid
balance
6
639.00
0.00
639.00
What is this `INNER JOIN' calculate?
In general, you probably wouldn't want to match each payment to each sale (unless there's an additional identifier matching specific payments to specific sales, not just matching each to a customer).
If a customer has 2 sales for $10 and $15, and two payments for $9 and $14, your join is going to match each payment to each sale for that customer, creating something like
Sale
Payment
$10
$9
$10
$14
$15
$9
$15
$14
So the sum of the sales after the join will be $50, not $25 (as you might be expecting). I think the above answers your question about why the join doesn't do what you expect.
The exact query you want might be a little different (do you want all customers even if they have no sales? is it possible for customers to have payments if they don't have a sale?), but in general I'd expect something like the following to work. There are multiple ways of doing this, but I think the following is easy to understand since it aggregates the data into one payment row per customer and one sales row per customer before joining them.
SELECT
s.customer_id,
s.purchased,
cp.amount as paid,
s.purchased - cp.amount as balance
FROM
(SELECT s.customer_id,
SUM(s.qty * s.sell_price) AS purchased
FROM beauty.sales s
GROUP BY s.customer_id) s
LEFT OUTER JOIN
(SELECT cp.customer_id,
SUM(cp.amount) AS amount
FROM beauty.customer_payments cp
GROUP BY cp.customer_id) cp
ON s.customer_id = cp.customer_id
WHERE s.customer_id = 6

How to find cross selling products in postgres

I have to create a report which shows the cross selling of a product ,Means if i have a product X i need to find the combination of this product which is purchased with other products and it's count.
So i have the table structure as follows,
Below is the same data.Reference is the order number and for each item there is a separate row which shows the product and category details.
Reference. Product Name. Prod ID. Category
1000001 Honda x12 10023 Machinery
1000001 Honda cv12 10025 Machinery
1000002 Medic. 12x 10026 Medicine
1000002 Honda x12 10023 Machinery
1000003 Honda x12 10023 Machinery
1000004 Appliance x12 10033 Household
1000004 Honda x12 10023 Machinery
1000005 Bag x234 100265 Bags
I want the output to be like,
Suppose i want to find the cross sell products for Honda x12, means i wan to know which all are the products sold in combination with Honda x12 and number of occurrences that particular combination count occured.
Can anyone suggest me how i can do this in PostgreSQL(Version 11).
Thanks in advance
I think that's a self-join with an inequality condition:
select t.prod_id prod_id1, x.prod_id prod_id2, count(*) cnt
from mytable t
inner join mytable x
on x.reference = t.reference
and x.prod_id > t.prod_id
group by t.prod_id, x.prod_id
order by 1, 2
> is on purpose in the join predicate instead of <>, to avoid "mirror" records in the result set.
For your sample data, this generates:
prod_id1 | prod_id2 | cnt
-------: | -------: | --:
10023 | 10025 | 1
10023 | 10026 | 1
10023 | 10033 | 1
This gives you the results for all products at once. If you want the list of "pairs" of a given product only, then it is slightly different:
select t.prod_id, count(*) cnt
from mytable t
inner join mytable x
on x.reference = t.reference
and x.prod_id <> t.prod_id
where x.prod_id = 10023
group by t.prod_id
order by 1
Demo:
prod_id | cnt
------: | --:
10025 | 1
10026 | 1
10033 | 1

Postgres: Query to compare Data with previous Data

I have a main table where all my results will be written to.
Each object that will be checked is identified by the item_id:
Checkdate item_id Price Cat A Price Cat B
2017-04-25 1 29.99 84.99
2017-04-24 1 39.99 89.99
2017-04-23 1 39.99 91.99
2017-04-25 2 42.99 88.99
2017-04-23 2 41.99 81.99
2017-04-22 2 50.99 81.99
2017-04-21 2 42.99 81.99
In the postgres query i select all results with the current_date = checkdate to provide the newest data:
Item Price Cat A Price Cat B
1 29.99 84.99
2 42.99 88.99
So far its not a problem for me. But now i want to compare these results with the previous results. Something like that:
Item Price Cat A Price Cat A Before Price Cat B Price Cat B Before
1 29.99 39.99 84.99 89.99
2 42.99 41.99 88.99 81.99
But I have no idea how to do that. These items doesn't exist on every day (item 2 doesn't exist on 2017-04-24 for example).
Can someone help me?
select
item_id,
min(price_cat_a) filter (where rn = 1) as a,
min(price_cat_a) filter (where rn = 2) as a_before,
min(price_cat_b) filter (where rn = 1) as b,
min(price_cat_b) filter (where rn = 2) as b_before
from (
select
item_id, price_cat_a, price_cat_b,
row_number() over (partition by item_id order by checkdate desc) as rn
from t
where checkdate <= current_date
) s
where rn <= 2
group by item_id
;
item_id | a | a_before | b | b_before
---------+-------+----------+-------+----------
1 | 29.99 | 39.99 | 84.99 | 89.99
2 | 42.99 | 41.99 | 88.99 | 81.99
You can use a lateral join:
SELECT today.item_id,
today."Price Cat A",
before."Price Cat A" AS "Price Cat A Before",
today."Price Cat B",
before."Price Cat B" AS "Price Cat B Before"
FROM main today
CROSS JOIN LATERAL
(SELECT "Price Cat A",
"Price Cat B"
FROM main
WHERE item_id = today.item_id
AND "Checkdate" < today."Checkdate"
ORDER BY "Checkdate" DESC
LIMIT 1
) before
WHERE today."Checkdate" = current_date
ORDER BY today.item_id;
These items doesn't exist on every day -- because of this, your original query has an error too (i.e. it won't contain all of your items).
If you are looking for the last (and the second last) checkdate, there is no need to use current_date (unless, there might be future data in your table; in that case just append where checkdate <= current_date to filter them out).
Finding the last row (within its group, i.e. in your case, it's item_id) is a typical greatest-n-per-group problem, and the second last is easy with the lag() window function:
select distinct on (item_id)
item_id,
price_cat_a,
price_cat_a_before,
price_cat_b,
price_cat_b_before
from (select *,
lag(price_cat_a) over w price_cat_a_before,
lag(price_cat_b) over w price_cat_b_before
from t
window w as (partition by item_id order by checkdate)) t
order by item_id, checkdate desc
http://rextester.com/AGZ99646

Calculated balance of purchased lots

I have a list of purchases by date. EG:
ItemCode, Purchase Date, Purchase Qty
XXX, 01 Jan 2012, 10
XXX, 10 Jan 2012, 5
For the item I have a corresponding Sales transactions:
Item, Sales Date, Sales Qty
XXX, 02 Jan 2012, -5
XXX, 09 Jan 2012, -3
XXX, 11 JAN 2012, -3
I am looking to get a SQL query (Without a cursor), to get the balance on each purchase order quantity. I.e Run each purchase (First in first out) to 0. (For the purposes of aging inventory )
How can you join the Purchases to the Sales to get this balance remaining each purchased Inventory Lot? Is this possible without a cursor?
Yes.
You union the two tables together, and run a running total on the resulting set.
;with cte as
(
select itemcode, purchasedate as tdate, purchaseqty as qty from purchases
union
select itemcode, salesdate, salesqty from sales
)
select
t1.*,
SUM(t2.qty)
from cte t1
left join cte t2
on t1.tdate>=t2.tdate
and t1.item = t2.item
group by t1.item, t1.pdate, t1.qty
To get the stock remaining at any particular time the same principal applies.
select p1.*,
case when (select SUM(abs(qty)) from sales) > SUM(p2.qty) then 0
else SUM(p2.qty) - (select SUM(abs(qty)) from sales) end as stockremaining
from purchases p1
left join purchases p2 on p1.item = p2.item
and p2.purchasedate <= p1.purchasedate
group by p1.purchasedate, p1.item, p1.qty
gives
1 2012-01-01 10 0
1 2012-01-10 5 4