Complex subqueries wihtin CASE statement - mysql

I have this query that calculates the expected value of a receipt, based on the 'channel' of acquisition of the user, on the 'id_store' and on the 'country' of the user.
The resulting query has this columns.
Count | id_store | country | price_usd | channel | T2S | ExpectedRevenue | ExpectedRevenue_projections
(note: ExpectedRevenue = count * price_usd * T2S)
In this query I want to know the value of those receipts that where purchased on August and expire on September (these products have a trial period, and we calculate their value is based on the historical conversion rate (Trial 2 subscription a.k.a. T2S)).
The problem arises when we do not have historical data for some scenarios; either we started selling different with id_stores, new country, channel, or any combination of the previous ones and the T2S becomes as a 'null' value and column 'ExpectedRevenue' therefore has 'null' values' ---> That is why I have a 'CASE' function that makes T2S to take the value of '.28' whenever this happens and I only have to sum the column 'ExpectedRevenue_projections' in order to get the final result.
However, this '.28' is just a fixed value that is not accurate at all (we have products from 10 to 120$...).
That's why I'm asking dear stackoverflow contributors is to replace this '28%' for another T2S that in this case would be calculated historically according to 'country' and 'price_usd' intervals such as (0-15), (15.01-29.99), (30 - 49.99)... and so on.
(The reason to put intervals is that 'price_usd' is calculated based on the exchange rate at the moment of the creation so the same product purchase by a customer in foreign currency could be 49.99, 49.98 , 49.97... and T2S would come as 'null' if there is not historical data).
Thank you
SELECT
r.count AS count,
r.id_store AS id_store,
r.country AS country,
r.price_usd AS price_usd,
r.channel AS channel,
a.count / (a.count + u.count) AS T2S,
r.count * r.price_usd * (a.count / (a.count + u.count)) AS ExpectedRevenue,
CASE WHEN (a.count / (a.count + u.count)) is null then (r.count * r.price_usd * .28) else r.count * r.price_usd * (a.count / (a.count + u.count)) END AS ExpectedRevenue_projections
FROM
(
select count(distinct(r.user_id)) AS count, p.id_store, u.country, r.price_usd, channel
from receipts as r
inner join products as p on p.id=r.product_id
inner join customers as c on r.customer_id=c.id
inner join users as u on u.id=r.user_id
where
MONTH(DATE_ADD(DATE_ADD(r.original_subscription_at, INTERVAL p.trial_days DAY), INTERVAL p.trial_months MONTH)) = 9
and YEAR(original_subscription_at) = 2017
and MONTH(original_subscription_at) = 8
and YEAR(r.expiration_at) = 2017
and MONTH(r.expiration_at) = 9
and r.status='trial'
and p.platform not in ('manual')
and c.tester=0
group by 2,3,4,5
) AS r
LEFT JOIN
(
select count(distinct r.user_id) AS count, u.country, p.id_store, channel
from receipts as r
inner join products as p on p.id=r.product_id
inner join users as u on u.id=r.user_id
where year(r.created_at)=2017
AND r.status = 'active'
group by 2,3,4
) AS a
ON r.country = a.country AND r.id_store = a.id_store AND r.channel = a.channel
LEFT JOIN
(
select count(distinct r.user_id) AS count, u.country, p.id_store, channel
from receipts as r
inner join products as p on p.id=r.product_id
inner join users as u on u.id=r.user_id
where year(r.created_at)=2017
AND r.status = 'unpaid'
group by 2,3,4
) AS u
ON r.country = u.country AND r.id_store = u.id_store AND r.channel = u.channel

Related

How to use grouping by XXX, and using aggregate funtion correctly? WITH MIN VALUES

