I am trying to fetch the value of available stock in MYSQL but when the number of rows gets more than 35000 request is failing with no response. I use subqueries to fetch the value of the available stock.
Here is my code:
SELECT
p.`product-id` AS id,
p.`product-id` AS product_id,
ped.purchase_entry_id AS purchase_id,
p.`product-name` AS name,
m.name AS manufacturer,
p.`product-type` AS product_type,
ped.id AS batch_no,
ped.internal_sales_rate AS barcode,
c.name as category,
ped.size AS size,
IFNULL(ped.sales_rate,0) AS sales_rate,
IFNULL(ped.purchase_rate,0) AS purchase_rate,
IFNULL(SUM(ped.units),0) AS units_purchased,
(select IFNULL(SUM(sed.qty),0) FROM sales_entry_details sed WHERE (sed.batch_no = ped.id)) AS units_sold,
(select IFNULL(SUM(sr.qty),0) FROM sales_return_item sr WHERE (sr.batch_no = ped.id)) AS retured,
(select IFNULL(SUM(pri.qty),0) FROM purchase_return_items pri WHERE (pri.batch_no = ped.id)) AS purchase_return,
(select IFNULL(SUM(ast.qty),0) FROM adjustment_stock ast WHERE (ast.batch_no = ped.id)) AS adjustment,
(select IFNULL(SUM(ast2.batch_no),0) FROM adjustment_stock ast2 WHERE (ast2.batch_no = ped.id)) AS isChecked
FROM purchase_entry_details ped
LEFT JOIN products p on p.`product-id` = ped.product_id
LEFT JOIN category c ON c.id = p.`product-type`
LEFT JOIN manufacturer m ON m.id = p.manufacturer
GROUP BY ped.id;
Is there any better option to fetch the value of stock available in SQL??
The query looks fine. As there is no limiting criteria (no WHERE clause), you'll read the whole table sequentially.
You'd want indexes on batch_no in the involved tables of course. But I guess these are foreign keys to some batch table, so the indexes should already exist.
The only thing that I notice is that you select from adjustment_stock twice. You can avoid this by moving the subqueries to your FROM clause:
SELECT
p.`product-id` AS id,
p.`product-id` AS product_id,
ped.purchase_entry_id AS purchase_id,
p.`product-name` AS name,
m.name AS manufacturer,
p.`product-type` AS product_type,
ped.id AS batch_no,
ped.internal_sales_rate AS barcode,
c.name as category,
ped.size AS size,
IFNULL(ped.sales_rate,0) AS sales_rate,
IFNULL(ped.purchase_rate,0) AS purchase_rate,
IFNULL(SUM(ped.units),0) AS units_purchased,
IFNULL(sed.sum_qty),0) AS units_sold,
IFNULL(sr.sum_qty),0) AS retured,
IFNULL(pri.sum_qty),0) AS retured,
IFNULL(ast.sum_qty),0) AS adjustment,
IFNULL(ast.sum_batch_no),0) AS isChecked
FROM purchase_entry_details ped
LEFT JOIN products p on p.`product-id` = ped.product_id
LEFT JOIN category c ON c.id = p.`product-type`
LEFT JOIN manufacturer m ON m.id = p.manufacturer
LEFT JOIN
(
select batch_no, sum(qty) as sum_qty from sales_entry_details group by batch_no
) sed on sed.batch_no = ped.id
LEFT JOIN
(
select batch_no, sum(qty) as sum_qty from sales_return_item group by batch_no
) sr on sr.batch_no = ped.id
LEFT JOIN
(
select batch_no, sum(qty) as sum_qty from purchase_return_items group by batch_no
) pri on pri.batch_no = ped.id
LEFT JOIN
(
select batch_no, sum(qty) as sum_qty, sum(batch_no) as sum_batch_no
from adjustment_stock group by batch_no
) ast on ast.batch_no = ped.id
GROUP BY ped.id;
(Adding up batch numbers seems a bit weird, though.)
You can make separate joins for each table used in subqueries against purchase_entry_details table. In all these separate selects make group by ped.id. Afterwards make left join to new tables with sums.
SELECT
...
SED.sumqty,
SR.sumqty,
...
FROM purchase_entry_details ped
LEFT JOIN products p on p.`product-id` = ped.product_id
LEFT JOIN category c ON c.id = p.`product-type`
LEFT JOIN manufacturer m ON m.id = p.manufacturer
LEFT JOIN (
SELECT ped1.id, SUM(sed1.qty) as sumqty
FROM purchase_entry_details ped1
JOIN sales_entry_details sed1 ON sed1.batch_no = ped1.id
GROUP BY ped1.id
) SED ON SED.id = ped.id
LEFT JOIN (
SELECT ped1.id, SUM(sr1.qty) as sumqty
FROM purchase_entry_details ped1
JOIN sales_return_item sr1 ON sr1.batch_no = ped1.id
GROUP BY ped1.id
) SR ON SR.id = ped.id
...
Related
In MySQL I have 4 tables:
- product(id)
- order(id)
- order_detail_1(id, product_id, order_id, qty)
- order_detail_2(id, product_id, order_id, qty)
I want to get the sum of the quantity of products sold from the 2 tables (order_detail_1, order_detail_2) grouping them by product
produt can existe in order_detail_1 and not in order_detail_2 and vice versa
i tested this query and it worked but I want a simpler query without the union and the subquery.
select tmp.product_id ,sum(tmp.qty) from
(
(
select order_detail_1.product_id ,sum(order_detail_1.qty)
from order_detail_1
inner join order on order_detail_1.id_order = order.id
where order_detail_1.product_id is not null
group by order_detail_1.product_id
)
union all
(
select order_detail_2.product_id ,sum(order_detail_2.qty)
from order_detail_2
inner join order on order_detail_2.id_order = order.id
where order_detail_2.product_id is not null
group by order_detail_2.product_id
)
) tmp
group by tmp.product_id
It looks like you're not using order table other then checking if it exists, so you can use EXISTS()
SELECT p.product_id,sum(p.qty) as qty
FROM (SELECT product_id,qty,id_order FROM order_detail_1
WHERE product_id IS NOT NULL
UNION ALL
SELECT product_id,qty,id_order FROM order_detail_2
WHERE product_id IS NOT NULL) p
WHERE EXISTS(SELECT 1 FROM order o
WHERE o.id = p.id_order)
GROUP BY p.product_id
If a product is in only one table, you can use left join:
select p.id, (coalesce(sum(od1.qty), 0) + coalesce(sum(od2.qty, 0))) as qty
from product p left join
order_detail_1 od1
on od1.product_id = p.id left join
order_detail_2 od2
on od2.product_id = p.id
group by p.id;
This formulation depends on the fact that the two tables are exclusion -- a product is in only one table.
EDIT:
If products can exist in both tables, then you need to aggregate them first:
select p.id, (coalesce(od1.qty, 0) + coalesce(od2.qty, 0)) as qty
from product p left join
(select product_id, sum(qty) as qty
from order_detail_1 od1
group by product_id
) od1
on od1.product_id = p.id left join
(select product_id, sum(qty) as qty
from order_detail_2 od2
group by product_id
) od2
on od2.product_id = p.id;
Given the database schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select cname, Province
From Customer c
Where exists (
Select *
from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple'
)
and Not exists (
Select *
from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple'
)
How would I go about rewriting this query without the two sub queries?
You can use the LEFT JOIN/NULL pattern to find customers who haven't bought any non-Apple products. Then you can do this all with just joins. You'll have to join with Supply and Parts twice, once for finding Apple products, then again for excluding non-Apple products.
SELECT distinct c.name, c.province
FROM Customer AS c
JOIN Supply AS s1 ON s1.cid = c.cid
JOIN Parts AS p1 ON p1.pid = s1.pid
LEFT JOIN Supply AS s2 ON s2.cid = c.cid
LEFT JOIN Parts AS p2 ON p2.pid = s2.pid AND p2.producer != 'Apple'
WHERE p1.producer = 'Apple' AND p2.pid IS NULL
Notice that in the LEFT JOIN you put restrictions of the second table in the ON clause, not the WHERE clause. See Return row only if value doesn't exist for more about this part of the query.
You want customer who only bought Apple products?
One possible solution is based on conditional aggregation:
Select c.cname, c.Province
From Customer c
join
( -- this is not a Subquery, it's a Derived Table
Select s.CId -- assuming there's a CId in Supply
from Supply s
join Part p
on p.pId = s.pId
group by s.CId
-- when there's any other supplier this will return 1
having max(case when p.Producer = 'Apple' then 0 else 1 end) = 0
) as p
on p.CId = c.CId
Using this query to get the products with words that fulfill all three required word terms (lenovo, laptop, computer):
SELECT t1.id, t1.name, t1.price FROM
(SELECT p.id AS productid, name, price
FROM products p JOIN productwords pw ON p.id = pw.productid
JOIN words w ON pw.wordid = w.id WHERE word.term = 'lenovo') t1
INNER JOIN
(SELECT p.id AS productid, name, price
FROM products p JOIN productwords pw ON p.id = pw.productid
JOIN words w ON pw.wordid = w.id WHERE word.term = 'laptop') t2
INNER JOIN
(SELECT p.id AS productid, name, price
FROM products p JOIN productwords pw ON p.id = pw.productid
JOIN words w ON pw.wordid = w.id WHERE word.term = 'computer') t3
ON
t1.productid = t2.productid
AND
t1.productid = t3.productid
ORDER BY t1.name
As far as I can see, the query considers the whole words table for each term (the tables have indexes. Database is MySql).
Can the query be rewritten in a better way, so it will become faster? (the tables contain millions of rows)
For example with subsets, so the 'laptop' search only considers the rows matching 'lenovo' - and the 'computer' search only considers the rows matching first 'lenovo' and then 'laptop'.
Thanks!
You can use the HAVING clause :
SELECT p.id AS productid, name, price
FROM products p
JOIN productwords pw ON p.id = pw.productid
JOIN words w ON pw.wordid = w.id
WHERE word.term in ('lenovo','computer','laptop')
GROUP BY p.id , name, price
HAVING COUNT(DISTINCT word.term) = 3
That is if I understood the question, it looks like product -> words is 1:n relation , and if no column from the word table is selected, that should work perfectly.
This might be a quicker way of doing it:
SELECT p.id, name, price
FROM products p
where
EXISTS (select null
from productwords pw1
JOIN words w1 ON pw1.wordid = w1.id
where w1.term = 'lenovo'
and p.id = pw1.productid )
and EXISTS (select null
productwords pw2
JOIN words w2 ON pw2.wordid = w2.id
where w2.term = 'laptop'
and and p.id = pw2.productid )
and EXISTS (select null
productwords pw3 ON p.id = pw3.productid
JOIN words w3
where w3.term = 'computer'
and p.id = pw3.productid )
ORDER BY name;
I need to query for the users with highest amount of sales by all projects, where the users are in users table, sales in units table, projects in projects table.
Projects Top Agent Total Sales for Project
Project A User A 100000
Project B User B 20000
Project C User A 1000
Project D - 0
The Projects column is list all the projects regardless it has sales or not.
The Top Agent column is list the user with the highest sales in the project.
The Total Sales for Project is the total sales for a projects.
The agent column i got is incorrect because there is someone else has the highest sales, the query seems to return the first row of the result
SELECT projects, pid, CASE WHEN agent is null THEN '-' ELSE agent END as agent,
CASE WHEN FORMAT(topagent,0) > 0 THEN FORMAT(topagent,0) ELSE 0 END as salesvolume
FROM (
SELECT projects.name as projects, projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent,
SUM(units.price) AS topagent
FROM users inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid
union
select projects.name as projects, projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent,
SUM(units.price) AS topagent
from projects left outer join types on projects.id = types.project_id
left outer join units on types.id = units.types_id and units.status = 'Sold'
left outer join bookings on units.id = bookings.unit and units.status = 'Sold'
left outer join users on bookings.agent_id = users.id and units.status = 'Sold'
group by pid
) a
GROUP BY pid
order by topagent desc
Try it if helps you-
SELECT a.prjname, IFNULL(usr.name,'-') AS Top_Agent, SUM(a.sale) AS Total_Sales_for_Project
FROM
(
SELECT prj.id AS prjid,prj.name AS prjname,usr.id,usr.name AS usrname,IFNULL(SUM(unit.price),0) AS sale
FROM projects AS prj
LEFT JOIN `types` AS typ ON typ.project_id=prj.id
LEFT JOIN units AS unt ON unt.type_id=typ.id AND unt.status='sold'
LEFT JOIN bookings bkg ON bkg.unit=unt.id
LEFT JOIN users usr ON usr.id=bkg.agent_it
GROUP BY prj.id,usr.id
ORDER BY prj.id,usr.id,sale DESC
) a
GROUP BY a.prjid
Your column aliases are confusing to read. In English, it seems what you mean by topagent is "sum of sales by a human". But in SQL, your GROUP BY pid means that the SUM(units.price) really means "sum of sales in a project".
Then the UNION adds a list of projects to a list of users. The agent names are basically random at this point.
If I decipher the requirements as "a list of projects ranked by the sales values of each project's top sales agent", then you'd have SQL as below:
SELECT
pid,
projects.name as project_name,
IFNULL(a.top_agent_name,'-') as top_agent_name,
CASE WHEN FORMAT(top_agent_sales,0) > 0 THEN FORMAT(top_agent_sales,0) ELSE 0 END as top_agent_salesvolume
FROM
projects
JOIN
SELECT
a.pid,
a.agent_name as top_agent_name,
a.agent_sales as top_agent_sales
FROM
(SELECT
projects.id as pid,
concat(users.f_name, ' ', users.l_name) as agent_name,
SUM(units.price) AS agent_sales
FROM users
inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid, users.id
) a # get all agents for all projects
JOIN
(SELECT
MAX(agent_sales) as max_project_agent_sales
FROM
(SELECT
projects.id as pid,
SUM(units.price) AS agent_sales
FROM users
inner join bookings on bookings.agent_id = users.id
inner join units on bookings.unit = units.id
inner join types on types.id = units.types_id
inner join projects on projects.id = types.project_id
WHERE units.status = 'Sold'
GROUP BY pid, users.id
)
GROUP BY pid) b ON a.pid = b.pid
WHERE
a.agent_sales = b.max_project_agent_sales
ORDER BY a.agent_sales desc
Old answer below:
There are 2 topagents for each pid in the inner query since it's a union of 2 group bys. There isn't a reducing function in the outer group by pid so the topagent returned in the select is the first one that came up in the inner query.
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