SQL can't figure out how to join correctly - mysql

Having a joining issue
I have one table that has an ID and description column the seasons are new, but the descriptions repeat. so we can have an Adult price for season 34 and an adult price for season 35 etc.
select * from tableA
-- returns id, description, price, season etc ...
-- 1 "GT Adult" 10 34
-- 2 "GT Child" 5 34
-- 3 "GT Senior" 8 34
-- 1 "GT Adult" 11 35
-- 2 "GT Child" 6 35
-- etc.
TableB has multiple columns these columns have names/headers that correspond to the description column.
select * from tableB
-- returns customer_no adult, child, senior, order_dt, order_data, season, perf_no etc.
-- returns 112 0, 12, 2, order_dt, order_data, order_season.
-- returns 415 23, 0, 0, order_dt, order_data, order_season.
Basically each customer places an order for a given amount of tickets of each type.
The information we can use to join is season and performance that they match on ...
but i can't figure out how to say for customer 112 since he got 12 children's tickets he should be charged 5 a ticket, and 2 senior tickets he should be charged 8 dollar for each of those tickets.
Where as customer 415 should be charged $10 for each of the 23 tickets. by season.
The only thing I can do for sure is join on season but how do i join on the correct column.
Please advise.

I don't think you can do what you want with the tables you have. There is no clear way to associate the "adult" column in TableB with the row that contains "GT Adult" in TableA.
You could redesign TableB to solve this:
TableB (customer_no, ticket_type, quantity, order_dt, ...)
So for customer 112 we would have in TableB:
112, "GT_Child", 12 ...
112, "GT_Senior", 2 ...
So you can answer your queries by joining on ticket_type (and possibly other columns if you need them).
If possible, you should move the details of the order itself into a third table (let's call it TableC) and allocate an order number. So we would now have TableA as you have it and then:
TableB (order_no, customer_no, ticket_type, quantity)
TableC (order_no, order_dt, season ...)

You can use PIVOT to get all ticket prices in a single row per season:
SELECT season, [GT Adult], [GT Child], [GT Senior]
FROM (
SELECT season, price, [description]
FROM tableA
) source
PIVOT (
MAX(price)
FOR [description] IN ([GT Adult], [GT Child], [GT Senior])
) pvt
Given the sample data quoted in the OP, the above produces sth like:
season GT Adult GT Child GT Senior
-----------------------------------------
34 10 5 8
35 11 6 NULL
Then you can perform a simple INNER JOIN operation in order to get the total amount per customer order:
SELECT customer_no, adult * [GT Adult] + child * [GT Child] + senior * [GT Senior] AS total
FROM tableB AS B
INNER JOIN (
SELECT season, [GT Adult], [GT Child], [GT Senior]
FROM (
SELECT season, price, [description]
FROM tableA) source
PIVOT (
MAX(price)
FOR [description] IN ([GT Adult], [GT Child], [GT Senior])
) pvt
) t ON b.season = t.season
SQL Fiddle Demo
P.S. The above query works in SQL Server.
EDIT:
To simulate PIVOT in MySQL we have to use conditional aggregates:
select season,
sum(if(description='GT Adult', price ,null)) as adultPrice,
sum(if(description='GT Child', price ,null)) as childPrice,
sum(if(description='GT Senior', price ,null)) as seniorPrice
from tableA
group by season;
The above query gives us the result set with which a JOIN operation can be performed:
SELECT customer_no, adult * adultPrice + child * childPrice + senior * seniorPrice AS total
FROM tableB AS b
INNER JOIN (
SELECT season,
SUM(IF(description='GT Adult', price ,null)) AS adultPrice,
SUM(IF(description='GT Child', price ,null)) AS childPrice,
SUM(IF(description='GT Senior', price ,null)) AS seniorPrice
FROM tableA
GROUP BY season) AS a ON b.season = a.season
MySQL Demo here

Related

Aggregate information from one table to another with a different “layout” (mysql)

this is my starting table which provides sales information by Id.
Id
Store_Name
Market
Sales
Main_Product
1
StoreA
Rome
10
a
2
StoreB
Rome
15
b
3
StoreC
Rome
9
c
4
Mag1
Paris
10
a
5
Mag2
Paris
23
b
6
Mag3
Paris
12
c
7
Shop1
London
11
a
8
Shop2
London
31
b
9
Shop3
London
45
c
10
Shop4
London
63
d
In order to build a report and create some dynamic sentences, I will need the dataset to be "paginated" as per below table:
Id
Dimension
Dimension_Name
Sales
Main_Product
1
ShoppingCentre
StoreA
10
a
1
Market
Rome
34
a
2
ShoppingCentre
StoreB
15
b
2
Maket
Rome
34
b
3
ShoppingCentre
StoreC
9
c
3
Market
Rome
34
c
Do you have any tip about how to build the last table starting from the first one?
To sum-up:
The new table will be always by Id
Aggregation of market sales happens at row level where every single shopping centre is located
This is the query that I have built so far but wondering if there is a better and more efficient way to accomplish the same:
with store_temp_table as (
select
id
,Store_Name
,Market
, Main_Product
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1,2,3,4
)
, market_temp_table as (
select
market
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1
)
, store_temp_table_refined as(
Select
a.id
,a.Main_Product
, 'ShoppingCentre' as Dimension_Name
,SUM(a.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
, market_temp_table_refined as (
Select
a.id
,a.Main_Product
, 'Market' as DimensionName
,SUM(b.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
select * from store_temp_table_refined
union all
select * from market_temp_table_refined
Thank you
Use a CTE that returns the dimensions that you want and cross join it to a query that returns the columns of the table and an additional column with the total sales of each market:
WITH Dimensions(id, Dimension) AS (VALUES
ROW(1, 'ShoppingCentre'),
ROW(2, 'Market')
)
SELECT p.Id,
d.Dimension,
CASE d.id WHEN 1 THEN p.Store_Name ELSE p.Market END Dimension_Name,
CASE d.id WHEN 1 THEN p.Sales ELSE p.MarketSales END Sales,
p.Main_Product
FROM Dimensions d
CROSS JOIN (SELECT *, SUM(Sales) OVER (PARTITION BY Market) AS MarketSales FROM Production_Table) p
ORDER BY p.id, d.id;
Or, with UNION ALL:
SELECT Id,
'ShoppingCentre' Dimension,
Store_Name Dimension_Name,
Sales,
Main_Product
FROM Production_Table
UNION ALL
SELECT Id,
'Market',
Market,
SUM(Sales) OVER (PARTITION BY Market),
Main_Product
FROM Production_Table
ORDER BY Id,
CASE Dimension WHEN 'ShoppingCentre' THEN 1 WHEN 'Market' THEN 2 END;
See the demo.

Calculate dynamic price with many to many relationship using SQL

I'm new to SQL and I have two tables with a many-to-many relationship. I know the math behind the calculation by am unable to transfer it into SQL language
Tables:
Table A like:
Item Name
Effective Date
Cancelled Date
Unit Price
Book
2010-01-02
2021-12-21
10
Book
2022-01-01
2028-01-01
15
Ice Cream
2018-01-01
2028-01-01
4
Table B like:
Item Name
Sales Date
Volume
Discounted Price
Book
2019-01-01
8
70
Book
2022-01-01
5
75
Ice Cream
2020-12-01
10
30
I want to calculate how much money each Item saved each month due to promotion, so my output should be:
Item Name
Month
MoneySaved
I came up with the following structure:
select a.price as price_at_that_time
from TableA join TableB on a.ItemName = b.ItemName
where SalesDate between EffectiveDate and CancelledDate
select to_char(SalesDate, 'YYYY-MM') as month,
a.ItemName,
sum(price_at_that_time * SalesVolume - discounted price) as MoneySaved
from TableA join TableB ...
group by to_char(SalesDate, 'YYYY-MM'),
a.fitm
However, I'm not able to combine these two steps into one because of the many to many relationship and the dynamic nature of the price. Items may have different unit prices in different months, and even within a month, the unit price can be different.
I tired code like:
select to_char(SalesDate, 'YYYY-MM') as month,
a.ItemName,
sum(a.UnitPrice * SalesVolume - DiscountedPrice) as MoneySaved
from TableA join TableB on a.ItemName = b.ItemName
where a.UnitPrice in
(select a.UnitPrice from TableA join TableB on a.ItemName = b.ItemName where SalesDate between EffectiveDate and CancelledDate)
group by to_char(SalesDate, 'YYYY-MM'),
a.fitm
I know the code is wrong but that's closest I can get.
Almost exactly as you described it:
With TableA as (
select *
from (values
('Book', '2010-01-02', '2021-12-21', 10)
,('Book', '2022-01-01', '2028-01-01', 15)
,('Ice Cream', '2018-01-01','2028-01-01', 4)
) T(ItemName,EffectiveDate,CancelledDate, UnitPrice)
)
, TableB as (
select *
from (values
('Book' ,'2019-01-01' ,8 ,70)
,('Book' ,'2022-01-01' ,5 ,75)
,('Ice Cream' ,'2020-12-01' ,10 ,30))
T(ItemName,SalesDate,Volume,DiscountedPrice)
)
select
Sales.ItemName
, sum((RegPRices.UnitPrice * Sales.Volume) -
Sales.DiscountedPrice) as SavedAmount
from
TableB as Sales
inner join
TableA RegPrices
on RegPrices.ItemName=Sales.ItemName
and Sales.SalesDate
between RegPrices.EffectiveDate and RegPrices.CancelledDate
group by Sales.ItemName
This returns:
ItemName
SavedAmount
Book
10
Ice Cream
10
.

how to get ALL amount_left with ALL partyname checking that ALL partyname same as in another table with in MYSQL

Here is the First Table:
Sales:
Restaurant Teliabagh 80000
Restaurant Teliabagh 20000
SHRI BALAJI 50000
Bhola ji 50000
Ajay 50000
Second Tables:
Cash:
Restaurant Teliabagh 20000
Restaurant Teliabagh 20000
Ajay 25000
I was able to get money_left for one party AJAY only amount not with his name
here is the command:
SELECT (select sum(totalamount) from acc.sales where Partyname='Ajay')-(select sum(Amountrecevied) from acc.cash where Party_name='Ajay') as money_left;
and output as
money_left
25000
BUT my problem is that Here I want to display the ALL Partyname with Money_left After subtraction (totalamount) of the partynames in sales table form (amount received) in cash tables.
IT will be fine if it displays partyname and amount as NUll after subtraction them.
SELECT Partyname, sales.totalamount - COALESCE(cash.totalamount, 0) as money_left
FROM ( SELECT Partyname, SUM(totalamount) totalamount
FROM acc.sales
GROUP BY 1 ) sales
LEFT JOIN ( SELECT Partyname, SUM(totalamount) totalamount
FROM acc.cash
GROUP BY 1 ) cash USING (Partyname);
If there exists Partyname value which is present in cash but is absent in sales then additional subquery with UNION DISTINCT which gathers all Partyname values needed as a base.

SQL task if the product has no sales in the given month, then display 0 in this month

I have to build and SQL query which must do these things:
select all products from table "products" - satisfied
SUM all sales and forecast to the next 3 months - satisfied
check if the product has no one sale, then write "0" -> here is the problem, because I don't know how to do that..
My SQL query is here..
select product.name,
(select sum(amount)
from forecast
where forecast.product_id = product.id),
sum(sale.amount)
from product join
 sale
  on sale.product_id = product.id
where sale.outlook > -4
group by product.id
Here is the products table:
id name
1 milk
2 roll
3 ham
Table sale (same structure like forecast):
product_id outlook amount
1 -1 9
1 -2 13
1 -3 14
2 -1 88
2 -3 61
3 -1 33
3 -4 16
You can use left join to bring in the rows and coalesce() to get the 0 instead of NULL:
select p.name,
(select sum(f.amount)
from forecast f
where v.product_id = p.id),
coalesce(sum(s.amount), 0)
from product p left join
sale s
on sale.product_id = product.id and
sale.outlook > -4
group by p.id
Understand the requirement to be, show the sales per product and if no sale for a product show "0". Tables are named Products and Sale.
For this, "with" statements are useful and help understanding too:
With SalesSummary as
(
select product_id, sum(amount) as ProductSales
from Sale
Group by product_id
)
select a.ProductID, ISNULL(b.ProductSales,0) as Sales
from products a left join SalesSummary b on a.product_id=b.product_id

TSQL to return the dates of price changes from a price history

I have a price history table that has these 4 fields
Id
Date
Product
Price
There is a record for ever day for every product. I'm trying to write a query to return a record for the starting price of each product along with a record for each time the price changed.
I tried grouping by price but obviously this breaks when a price changes than at a later date changes back as it will only return 1 record.
I've written this query to generate sample data that I am trying to work off
CREATE TABLE #PriceHistory
(
[Id] INT IDENTITY,
[Date] DATETIME ,
[Product] NVARCHAR(30) ,
[Price] MONEY
)
INSERT INTO #PriceHistory([Date],[Product],[Price])
SELECT '20120101', 'Tesco', 1.99
UNION ALL
SELECT '20120102', 'Tesco', 1.97
UNION ALL
SELECT '20120103', 'Tesco', 1.97
UNION ALL
SELECT '20120105', 'Tesco', 1.99
UNION ALL
SELECT '20120104', 'Tesco', 1.99
UNION ALL
SELECT '20120106', 'Tesco', 1.99
UNION ALL
SELECT '20120101', 'BP', 1.99
UNION ALL
SELECT '20120102', 'BP', 1.01
UNION ALL
SELECT '20120103', 'BP', 1.99
SELECT * FROM #PriceHistory
DROP TABLE #PriceHistory
From that sample data the results I'm expecting should be
1 2012-01-01 Tesco 1.99
2 2012-01-02 Tesco 1.97
5 2012-01-04 Tesco 1.99
9 2012-02-11 BP 1.99
8 2012-02-20 BP 1.01
Any ideas on the best way to achieve this?
This query starts with a price, and attempts to find the previous record for the same product with the same price... if there's no record for the same product at the same price, it returns the record:
SELECT ph.*
FROM
#PriceHistory ph
LEFT JOIN #PriceHistory ph2 ON
ph.Product = ph2.Product
AND ph.Price = ph2.Price
AND ph2.Date = (
SELECT MAX(ph3.Date)
FROM #PriceHistory ph3
WHERE
ph3.Product = ph.Product
AND ph3.Date < ph.Date
)
WHERE ph2.ID IS NULL
Not very well tested, but try this:
SELECT DISTINCT P1.* FROM #PriceHistory P1
JOIN #PriceHistory P2 ON P1.Date = P2.Date + 1
OR P1.Date in (SELECT MIN(Date) From #PriceHistory P3)
WHERE P1.Product = P2.Product AND P1.Price <> P2.Price
Adjacent dates; same products; different price
[EDIT to capture the "first" date]