I am trying to fetch records from 4 tables by using joins. These tables are rates, carriers, depots and margin. I am able to get the records that needed from this 4 table, but when I am using a group by and aggregate function (MIN), my min sell column is correct, however, the other data such as carrierID, depotID and ratesID are different.
I have OriginType (OT) and DestinatioType (DT)and there are 2 cases for each; Depot and Door. So when I group them, I get four options as (services):
Depot To Depot
Depot To Door
Door To Depot
Door To Door
I am trying to fetch the min(rates) as 'Sell' for each of these service and display it. The Sell is calculated as:
autoRate table as A has a column Buy, carrier, OT, DT, Origin (Value: Canberra), Destination (Value: Melbourne) and Car (Value :4WD/Van)
Left Join with carriers as C with A.carrier = C.ID
C.Fuellevy column as Percentage ((C.FuelLevy * A.buy) + C.FuelLevy) as EQ1
((EQ1 * 10%) + EQ1) as EQ2
EQ2 price for each row with have a Margin table Percentage. For example, if the EQ2 value is 400, then It will look in the Margin table, find the range (low and high as 350(low) and 500(high)) and its percentage is 25%, so ((EQ2 * 25%)+ EQ2) gives the sell value.
I am not too sure how to upload my data and database table here, so I tried to explain here what I want.
the query I build is:-
Select Depo.*, DL.id as DepoID, DL.carrier as CarNo, DL.depotCity, DL.depoSuburb, min(Depo.Sell) as sellcost , Depo.OriginType as OT From (
Select Mar.*, M.MarginPer, round((eq2 * M.MarginPer) + eq2)as Sell From (
Select GST.* , EQ1 as 'FinalEQ1' , round((EQ1 * .10) + EQ1,2) as eq2 From (
Select A.ID as RateID, A.Origin, A.OriginState, A.Destination, A.DestinationState, A.Carrier as RateCarrier, A.Car as CarType, A.Buy as Buy, A.OriginType, A.DestinationType ,
C.ID as CarrierID, C.Carrier, C.FuelLevy , round((A.buy * C.FuelLevy) + A.Buy, 2) As EQ1,
CONCAT(A.OriginType, ' to ' ,A.DestinationType ) as service,
D.id as DepoID, D.carrier as CarNo, D.depotCity, D.depoSuburb
from carrier C
left join autorates A on A.carrier = C.ID
left join dList D on D.carrier = C.ID
where A.origin = 'Canberra' and A.destination = 'Melbourne' and A.car = '4WD/Van' AND D.carrier = A.carrier AND A.goodsAllowed = 0
AND C.Disabled = 0
AND D.depotCity = 'Canberra'
order by EQ1
) As GST
order by eq2
) As Mar
Left Join margin M on Mar.eq2 >= M.low and Mar.eq2 <= M.high
order by Sell
) As Depo
Left Join dList DL on DL.Carrier = Depo.RateCarrier
Where DL.depotCity = 'Melbourne'
group by OT
order by sellcost
Results before the Group by and MIN():-
As we see the sellcost value 412 and the carNo is 51 here
And in this screenshot, the carrierno has changed, but the the MIN value remains the same.
I Just managed the get query. This will Work as I have tested on my SQL-WB
SELECT DepoD.*, DL2.id, DL2.carrier AS a, DL2.depotCity
, DL2.depoSuburb AS 'PickupSub', MIN(Sell) AS sellcost
FROM (
SELECT Depo.*, DL.id AS DepoID, DL.carrier AS CarNo, DL.depotCity, DL.depoSuburb AS 'PickupSub'
, DL.depoSuburb AS 'DestSub', MIN(Sell) AS sellcost, Depo.OriginType AS OT
FROM (
SELECT Mar.*, M.MarginPer, MIN(round((eq2 * M.MarginPer) + eq2)) AS Sell
FROM (
SELECT GST.*, EQ1 AS 'FinalEQ1', MIN(round((EQ1 * .10) + EQ1, 2)) AS eq2
FROM (
SELECT A.ID AS RateID, A.Origin, A.OriginState, A.Destination, A.DestinationState
, A.Carrier AS RateCarrier, A.Car AS CarType, A.Buy AS Buy, A.OriginType
, A.DestinationType, C.ID AS CarrierID, C.Carrier, C.FuelLevy
, round((A.buy * C.FuelLevy) + A.Buy, 2) AS EQ1
, CONCAT(A.OriginType, ' to ', A.DestinationType ) AS service
FROM carrier C
LEFT JOIN autorates A ON A.carrier = C.ID
WHERE A.origin = 'Melbourne'
AND A.destination = 'Canberra'
AND A.car = '4WD/Van'
AND A.goodsAllowed = 0
AND C.Disabled = 0
ORDER BY EQ1 ASC
) AS GST
GROUP BY service, CarrierID
ORDER BY eq2
) AS Mar
LEFT JOIN margin M ON Mar.eq2 >= M.low AND Mar.eq2 <= M.high
GROUP BY service
ORDER BY Sell
) AS Depo
LEFT JOIN dList AS DL ON DL.Carrier = Depo.RateCarrier
WHERE DL.depotCity IN('Melbourne', 'Canberra')
GROUP BY carrier, service
ORDER BY sellcost
) AS DepoD
LEFT JOIN dList DL2 ON DL2.Carrier = DepoD.RateCarrier
WHERE DL2.depotCity IN('Melbourne', 'Canberra')
GROUP BY carrier, service
ORDER BY sellcost
NOTE: Not a final answer, but a query to test and see if this part works. If so, I'll provide the rest of the query.
If you break the problem down, I think what you have to do is to identify the lowest Sell for each combination of OriginType and DestinationType. Then join to the tables to get the matching rows.
The query below doesn't get all of the detail info, but it should get the rows with the lowest Sell value. If it works, then I can add to the query to get the other, matching data.
One aspect that question is the use of LEFT JOIN. That might include rows that do not match up with the ON clauses, essentially adding columns with null values. This might impact performance, and using just a JOIN might work better. If you test this and it works as stated below, run it again without the LEFT that is in front of each of the JOIN clauses. If you get the same results, let me know in the comments.
Give it a try and see if you get the four values for each of the combinations of OriginType and DestinationType. If it does, I'll update the answer with the rest of the query.
SELECT
A.OriginType,
A.DestinationType,
round(A.buy * (1.0 + C.FuelLevy), 2) AS EQ1,
round(round(A.buy * (1.0 + C.FuelLevy), 2) * 1.10, 2) AS eq2,
MIN(round((eq2 * (1.0 + M.MarginPer)))) AS Sell
FROM carrier C
LEFT JOIN autorates A
ON A.carrier = C.ID
LEFT JOIN dList D
ON D.carrier = A.carrier
LEFT JOIN margin M
ON round(round(A.buy * (1.0 + C.FuelLevy), 2) * 1.10, 2) BETWEEN M.low AND M.high
WHERE A.origin = 'Canberra'
AND A.destination = 'Melbourne'
AND A.car = '4WD/Van'
AND A.goodsAllowed = 0
AND C.Disabled = 0
AND D.depotCity = 'Canberra'
GROUP BY LOWER(A.OriginType), LOWER(A.DestinationType)
ORDER BY Sell

