Calculate dynamic price with many to many relationship using SQL - mysql

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
.

Related

SQL : Show date when product was present in 6 months of data

I am pretty new to sql but have a problem I can't seem to find an answer to yet.
I need to find the month where the number of months a product in a certain segment showed up in a report is equal to 6. However, a product may not show up in sequential months, shows up multiple times a month, and some products have not beensold in 6 months yet.
I have a database with the following attributes:
Entry_Id Product_Name Sold_Date Payment_Amount Segment
======================================================================
112341 ABC Product 2017/12/20 10.50 Segment 1
112342 123 Product 2016/08/21 11.20 Segment 1
112343 ABC Product 2017/12/20 11.50 Segment 1
112344 123 Product 2017/08/21 11.20 Segment 1
112345 123 Product 2017/06/12 11.20 Segment 1
112346 123 Product 2016/06/21 11.20 Segment 1
112347 123 Product 2016/05/02 11.20 Segment 1
112348 123 Product 2015/04/01 11.20 Segment 1
112348 123 Product 2018/01/05 11.20 Segment 1
I would like to get something to the following effect
Product_Name Date where N = 6 segment
=================================================
ABC Product N/A Segment 1
123 Product 2018/01/05 Segment 1
The Day of month does not matter, just the month where the the number of months it has shown up in is equal to 6.
This is my first question and I will be as active as possible, please ask any clarifying questions.
Thank you!
Use GROUP BY and COUNT() to count the number of months that each product was sold in, and HAVING to filter the results.
SELECT t1.product_name, max_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date
FROM yourTable
GROUP BY product_name
HAVING COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) >= 6
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
The t2 subquery finds all the products that were sold in at least 6 different months. Joining that with the table finds the row with the last date, so its segment column can be selected.
If you want to include the products that weren't sold in at least 6 months, with N/A in that column, you can move the test out of the subquery.
SELECT product_name, IF(month_count >= 6, max_date, "N/A") AS last_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date, COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) as month_count
FROM yourTable
GROUP BY product_name
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
You can do this with a correlated subquery:
select t.*
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
);
Because of the count(distinct), this will show all records from the 6th month. Also, this is not particularly efficient, but it does use just standard SQL.
You can summarize this to one row per segment/product:
select t.segment, t.product, min(t.sold_date)
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
)
group by t.segment, t.product;

selecting top row of each repeating tuple according to some criteria (MYSQL)

I have an example table like this:
Month City Person
8 LHR ABC
10 BEIJING BCS
11 NY JJJ
11 VENICE hghg
11 VENICE KKK
12 NY aa
12 ORL abc
12 ORL bbc
So what I want to achieve is see the city in a specific month with the most number of people visiting
Like the output should be:
12 ORL
11 VENINCE
10 BEIJING
8 LHR
I have tried grouping it like
SELECT month, city , count(*) AS 'no of people visiting'
FROM table
GROUP BY month, city
This table does tell me which city and month location had how many people
visiting but I cannot extract the the top most month and city combination
with respect to a certain month.
Updated Query (with error)
SELECT *
FROM
( SELECT monthname(reservation.PickupDate), location.LocationName, COUNT(*) AS count
FROM reservation NATURAL JOIN location
WHERE reservation.pickupdate >= DATE_ADD(NOW(), INTERVAL - 3 MONTH)
GROUP BY month(reservation.PickupDate), location.LocationName) AS t1
WHERE NOT EXISTS (SELECT 1
FROM reservation R1 NATURAL JOIN location L1
WHERE monthname(R1.PickupDate) = monthname(t1.PickupDate)
GROUP BY month(R1.PickupDate), L1.LocationName)
Starting from your query, you just need to eliminate those rows having another city with more visitors on that month:
SELECT *
FROM
(SELECT `month`, city, count(*) AS cnt
FROM `table`
GROUP BY `month`, city) t1
WHERE NOT EXISTS(SELECT 1
FROM `table` t2
WHERE t2.`month` = t1.`month`
GROUP BY `month`, city
HAVING count(*) > t1.cnt)

