How to query scalable prices in MySQL - mysql

Dear stack overflow community,
This is my first post so please bear with me :)
I need to solve a SQL problem for a friend of mine.
He is running a web shop and wants to create a finance report.
The application he is using provides such functionality using an interface were MySQL queries can be executed.
I already created most of the report with my (limited) SQL knowledge, however I am struggeling to solve the last problem.
The goal of the report is to UNION and JOIN several tables to get an overview of all commissions, invoices and proposals with their respective articles and prices.
So what I did so far:
I did a union of commissions, invoices, proposals (lets call them receipt) and joined them with articles and prices.
That worked very well.
However, here is my problem:
An article could have multiple prices depending on the date of the respective receipt.
So I end up with more rows in my table as there should be.
There is a "valid_until" field within the prices table, which I have to use for the filter ... but how?
Example:
receipt_id
receipt_date
article_id
article_price
valid_until
price_id
209986-1
2020-09-10
2925
13
2020-12-06
1
209986-1
2020-09-10
2931
13
2020-09-09
2
209986-1
2020-09-10
2937
12,6
2020-09-12
3
209986-1
2020-09-10
2980
12,32
0000-00-00
4
In this case, only price_id 3 is valid as the receipt_date is "2020-09-10".
My Query (with limited SQL knowledge):
SELECT *
FROM (SELECT * FROM commissions UNION ALL SELECT * FROM invoices UNION ALL SELECT * FROM proposals) AS receipt
LEFT JOIN article ON receipt.article = article.id
LEFT JOIN prices ON article.id = prices.artikel
WHERE receipt.date <= IF(prices.valid_until = '0000-00-00', Date('3000-01-01'), prices.valid_until)
With that query I still get 3 results (price_id 4, 3 and 1).
I managed to identify the valid price using DATEDIFF(), ORDER BY and LIMIT, however MySQL does not allow to use LIMIT in sub-queries :(
Any help would be much appreciated.
KR,
Wlad

Related

Left Join and Sum

I have two tables, one that lists grants/loans and one that lists individual expenditures. They share an ID column as each expenditure is assigned to a specific grant or loan. I'm trying to use LEFT JOIN to sum the expenditures for all the loans combined, but not the grants.
Here's where I'm at:
SELECT SUM(expenses.total_amt) AS total
FROM expenses WHERE loans_grants.grant_loan_type = 'Loan'
LEFT JOIN loans_grants
ON expenses.grant_loan_id = loans_grants.internal_id;
Any tips much appreciated!
Edit: thanks all, and apologies for the half baked question, it was late and I was in the weeds.
Here's the basic structures:
expenses:
expenses table structure
loans_grants:
loans_grants table structure
I've updated the code based on #jwood74's answer to this:
SELECT l.internal_id, SUM(e.total_amt) amount
FROM loans_grants l
LEFT JOIN expenses e ON e.grant_loan_id = l.internal_id
WHERE grant_loan_type = 'Loan'
group by l.internal_id
which produces this:
internal id
amount
1
3234
4
null
5
7625
7
null
9
null
Please excuse my noviceness, but I'm trying to sum up all expenses for loans, so I'd like to return 3234 + 7625, rather than summing expenses for each loan separately. Thanks for your help!
If you are looking for a SINGLE ROW RETURNED, you do not need to do a group by anything... just the SUM() of what you are looking for.
Second, do not post pictures of your sample data and table structures. Edit your original post and type the values in, even if you copy/paste the data and format it for readability (via Ctrl+K, or the curly brackets {} icon above post editing header area).
In this case, your tables
Loan_Grants table
Internal_id grant_loan_type
1 Loan
2 Grant
3 Grant
4 Loan
5 Loan
6 Grant
7 Loan
8 Grant
9 Loan
Expenses Table
total_amt grant_loan_id
2000 1
245 5
4500 5
2200 5
445 5
185 5
1234 1
50 5
Starting with your Loan_Grants table filtered on just your 'Loan' records
select
sum( e.total_amt ) totalExpenses
from
loan_grants lg
JOIN expenses e
on lg.internal_id = e.grant_loan_id
where
lg.grant_loan_type = 'Loan'
You dont want a left-join unless you explicitly want to see ALL "Loan" entries, even if they have no expenses yet recorded. By doing a regular (inner) JOIN, it means there MUST be a record in the expenses table. Again, based on your needs. If you have 10,000 loans and only 247 loans have expenses, do you want to see all 10,000 or just the 247 and what their totals are. Since you are summarizing to a single return record, JOIN is your best choice here.
For future, ALWAYS try to apply a table.column or alias.column to all your fields so anyone assisting does not have to guess which table the column comes from.
Without knowing the exact format of the two tables, it's a bit hard. But here would be the general idea-
select
l.id,
sum(e.amount) amount
from loans_grants l
left join expenses e on e.grant_loan_id = l.internal_id
where grant_loan_type = 'Loan'
group by l.id

How to combine the data in 2 table, form the aggregation without duplicating the record by left join

Now I am working with SQL files and have a question:
I would like to review the effect of the promotion campaign with the data in the sql file. In the SQL file there are 2 tables, web traffic and promotion campaign
The web traffic table, let's say table web are as follows
visitor_id purchase date traffic_source campaign_name country purchase_value
1 1/1/2018 Search promotion101 US 100
2 2/1/2018 Direct voucher02 UK 110
3 2/1/2018 Search buyme01 US 50
4 3/1/2018 Banner Example01 DE 130
.. ....... ... ... .. ...
And in the second table I have the campaign information, let's say table promotion
Promotion_date campaign_name num_delivered promotion_fee
1/12/2017 promotion101 50 30
2/12/2017 promotion101 30 20
2/12/2017 voucher02 40 10
3/12/2017 Example01 70 30
... ... ... ...
In this case, I tried to use the left join to merge the table first but the record duplicated
Select
web.campaign_name,
sum(web.promotion_fee),
sum(promotion.purchase_value)
FROM
web LEFT JOIN promotion
ON web.campaign_name = promotion.campaign_name
GROUP BY
1
However, it doesn't work because the left join simply duplicate the record...
In this case, If I want to formulate the table like this:
Campaign_name Traffic_source Total_Customer Total_purchase_value Total expenditure
promotion101 Search 1000 2000 1500
Example01 Banner 2000 3750 3000
Is it possible to do so? If yes then How can I make it?
Many thanks for your help in advance!
You may peform the aggregations of each table in separate subqueries:
SELECT
w.campaign_name,
w.purchase_value AS Total_purchase_value,
COALESCE(p.promotion_fee, 0) AS Total_expenditure
FROM
(
SELECT campaign_name, SUM(purchase_value) AS purchase_value
FROM web
GROUP BY campaign_name
) w
LEFT JOIN
(
SELECT campaign_name, SUM(promotion_fee) AS promotion_fee
FROM promotion
GROUP BY campaign_name
) p
ON w.campaign_name = p.campaign_name;
A critical assumption I have made here is that the web table contains data for all campaigns. If not, then you might have to join to a third table containing all campaigns which happened. Actually, arguably such a table should already exist.

Joining and selecting multiple tables and creating new column names

I have very limited experience with MySQL past standard queries, but when it comes to joins and relations between multiple tables I have a bit of an issue.
I've been tasked with creating a job that will pull a few values from a mysql database every 15 minutes but the info it needs to display is pulled from multiple tables.
I have worked with it for a while to figure out the relationships between everything for the phone system and I have discovered how I need to pull everything out but I'm trying to find the right way to create the job to do the joins.
I'm thinking of creating a new table for the info I need, with columns named as:
Extension | Total Talk Time | Total Calls | Outbound Calls | Inbound Calls | Missed Calls
I know that I need to start with the extension ID from my 'user' table and match it with 'extensionID' in my 'callSession'. There may be multiple instances of each extensionID but each instance creates a new 'UniqueCallID'.
The 'UniqueCallID' field then matches to 'UniqueCallID' in my 'CallSum' table. At that point, I just need to be able to say "For each 'uniqueCallID' that is associated with the same 'extensionID', get the sum of all instances in each column or a count of those instances".
Here is an example of what I need it to do:
callSession Table
UniqueCallID | extensionID |
----------------------------
A 123
B 123
C 123
callSum table
UniqueCallID | Duration | Answered |
------------------------------------
A 10 1
B 5 1
C 15 0
newReport table
Extension | Total Talk Time | Total Calls | Missed Calls
--------------------------------------------------------
123 30 3 1
Hopefully that conveys my idea properly.
If I create a table to hold these values, I need to know how I would select, join and insert those things based on that diagram but I'm unable to construct the right query/statement.
You simply JOIN the two tables, and do a group by on the extensionID. Also, add formulas to summarize and gather the info.
SELECT
`extensionID` AS `Extension`,
SUM(`Duration`) AS `Total Talk Time`,
COUNT(DISTINCT `UniqueCallID`) as `Total Calls`,
SUM(IF(`Answered` = 1,0,1)) AS `Missed Calls`
FROM `callSession` a
JOIN `callSum` b
ON a.`UniqueCallID` = b.`UniqueCallID`
GROUP BY a.`extensionID`
ORDER BY a.`extensionID`
You can use a join and group by
select
a.extensionID
, sum(b.Duration) as Total_Talk_Time
, count(b.Answered) as Total_Calls
, count(b.Answered) -sum(b.Answered) as Missed_calls
from callSession as a
inner join callSum as b on a.UniqueCallID = b.UniqueCallID
group by a.extensionID
This should do the trick. What you are being asked to do is to aggregate the number of and duration of calls. Unless explicitly requested, you do not need to create a new table to do this. The right combination of JOINs and AGGREGATEs will get the information you need. This should be pretty straightforward... the only semi-interesting part is calculating the number of missed calls, which is accomplished here using a "CASE" statement as a conditional check on whether each call was answered or not.
Pardon my syntax... My experience is with SQL Server.
SELECT CS.Extension, SUM(CA.Duration) [Total Talk Time], COUNT(CS.UniqueCallID) [Total Calls], SUM(CASE CS.Answered WHEN '0' THEN SELECT 1 ELSE SELECT 0 END CASE) [Missed Calls]
FROM callSession CS
INNER JOIN callSum CA ON CA.UniqueCallID = CS.UniqueCallID
GROUP BY CS.Extension

How to query the most profitable item in a specific year in MySQL

I have a practice problem where I am to write a query to find the top most 15 percent profitable products in the year 2005 from a database. The database does NOT have attributes like "Saleprice, or Purchaseprice". It has tables like PUrchaseProductDetails or SalesOrderDetails, and other stuff with Unitprice, orderquantity, ProdID, LIstPrice, ActualCost, StandardPrice, etc as attributes. I am confused as to which one I should use and how to come up with a formula. I tried to write a query, but got infinitely running results.
SELECT A.ProdID, B.ProdID, A.Unitprice - (B.Unitprice * orderquantity) Profit
FROM SalesOrderDetails A join PurchaseOrderD B
ON A.ProdID = B.ProdID
WHERE year(DateOrdered) = 2005
Group by A.ProdID
I have spent hours on these type of questions and my brain is at a dead end right now. If someone can please direct me to do it the right way, it would really help me out.
SELECT sale.ProdID, sum(sale.Unitprice - buy.Unitprice) * sale.Qty AS profit
FROM SalesOrderDetails AS sale
JOIN PurchaseOrderD AS buy ON ...
WHERE year(...) = 2005
GROUP BY 1
ORDER BY 2 DESC
LIMIT 15

Mysql: Adding product restricted shipping options to cart

I have a custom shop, and I need to redo the shipping. However, that is sometimes later, and in the meantime, I need to add a shipping option for when a cart only contains a certain range of products.
SO there is a ship_method table
id menuname name zone maxweight
1 UK Standard ukfirst 1 2000
2 UK Economy uksecond 1 750
3 Worldwide Air world_air 4 2000
To this I have added another column prod_restrict which is 0 for the existing ones, and 1 for the restricted ones, and a new table called ship_prod_restrict which contains two columns, ship_method_id and item_id, listing what products are allowed in a shipping category.
So all I need to do is look in my transactions, and for each cart, just check which shipping methods are either prod_restrict of 0 or have 1 and have no products in the cart that aren't in the restriction table.
Unfortunately it seems that because you can't values from an outer query to an inner one, I can't find a neat way of doing it. (edited to show the full query due to comments below)
select ship_method.* from ship_method, ship_prod_restrict where
ship_method.`zone` = 1 and prod_restrict='0' or
(
prod_restrict='1'
and ship_method.id = ship_prod_restrict.ship_method_id
and (
select count(*) from (
select transactions.item from transactions
LEFT JOIN ship_prod_restrict
on ship_prod_restrict.item_id = transactions.item
and ship_prod_restrict.ship_method_id=XXXXX
where transactions.session='shoppingcartsessionid'
and item_id is null
) as non_permitted_items < 1 )
group by ship_method.id
gives you a list of whether the section matches or not, and works as an inner query but I can't get that ship_method_id in there (at XXXXX).
Is there a simple way of doing this, or am I going about it the wrong way? I can't currently change the primary shipping table, as this is already in place for now, but the other bits can change. I could also do it within PHP but you know, that seems like cheating!
Not sure how the count is important, but this might be a bit lighter - hard to tell without a full table schema dump:
SELECT COUNT(t.item) FROM transactions t
INNER JOIN ship_prod_restrict r
ON r.item_id = t.item
WHERE t.session = 'foo'
AND r.ship_method_id IN (**restricted, id's, here**)