Get products without sales for last 7day

I have two tables products and user_sales. I need to get the products which have 0 sales for the last 7 days.
I tried some code which I found in stack overflow with the "HAVING" statement but I found out if I put a limit of 12 products than it doesnt works.
Can anoyone please help ?
table products
id | title | price | images | description
table user_sales
id | product_id | sale_date | user_owner
Well that was the simplified version of the tables. The actual query looks somehow like this.
SELECT `product_store`.`friendly_name` AS `store_friendly_name`,
`api_keys`.`key_data` AS `api_data`,
`ep`.*,
`supplier_store`.`icon` AS `supplier_store_icon`,
`supplier_store`.`internal_name` AS `supplier_store_internal_name`,
`supplier_store`.`friendly_name` AS `supplier_store_friendly_name`,
`supplier_store`.`amazon_type_product` AS
`supplier_store_amazon_type_product`,
`supplier_store`.`handler` AS `supplier_store_handler`,
`sp`.`extra_data` AS `sp_extra_data`,
`sp`.`id` AS `sp_id`,
`sp`.`remote_id` AS `sp_remote_id`,
`sp`.`url` AS `sp_url`,
`sp`.`price` AS `sp_price`,
`sp`.`stock` AS `sp_stock`,
`sp`.`picture` AS `sp_picture`,
`sp`.`store_id` AS `sp_store_id`,
Count(us.date) AS sale_date
FROM `products` `ep`
LEFT JOIN `stores` `product_store`
ON `ep`.`store_id` = `product_store`.`id`
LEFT JOIN `api_keys`
ON `api_keys`.`id` = `ep`.`link_key`
LEFT JOIN `products` `sp`
ON `ep`.`linked_to` = `sp`.`id`
LEFT JOIN `stores` `supplier_store`
ON `supplier_store`.`id` = `sp`.`store_id`
RIGHT JOIN `user_sales` `us`
ON `ep`.`remote_id` = `us`.`remote_id`
WHERE `ep`.`user_owner` = '3992'
AND `ep`.`expired` = 0
AND `us`.`user_id` = '3992'
AND us.date > "2019-02-09 14:21:34"
AND us.date < "2019-05-10 14:21:34"
AND `ep`.`store_id` = 3
GROUP BY `ep`.`id`
HAVING `sale_date` < 1
ORDER BY `ep`.`id` DESC
LIMIT 15
You want to ensure that no sales exist for the product in the last seven days. Use NOT EXISTS for that.
From your query I take it that you want to restrict this to products belonging to user_owner 3992 and to sales of the same user.
WHERE ep.user_owner = 3992
AND ep.expired = 0
AND ep.store_id = 3
AND NOT EXISTS
(
SELECT *
FROM user_sales us
WHERE us.user_id = ep.user_owner
AND us.date > current_date - interval 7 day
)
The complete query:
SELECT product_store.friendly_name AS store_friendly_name,
api_keys.key_data AS api_data,
ep.*,
supplier_store.icon AS supplier_store_icon,
supplier_store.internal_name AS supplier_store_internal_name,
supplier_store.friendly_name AS supplier_store_friendly_name,
supplier_store.amazon_type_product AS
supplier_store_amazon_type_product,
supplier_store.handler AS supplier_store_handler,
sp.extra_data AS sp_extra_data,
sp.id AS sp_id,
sp.remote_id AS sp_remote_id,
sp.url AS sp_url,
sp.price AS sp_price,
sp.stock AS sp_stock,
sp.picture AS sp_picture,
sp.store_id AS sp_store_id
FROM products ep
LEFT JOIN stores product_store ON ep.store_id = product_store.id
LEFT JOIN api_keys ON api_keys.id = ep.link_key
LEFT JOIN products sp ON ep.linked_to = sp.id
LEFT JOIN stores supplier_store ON supplier_store.id = sp.store_id
WHERE ep.user_owner = 3992
AND ep.expired = 0
AND ep.store_id = 3
AND NOT EXISTS
(
SELECT *
FROM user_sales us
WHERE us.user_id = ep.user_owner
AND us.date > current_date - interval 7 day
)
ORDER BY ep.id DESC;