Join tables and preform aggregation on each of them

I have the following tables:
table part_list:
part_number | description | type
100 blablabla blabla
table part_list_supplier:
part_id | artikel
100 100100
100 200100
and I have this query:
select part_list.part_number, part_list.description, part_list.type, group_concat(coalesce(part_list_supplier.artikel, "nothing")) as "artikel"
from part_list
left join part_list_supplier on (part_list.part_number = part_list_supplier.part_id)
group by part_list.part_number;
this is the result:
part_number | description | type | artikel
100 blablablabla blabla 100100,200100
but I want to show the total stock per partnumber behind it. table receipt:
Number | import
100 5
100 10
table sales:
Number | sold
100 5
this is my query for one table:
SELECT SUM(sold) AS sold
FROM sales WHERE number = '".$partnumber.”'
but I want to calculate the stock per number and that must be shown behind the other results.
the full result:
part_number | description | type | artikel | stock
100 blablablabla blabla 100100,200100 10
The stock should be 10 because the total number of imports is 15 (5 + 10) and the total number of sales is 5.
I broke this up into pieces to solve it. I started by writing two queries, one that counted total receipt and one that counted total sales:
SELECT r.number, SUM(r.import) AS totalIn
FROM receipt r
GROUP BY r.number;
SELECT s.number, SUM(s.sold) AS totalOut
FROM sales s
GROUP BY s.number;
Then, I used those as two subqueries of a join to get the stock:
SELECT r.number, totalIn - totalOut AS stock
FROM(
SELECT r.number, SUM(r.import) AS totalIn
FROM receipt r
GROUP BY r.number) r
JOIN(
SELECT s.number, SUM(s.sold) AS totalOut
FROM sales s
GROUP BY s.number) s ON s.number = r.number;
Once I verfied this gave the proper stock, I was able to include those subqueries into your original query to build this:
SELECT pl.part_number, pl.description, pl.type,
GROUP_CONCAT(COALESCE(pls.artikel, "Nothing.")) AS artikel,
r.totalIn - s.totalOut AS stock
FROM part_list pl
LEFT JOIN part_list_supplier pls ON pls.part_id = pl.part_number
JOIN(
SELECT number, SUM(import) AS totalIn
FROM receipt
GROUP BY number) r ON r.number = pl.part_number
JOIN(
SELECT number, SUM(sold) AS totalOut
FROM sales
GROUP BY number) s ON s.number = r.number
GROUP BY pl.part_number;
Here is an SQL Fiddle example.
I may not be understanding your question properly, but can't you just add sum(sales.sold) to your select statement and join the sales table? E.g.:
select part_list.part_number, part_list.description, part_list.type, group_concat(coalesce(part_list_supplier.artikel, "nothing")) as "artikel", sum(sales.sold)
from part_list
left join part_list_supplier on (part_list.part_number = part_list_supplier.part_id)
left join sales on (part_list.part_number = sales.number
group by part_list.part_number;

SQL can't figure out how to join correctly

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

JOINING two tables to fetch COUNT from one and SUM from the other

I am working on a project where users work on reports and enter details of their work in database. My database structure has two tables:
tbl_reports - this table contains all details of work performed
report_id user_id date country status
-----------------------------------------------------------------------
0001 abc 2014-05-04 USA checked
0002 abc 2014-05-04 USA checked
0003 abc 2014-05-05 India checked
0004 lmn 2014-05-04 USA checked
0005 lmn 2014-05-04 India checked
0006 xyz 2014-05-06 Taiwan checked
tbl_time - this table contains all details on time repoted by the users, date and country wise
id user_id date country time (hrs)
----------------------------------------------------
01 abc 2014-05-04 USA 4
02 abc 2014-05-05 India 2
03 lmn 2014-05-04 USA 3
04 lmn 2014-05-04 India 2
05 opq 2014-05-05 Belgium 4
As you can see users "abc and "lmn" have tracked all their tasks appropriately while user "xyz" has not tracked his time yet and user "opq" has tracked his time but has no records of reports he has worked on.
Now out of this I want to extract details of this team GROUPING BY "date" and "country" as below:
date country total_report_count total_time_count
-----------------------------------------------------------------------
2014-05-04 India 1 2
2014-05-04 USA 3 7
2014-05-05 Belgium 0 4
2014-05-05 India 1 2
2014-05-06 Taiwan 1 0
Which means irrespective of which user has tracked his reports or time, I need to generate team report for worked done in which country on which date , its counts and total time tracked.
Now I was able to find total_time_count report using below code:
CREATE VIEW vw_teamreport AS
SELECT
tb1.date , tb1.country,
SUM(tb1.time) AS total_time_count
FROM tbl_time tb1
LEFT JOIN tbl_reports tb2
ON tb2.report_id IS NULL
GROUP BY tb1.date, tb1.country
ORDER BY tb1.date, tb1.country;
Need help to complete the problem, and I am using MYSQL (In case if FULL JOIN is required, FULL JOIN keyword is not supported)
Because there's no FULL JOIN you'll need a query to pull out all the distinct date/country combinations from the UNION of these two tables. Or, you'll need some other query to generate the full list of dates and countries. Call this query A.
You need to write two separate aggregating queries. One will aggregate the hours by date and country and the other will aggregate the reports by date and country. Call these queries B and C.
Then you need to do
SELECT whatever, whatever
FROM (
/*query A*/
) AS a
LEFT JOIN (
/*query B*/
) AS b ON a.date=b.date AND a.country=b.country
LEFT JOIN (
/*query C*/
) AS c ON a.date=c.date AND a.country=c.country
This will produce a correctly summarized report with all the rows you need, and NULLs where there is missing summary data.
Edit
Sorry, forgot about the nested query view restriction. You'll need to create four views, one for each subquery and one for the join query. So it will be:
CREATE VIEW dates_countries AS
SELECT DISTINCT `date`, country FROM tbl_time
UNION
SELECT DISTINCT `date`, country FROM tbl_reports;
CREATE VIEW time_totals AS
SELECT `date`, country, SUM(time) AS tot
FROM tbl_time
GROUP BY `date`, country
CREATE VIEW report_totals AS
SELECT `date`, country, COUNT(*) AS tot
FROM tbl_reports
GROUP BY `date`, country
And finally this view.
CREATE VIEW team_report AS
SELECT a.`date`, a.country,
c.tot AS total_report_count,
b.tot AS total_time_count
FROM dates_countries AS a
LEFT JOIN time_totals AS b ON a.`date` = b.`date` AND a.country = b.country
LEFT JOIN repoorts_totals AS r ON a.`date` = r.`date` AND a.country = r.country;
You don't have much choice about this when you need a view.
you could do a divide it into two sub-queries with each section providing one of the reporting data like this,
CREATE VIEW vw_teamreport AS
SELECT tt.date, tt.country, t1.total_time_count, t2.total_report_count
FROM
(SELECT distinct tb1.date , tb1.country
FROM tbl_time tb1
UNION
SELECT tb1.date , tb1.country
FROM tbl_reports tb1
) tt
LEFT JOIN
(SELECT tb1.date , tb1.country, SUM(tb1.time) AS total_time_count
FROM tbl_time tb1
GROUP BY tb1.date, tb1.country) t1
ON tt.date = t1.date and tt.country = t1.country
LEFT JOIN
(SELECT tb1.date , tb1.country, COUNT(tb1.country) AS total_report_count
FROM tbl_reports tb1
GROUP BY tb1.date, tb1.country)t2
ON tt.date = t2.date and tt.country = t2.country
The first query provides the union for all time & country. The 2nd and the 3rd query provides the report data.