Our db stores customer orders in two tables: customerorders and customerorderlines.
Amongst other fields, there is a customerorders.type field which determines (being = 1 or = 2)
if that customerorder is an invoice or a creditnote.
We currently have a report that lists amount of units sold, profits etc but with its current query, only displays total units sold over the period, ie, it doesnt subtract the number of credits, if any. Here is its sql and an example of the results it generates:
(apologies for the massive query that follows :))
SELECT l.name AS locationname
, sr.name AS salesrepname
, ct.name AS customertypename
, c.name AS customername
, c.id AS customer_id
, c.code
, s.name AS suppliername
, p.description AS productname
, p.id AS product_id
, p.unitofmeasure
, SUM(col.vatableprice) AS totalsales
, SUM(col.vatprice) AS vat
, SUM(col.quantity) AS totalitems
, SUM(col.quantity * col.costprice) AS totalsalecost
, SUM(col.vatableprice) - SUM(col.quantity * col.costprice) AS totalprofit
,(
SELECT SUM(col2.vatableprice) AS totalsales
FROM customerorders AS co2
LEFT JOIN customerorderlines AS col2 ON col2.customerorder_id = co2.id
LEFT JOIN customers AS c2 ON c2.id = co2.customer_id
LEFT JOIN locations AS l2 ON l2.id = c2.location_id
LEFT JOIN customertypes AS ct2 ON ct2.id = c2.customertype_id
LEFT JOIN salesreps AS sr2 ON sr2.id = c2.salesrep_id
LEFT JOIN products AS p2 ON p2.id = col2.product_id
LEFT JOIN suppliers AS s2 ON s2.id = p2.supplier_id
WHERE c.salesrep_id = c2.salesrep_id
AND co2.type = 2 AND p2.supplier_id = 179
AND co2.orderdate >= '2010-01-01 00:00:00'
AND co2.orderdate <= '2010-02-01 23:59:59'
) AS credits
,(
SELECT SUM(col2.vatprice) AS totalvat FROM customerorders AS co2
LEFT JOIN customerorderlines AS col2 ON col2.customerorder_id = co2.id
LEFT JOIN customers AS c2 ON c2.id = co2.customer_id
LEFT JOIN locations AS l2 ON l2.id = c2.location_id
LEFT JOIN customertypes AS ct2 ON ct2.id = c2.customertype_id
LEFT JOIN salesreps AS sr2 ON sr2.id = c2.salesrep_id
LEFT JOIN products AS p2 ON p2.id = col2.product_id
LEFT JOIN suppliers AS s2 ON s2.id = p2.supplier_id
WHERE c.salesrep_id = c2.salesrep_id
AND co2.type = 2
AND p2.supplier_id = 179
AND co2.orderdate >= '2010-01-01 00:00:00'
AND co2.orderdate <= '2010-02-01 23:59:59'
) AS creditsvat
FROM customerorders AS co
LEFT JOIN customerorderlines AS col ON col.customerorder_id = co.id
LEFT JOIN customers AS c ON c.id = co.customer_id
LEFT JOIN locations AS l ON l.id = c.location_id
LEFT JOIN customertypes AS ct ON ct.id = c.customertype_id
LEFT JOIN salesreps AS sr ON sr.id = c.salesrep_id
LEFT JOIN products AS p ON p.id = col.product_id
LEFT JOIN suppliers AS s ON s.id = p.supplier_id
WHERE co.status_v = 5
AND co.type = 1
AND p.supplier_id = 179
AND co.orderdate >= '2010-01-01 00:00:00' AND co.orderdate <= '2010-02-01 23:59:59'
GROUP BY c.salesrep_id
Which, in this case (grouping etc is determined by application code) generates a 'per sales rep' report:
Rep | TotalItems | SalesValue| CostOfSales | Profit | VAT | Credits | Credits(VAT)
Rep1| 937 | £5796.49 | £3606.49 | £2190.00 | £1013.73 | £220.12 | £38.57
Rep2| 1905 | £11695.09 | £7314.95 | £4380.14 | £2045.32 | £268.85 | £47.00
Rep3| 1074 | £6346.61 | £3950.53 | £2396.08 | £1109.76 | £54.89 | £9.57
Rep4| 2687 | £16129.42 | £10171.65 | £5957.77 | £2820.46 | £839.15 | £146.78
So, the problem lies in that TotalItems is the absolute number of items sold (all customerorders of type = 1). The credits field shows total cost of items in the period of type = 2, ie returned. TotalItems should have the qty of credits deducted from it so at a glance it can be seen what has actually been sold and of course all the other fields need their Credits counterpart deducting from each other too so that they reflect the correct amounts for items sold.
At first I thought this would be as simple modification to the existing query, but then noticed that i could not reference subquery aliases in the select so I rewrote the whole query using JOIN (SELECT ....) AS sales / JOIN (SELECT .....) AS credits so I could then reference sales.qty and credits.qty from the SELECT at the top of the query but then that didnt scale at all unless you were doing little queries.
This is how far I got:
(Yes, Im querying for different things here... this would be essentially the simplest form of the query: sales / credits for a single product)
SELECT sr.name AS salesrepsname
,l.name AS locationname
,sup.name AS suppliername
,p.description AS productname
,sales.qty AS sold
,credits.qty AS credits
,sales.qty - credits.qty AS actualsold
FROM
customerorders co
LEFT JOIN customerorderlines col ON col.customerorder_id = co.id
LEFT JOIN customers c ON c.id = co.customer_id
LEFT JOIN products p ON p.id = col.product_id
LEFT JOIN salesreps sr ON sr.id = c.salesrep_id
LEFT JOIN locations l ON l.id = c.location_id
LEFT JOIN suppliers sup ON sup.id = p.supplier_id
JOIN (SELECT SUM(col.quantity) AS qty,
SUM(col.vatableprice) AS total FROM customerorderlines col
LEFT JOIN customerorders co ON co.id = col.customerorder_id
WHERE col.product_id = 27642 AND co.type = 1)
AS sales
JOIN (SELECT SUM(col2.quantity) AS qty FROM customerorderlines col2
LEFT JOIN customerorders co2 ON co2.id = col2.customerorder_id
WHERE col2.product_id = 27642 AND co2.type = 2) AS credits
WHERE col.product_id = 27642
GROUP BY c.salesrep_id
So I have to admit Im a bit stuck, not being very knowledgable with mysql at all.
Any suggestions are very welcome and please feel free to point me towards any literature on advanced subquerying and joins that I should be reading.
Cheers!
I think the trick you may be looking for is along these lines. In any given query/subquery you want to fix, use some SQL like this, selecting all the rows, regardless of whether it's an invoice or a creditnote, and summing a value that's either positive or negative depending on which way the order/money is going:
SELECT
SUM(CASE WHEN co.type = 1 THEN col.quantity ELSE -col.quantity END)
FROM
...
Get it? You grab all your order lines back, but when you do the sum, you're adding up positive credits and negative debits, to get the real total in one operation.
Related
I have two tiers. One tier is for customers who earned at least 1 point, another one is for customers who has at least 1000 points.
I want to get records of customers who has points >= 950 and < 1000. I am planning to move them to the second tier as a bonus. I imported customers lately and they were not automatically added in the first tier even when they have at least 1 point.
When I do my query, it doesn't include the ones that I imported because they dont exist in the customer_vip_tiers table.
tier 1 id = 1733
tier 2 id = 1734
select
c.id,
c.email,
sum(p.reward_points) as total_points,
vt.name,
cvt.tier_id
from vip_tiers_settings vts
left join customers c on c.merchant_id = vts.merchant_id
left join customers_vip_tiers cvt on c.id = cvt.customer_id
left join vip_tiers vt on vt.id=cvt.tier_id
left join perks p on p.customer_id = c.id
and p.created_at >= coalesce(vts.custom_start_date,0)
and p.completed = 1
and p.reversed = 0
and p.expired = 0
where
c.merchant_id = 50506
and cvt.tier_id != 1734
group by c.id having sum(p.reward_points) >= '950' and sum(p.reward_points) < '1000'
;
My understanding is that they dont get the records of those who are not in the tier because of the cvt.tier_id != 1734 clause in my where because they dont have records in the customers_vip_tiers table. How do I also get the ones that dont have records in that table?
I got the answer.
I got rid of cvt.tier_id != 1734.
select
c.id,
c.email,
sum(p.reward_points) as total_points,
vt.name,
cvt.tier_id
from vip_tiers_settings vts
left join customers c on c.merchant_id = vts.merchant_id
left join customers_vip_tiers cvt on c.id = cvt.customer_id
left join vip_tiers vt on vt.id=cvt.tier_id
left join perks p on p.customer_id = c.id
and p.created_at >= coalesce(vts.custom_start_date,0)
and p.completed = 1
and p.reversed = 0
and p.expired = 0
where
c.merchant_id = 50506
and c.id not in(select customer_id from customers_vip_tiers cvv where cvv.merchant_id=50506 and cvv.tier_id=1734)
group by c.id having sum(p.reward_points) >= '950' and sum(p.reward_points) < '1000';
I am trying to get the total prices of all parent categories in a specific month and year. A parent category is any category with a parent_id == 0. My query is like below:
SELECT
ROUND(SUM(od.total_price)) as price,
c.parent_id as pId,
c1.name
FROM a_orders o
INNER JOIN a_order_details as od on o.id = od.order_id
INNER JOIN a_product as p on p.id = od.product_id
INNER JOIN a_category as c on c.id = p.category_id
LEFT OUTER JOIN a_category c1 ON c.parent_id = c1.id
WHERE YEAR(order_date) = 2018
AND o.STAT = 'Y'
AND MONTH(order_date) = 6
GROUP BY c.parent_id;
I'm getting parent categories just which have a price but I need to get all parent categories, if there is no price result should be 0.
My sqlfiddle is -> http://sqlfiddle.com/#!9/b9f4c1/1
My result is like this :
price pId name
410 1 T-SHIRT
400 2 JEANS
But should be like this :
price pId name
410 1 T-SHIRT
400 2 JEANS
0 6 SHOES
To do this you need switch your logic around. First select all of your parent categories, then join your order data. This way you can ensure all of the desired categories will be included in the results.
There are a bunch of ways you could approach this — here I created a subquery with all of the order data and then a LEFT JOIN with the category table:
SELECT
IFNULL(ROUND(SUM(orders.total_price)),0) AS price,
c.id AS pId,
c.name
FROM a_category c
LEFT JOIN (
SELECT od.total_price, c.parent_id
FROM a_orders o
INNER JOIN a_order_details od ON o.id = od.order_id
INNER JOIN a_product p ON p.id = od.product_id
INNER JOIN a_category c ON c.id = p.category_id
WHERE YEAR(order_date) = 2018
AND o.STAT = 'Y'
AND MONTH(order_date) = 6
) orders ON orders.parent_id = c.id
WHERE c.parent_id = 0
GROUP BY c.id;
http://sqlfiddle.com/#!9/b9f4c1/3
I have the following query which works (without the "WHERE stats.dt" part). I get all users with their data.
My problem is that this query of course results in rows ONLY with users that have stats.dt > $timestampnow-$maxdays_data). But I need ALL users but their values of SUM(upload) or SUM(download) need only to be fetched when stats.dt is larger than tstamp-maxdays. The other rows with values of upload and download where stats.dt is smaller than what I need, can be ignored.
An example would be that the user with nodeid 2 would not be selected because his dt is too small. I do want the user to be selected but just not with data or upload values (they can be 0).
The stats table looks like this
nodeid | dt | upload | download
----------------------------------------
1 | 1381699533 | 345345 | 42324234
1 | 1382899152 | 7575 | 574234
1 | 1380699533 | 764534 | 7235232
2 | 1372899152 | 71455 | 124123
I don't know where to start looking how to solve this so maybe somebody out there can point me in the right direction. Thanks!
SELECT b.id, b.lastname, b.name, c.balance, a.maxdebt, b.warndata, b.warndownload, b.warnupload, b.warndebt, b.cutoffdata, b.cutoffdownload, b.cutoffupload, b.cutoffdebt, b.data, b.download, b.upload, b.warning, b.access, b.cutoffstop
FROM (
SELECT customers.id AS id, SUM(tariffs.value) AS maxdebt
FROM tariffs
LEFT JOIN assignments ON tariffs.id = assignments.tariffid
RIGHT JOIN customers ON assignments.customerid = customers.id
GROUP BY id
) a
JOIN (
SELECT customers.id AS id, UPPER(lastname) AS lastname, customers.name AS name, SUM(stats.upload+stats.download) AS data, SUM(stats.download) AS download, SUM(stats.upload) AS upload, customers.cutoffstop, warndata, warndownload, warnupload, warndebt, cutoffdata, cutoffdownload, cutoffupload, cutoffdebt, nodes.warning, nodes.access
FROM customers
LEFT JOIN nodes ON customers.id = nodes.ownerid
LEFT JOIN stats ON nodes.id = stats.nodeid
LEFT JOIN customerwarnings ON customers.id = customerwarnings.id
WHERE stats.dt > ($timestampnow-$maxdays_data)
GROUP BY id
) b ON a.id = b.id
JOIN (
SELECT customerid, SUM(cash.value) AS balance
FROM cash
GROUP BY customerid
) c ON b.id = c.customerid
Here's a brute force way of doing it. It can almost certainly be simplified, but without knowing more about the table and foreign key structures it's hard to be sure.
What I've done is replace sum(stats.download) with sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.download end) and similarly for upload. I've also changed the join on b to be an outer join:
Select
b.id,
b.lastname,
b.name,
c.balance,
a.maxdebt,
b.warndata,
b.warndownload,
b.warnupload,
b.warndebt,
b.cutoffdata,
b.cutoffdownload,
b.cutoffupload,
b.cutoffdebt,
b.data,
b.download,
b.upload,
b.warning,
b.access,
b.cutoffstop
From (
Select
c.id,
sum(t.value) as maxdebt
From
tariffs t
left join
assignments a
on t.id = a.tariffid
right join
customers
on a.customerid = c.id
Group by
c.id
) a left outer join (
Select
c.id,
upper(lastname) as lastname,
c.name,
sum(s.upload + s.download) as data,
sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.download end) as download,
sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.upload end) as upload,
c.cutoffstop,
warndata,
warndownload,
warnupload,
warndebt,
cutoffdata,
cutoffdownload,
cutoffupload,
cutoffdebt,
n.warning,
n.access
From
customers c
left join
nodes n
on c.id = n.ownerid
left join
stats s
on n.id = s.nodeid
left join
customerwarnings w
on c.id = w.id
Group By
c.id
) b
On a.id = b.id
inner join (
Select
customerid,
sum(cash.value) as balance
From
cash
Group By
customerid
) c
on a.id = c.customerid
I have a table of products, suppliers and prdtFrn as follows:
suppliers:
fid , name
1 | 'Andrey'
2 | 'lucas'
products:
pid , name
1 | 'X'
2 | 'Y'
prdtFrn:
pid , fid , price
---------------- supplier 'andrey'
1 | 1 | 19.00
2 | 1 | 16.00
----------------- supplier 'lucas'
1 | 2 | 14.00
2 | 2 | 18.00
And I am looking for a SQL query that will return all products that are sold at a price less than mine (andrey). In this example, I would want to get product "X", because lucas is selling it for less than I am.
A lot of the other answers seem complicated, but the answer is simple:
select distinct p1.*
from prdtfrn p1
join prdtfrn p2 on p1.pid = p2.pid and p2.fid != 1 and p2.price < p1.price
where p1.fid = 1; // fid=1 is known to be 'Audrey'
This query lists all products that are sold cheaper elsewhere.
Just select from the prdtFrn table twice. From there, the query is straightforward.
I've included an untested example below. A corresponds to the competitors' products and B corresponds to yours.
SELECT
suppliers.name,
A.pid,
A.price
FROM
prdtfrn AS A,
prdtfrn AS B,
suppliers
WHERE
A.price < B.price
AND A.pid = B.pid
AND B.fid = 1
AND A.fid = suppliers.fid;
I assumed that you are comparing to many suppliers (not only to lucas) so this is my query. Try this one:
SELECT e.name,
g.name,
f.price
FROM suppliers e INNER JOIN prdtFrn f ON
e.fid = f.fid
INNER JOIN products g ON
f.pid = g.pid
WHERE e.name <> 'Andrey' AND -- this selects all products which is not yours
f.pid IN -- the selected products should match your products
(SELECT c.pid -- this subquery selects all your products
c.name,
b.price
FROM suppliers a INNER JOIN prdtFrn b ON
a.fid = b.fid
INNER JOIN products c ON
b.pid = c.pid
WHERE a.name = 'Audrey') d AND
f.price < d.price -- product is less than yours
Here is a query to get the info you're looking for. As there might be products that other suppliers have on sale and you don't, I thought you might be interested in finding those out too.
This is the query that you're asking for (without the products other suppliers have and you don't):
select sp2.pid, p.name as ProductName, sp2.price, s2.name as SupplierName
from prdtFrn sp2 join (
select sp.pid, sp.price from suppliers s
join prdtFrn sp on sp.fid = s.fid
where s.name = 'Andrey'
) as AndreysProducts
on AndreysProducts.pid = sp2.pid
join products p on sp2.pid = p.pid
join suppliers s2 on s2.fid = sp2.fid
where sp2.price < AndreysProducts.price
Example
This is the query that you might be interested in (with the products other suppliers have and you don't):
select sp2.pid, p.name as ProductName, sp2.price, s2.name as SupplierName
from prdtFrn sp2 left join (
select sp.pid, sp.price from suppliers s
join prdtFrn sp on sp.fid = s.fid
where s.name = 'Andrey'
) as AndreysProducts
on AndreysProducts.pid = sp2.pid
join products p on sp2.pid = p.pid
join suppliers s2 on s2.fid = sp2.fid
where sp2.price < AndreysProducts.price or AndreysProducts.pid is null
Example
I wasn't too sure how to present this question to keep it consise while still giving plenty of info to work with. If any one needs more info/tables let me know and I'll be happy to edit.
I'm trying to query a database and need to join several tables to get the data I want.
The query is: Show me amount of items from supplier x that were sold between two dates and how many of them are currently in stock.
The SQL for this is as follows:
SELECT p.id as product_id, p.description as description, SUM(col.quantity) as qty, SUM(s.stocklevel) as stocklevel, sup.name as supplier from products as p
LEFT JOIN customerorderlines as col on col.product_id = p.id
LEFT JOIN customerorders as co on co.id = col.customerorder_id
LEFT JOIN stock as s on s.product_id = p.id
LEFT JOIN suppliers as sup on sup.id = p.supplier_id
WHERE co.orderdate BETWEEN '2009-07-01' AND '2009-08-01'
AND p.supplier_id = 51
GROUP by col.product_id
ORDER by SUM(col.quantity)
DESC
Here is one of the rows it retuns:
product_id description qty stocklevel supplier
24376 Streaker Wax Paper Strips 330 3510 Rand Rocket Ltd
Notice stocklevel returning 3510.
Now lets take a look at this on its own:
SELECT SUM(stocklevel) from stock where product_id = 24376
SUM(stocklevel)
90
With my limited mysql knowledge, I have no idea why it is doing this. Any suggestions are extremly appreciated.
Thanks in advance.
You are most likely counting several product_id's stocklevels multiple times.
One solution would be to pre-compute the stocklevel for each product_id and join this with your original query.
SELECT p.id as product_id
, p.description as description
, SUM(col.quantity) as qty
, sl.stocklevel as stocklevel
, sup.name as supplier
from products as p
INNER JOIN (
SELECT product_id, SUM(stocklevel) as stocklevel
from stock
GROUP BY product_id
) sl ON sl.product_id = p.product_id
LEFT JOIN customerorderlines as col on col.product_id = p.id
LEFT JOIN customerorders as co on co.id = col.customerorder_id
LEFT JOIN stock as s on s.product_id = p.id
LEFT JOIN suppliers as sup on sup.id = p.supplier_id
WHERE co.orderdate BETWEEN '2009-07-01' AND '2009-08-01'
AND p.supplier_id = 51
GROUP by col.product_id, sl.stocklevel
ORDER by SUM(col.quantity) DESC
SELECT prod.id as product_id, prod.description as description,
SUM(col.quantity) as qty, SUM(sup.stocklevel) as stocklevel,
sup.name as supplier, sup.id
FROM product prod
INNER join suppliers sup as sup.id = prod.supplier_id
LEFT join stock stk as stk.product_id = prod.id
LEFT JOIN customerorderlines as col on col.product_id = prod.id
LEFT JOIN customerorders as co on co.id = col.customerorder_id
WHERE co.orderdate BETWEEN '2009-07-01' AND '2009-08-01'
GROUP BY sup.id, prod.id
ORDER by SUM(col.quantity) DESC