How to add the tax and then multiply with the price in mysql

I am trying to add the tax and multiply the result with a total price.
Here are the tables
"Charge" Table
Charge_Type
Tax_type
charge type tax list
I want to calculate the tax and the multiply with the amount for each charge_id
I tried attempting this way:
SELECT `charge_id` AS "Charge ID", SUM( tt.percentage ) AS "Tax"
FROM charge c, charge_type ct, tax_type tt, charge_type_tax_list cttl
WHERE tt.tax_type_id = cttl.tax_type_id
GROUP BY c.charge_type_id, c.charge_id
LIMIT 0 , 30
You are using a join syntax that was made redundant more than twenty years ago. Whichever book or class or tutorial has been teaching you this outdated syntax, quit it. This syntax is prone to errors, as your own query plainly shows. Your query translated to proper joins is:
SELECT `charge_id` AS "Charge ID", SUM( tt.percentage ) AS "Tax"
FROM charge c
CROSS JOIN charge_type ct
CROSS JOIN tax_type tt
INNER JOIN charge_type_tax_list cttl ON tt.tax_type_id = cttl.tax_type_id
GROUP BY c.charge_type_id, c.charge_id
LIMIT 0 , 30
You are combining all charge records with all charge_type records and all tax_type records. 6 x 3 x 3 = 54 records in your example. This makes no sense. You would want to join only related records in the first place.
What you really want to do is join charges with their tax sum:
select
c.charge_id,
tax.tax_sum,
c.amount,
c.amount * (1 + coalesce(tax.tax_sum, 0)) as taxed_amount
from charge c
left join
(
select
cttl.charge_type_id,
sum(tt.percentage) as tax_sum
from charge_type_tax_list cttl
join tax_type tt on tt.tax_type_id = cttl.tax_type_id
group by cttl.charge_type_id
) tax on tax.charge_type_id = c.charge_type_id;
You could do an INNER JOIN between the tables and calculate the corresponding percentage's grouped by charge id. The query below should do the trick:
SELECT charge.charge_type_id, (SUM(percentage) + 1) * charge.amount AS total_tax
FROM charge INNER JOIN charge_type ON charge.charge_type_id = charge_type.charge_type_id
INNER JOIN charge_type_tax_list ON charge_type.id = charge_type_tax_list.charge_type_id
INNER JOIN tax_type ON charge_type_tax_list.tax_type_id = tax_type.tax_type_id GROUP BY charges.id;

Create One Result set from 3 different Queries

I am trying to create a result set using three queries. I have three tables an inventory table, a form order table, and a form order detail table. I need to be able to input a date range and get how many forms were ordered, how many are in current inventory, and how many were destroyed based on if they contain a destruction date. Ultimately i want a result set that shows:
InventoryId, FormDescription, Product, Ordered, Shipped, Destroyed, Total ending
What would be the best way to get that result set using these queries?
These are my three Queries
SELECT FOD.InventoryId, SUM(FOD.FormOrderAmount) as totalOrdered, FOD.FormShippedAmount FROM tblFormOrder FMO
JOIN tblFormOrderDetails FOD ON FOD.FormOrderId = FMO.FormOrderId
WHERE FMO.OrderDateTime BETWEEN '20110101' and '20120101'
AND FMO.OrderStatus IN ('S')
GROUP BY FOD.InventoryId, FOD.FormShippedAmount -- total shipped by date and inventoryid
SELECT INV.InventoryId, SUM(INV.CurrentAmount) as currentAmount, SUM(INV.OrderAmount) as OrderAmount,
(SUM(INV.OrderAmount) - SUM(INV.CurrentAmount)) as InventoryUsed
FROM tblInventory INV
where INV.CreatedOn
BETWEEN '20110101' and '20120101'
GROUP BY INV.InventoryId -- current amount based off ordered and used
select INV.InventoryId, count(*) as total
, FMO.OrderDateTime as OrderDate, Inv.FormNo, INV.FormDescription, INV.Product
from [tblinventory] INV
join tblformorderdetails FOD ON FOD.InventoryId = inv.InventoryId
join tblformorder FMO on FMO.FormOrderId = FOD.FormOrderId
where INV.DestructionDate
BETWEEN '20110101' and '20120101'
group by
FMO.OrderDateTime,
Inv.FormNo, INV.FormDescription, INV.Product, INV.InventoryId -- using count to find how many destroyed if they have a destruction date
If you want an inner join you can do this
SELECT *
FROM (...) AS Q1, (...) AS Q2, (...) AS Q3
WHERE Q1.InventoryID = Q2.InventoryID AND Q2.InventoryID = Q3.InventoryID
This will only give you the InventoryIDs that are in all 3 queries.
You probably don't want this you probably want all inventory IDs... so you do this.
SELECT *
FROM (SELECT DISTINCT InventoryID FROM tblFormOrderDetails
UNION ALL
SELECT DISTINCT InventoryID FROM tblInventory) I
LEFT JOIN (...) Q1 ON I.InventoryID = Q1.InventoryId
LEFT JOIN (...) Q2 ON I.InventoryID = Q2.InventoryId
LEFT JOIN (...) Q3 ON I.InventoryID = Q3.InventoryId
Make each query a subquery that you join together.
SELECT *
FROM (
SELECT FOD.InventoryId, SUM(FOD.FormOrderAmount) as totalOrdered, FOD.FormShippedAmount FROM tblFormOrder FMO
JOIN tblFormOrderDetails FOD ON FOD.FormOrderId = FMO.FormOrderId
WHERE FMO.OrderDateTime BETWEEN '20110101' and '20120101'
AND FMO.OrderStatus IN ('S')
GROUP BY FOD.InventoryId, FOD.FormShippedAmount -- total shipped by date and inventoryid
) AS q1
LEFT JOIN (
SELECT INV.InventoryId, SUM(INV.CurrentAmount) as currentAmount, SUM(INV.OrderAmount) as OrderAmount,
(SUM(INV.OrderAmount) - SUM(INV.CurrentAmount)) as InventoryUsed
FROM tblInventory INV
where INV.CreatedOn
BETWEEN '20110101' and '20120101'
GROUP BY INV.InventoryId -- current amount based off ordered and used
) AS q2 ON q1.InventoryId = q2.InventoryId
LEFT JOIN (
select INV.InventoryId, count(*) as total
, FMO.OrderDateTime as OrderDate, Inv.FormNo, INV.FormDescription, INV.Product
from [tblinventory] INV
join tblformorderdetails FOD ON FOD.InventoryId = inv.InventoryId
join tblformorder FMO on FMO.FormOrderId = FOD.FormOrderId
where INV.DestructionDate
BETWEEN '20110101' and '20120101'
group by
FMO.OrderDateTime,
Inv.FormNo, INV.FormDescription, INV.Product, INV.InventoryId -- using count to find how many destroyed if they have a destruction date
) AS q3 ON q1.InventoryId = q3.InventoryId

EXCEPT query to get info year to date

I have a table which holds account transactions for each account, I’ve written a query that tells me when an account has paid one month, but not the next based on the Parameter MonthID (from cal lookup table)
DECLARE #MonthID INT
SET #MonthID = 128
SELECT AccountNo, Emp,
FROM Table1 AS t INNER JOIN
tblcal AS cl ON t.date = cl.Date
WHERE cl.MonthID = #MonthID
EXCEPT
SELECT AccountNo, Emp,
FROM Table1 AS t INNER JOIN
tblcal AS cl ON t.date = cl.Date
WHERE cl.MonthID = #MonthID+1
Query works great for getting the records for the specified month however I need to look at getting this for every month of the year and have no idea how to factor that requirement in?
Is a cursor the best way for doing this? I’ve read a lot of negative’s about cursors?
At the moment it returns data like:
MonthID | SkippedPay
128 | 12445
(as i perform an count on the account numbers)
What i need is to get it for the last 12 months, so the same as above but with 12 months of data, this is what is making me think of a cursor to go through each month and populate a table?
MonthID | SkippedPay
128 | 12445
129 | 1256
What about the following approach:
You left join the dataset of one month against the data for the following month and return the rows in which there is no corresponding data in the following month.
DECLARE #MonthID INT
SET #MonthID = 128
SELECT MonthA.AccountNo, MonthA.Emp FROM
(SELECT AccountNo, Emp, cl.MonthID as [Month]
FROM Table1 AS t INNER JOIN
tblcal AS cl ON t.date = cl.Date
WHERE cl.MonthID >= #MonthID-12 and cl.MonthID <= #MonthID) AS MonthA
LEFT JOIN
(SELECT AccountNo, Emp, cl.MonthID as [Month]
FROM Table1 AS t INNER JOIN
tblcal AS cl ON t.date = cl.Date
WHERE cl.MonthID >= #MonthID-11 and and cl.MonthID <= #MonthID+1) AS MonthB on MonthA.AccountNo = MonthB.AccountNo AND MonthA.Emp = MonthB.Emp AND (MonthA.[Month]+1) = Monthb.[Month]
WHERE MonthB.AccountNo IS NULL
p.s.: You got a syntax error in your query: "Emp," with no following field.