In the following query I am trying to fetch data from tables: company, classes_by_company, and person. I have a foreign key in all tables with the name company_id. In the classes_by_company table a class can have a class_status that can be either active or inactive. The same goes for table person with its corresponding person_status. I am able to count the total number of classes and the total number of persons, find the working query here: SQFIDDLE. In the query below I am trying to incorporate the previous query but now also count the total number of class_status=ACTIVE and person_status=ACTIVE. I am getting error Not unique table/alias: 'c'. How can I can count the total number and the total number of active classes and person? SQLFIDDLE
SELECT a.id, a.company_id, a.status, c.class_status, p.person_status,
c.total_count AS classes_per_company,
p.total_count AS employees_per_company,
c.active_count AS active_classes,
p.active_count AS active_instructors
FROM company a
LEFT JOIN (SELECT company_id, COUNT(*) as total_count
FROM classes_by_company
GROUP BY company_id) c
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM classes_by_company
WHERE class_status = 'ACTIVE'
GROUP BY company_id) c
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as total_count
FROM person
GROUP BY company_id) p
ON a.company_id = p.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM person
WHERE person_status = 'ACTIVE'
GROUP BY company_id) p
ON a.company_id = c.company_id
Table Structure:
CREATE TABLE company
(
id int auto_increment primary key,
company_id int,
status varchar(20)
);
CREATE TABLE classes_by_company
(
id int auto_increment primary key,
company_id int,
class_name varchar(20),
class_status varchar(20)
);
CREATE TABLE person
(
id int auto_increment primary key,
employee_id int,
company_id int,
person_name varchar(20),
person_status varchar(20)
);
Your query structure is fine, you just need to give different aliases to each subquery that you join with.
You also failed to return class_status and person_status from the c and p subqueries.
SELECT a.id, a.company_id, a.status, c.class_status, p.person_status,
c.total_count AS classes_per_company,
p.total_count AS employees_per_company,
ca.active_count AS active_classes,
pa.active_count AS active_instructors
FROM company a
LEFT JOIN (SELECT company_id, class_status, COUNT(*) as total_count
FROM classes_by_company
GROUP BY company_id) c
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM classes_by_company
WHERE class_status = 'ACTIVE'
GROUP BY company_id) ca
ON a.company_id = ca.company_id
LEFT JOIN (SELECT company_id, person_status, COUNT(*) as total_count
FROM person
GROUP BY company_id) p
ON a.company_id = p.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM person
WHERE person_status = 'ACTIVE'
GROUP BY company_id) pa
ON a.company_id = pa.company_id
I changed the aliases to ca for active classes, and pa for active person.
SQLFIDDLE
You can also reduce the number of joins by counting the active rows in the same query as the total rows:
SELECT a.id, a.company_id, a.status, c.class_status, p.person_status,
c.total_count AS classes_per_company,
p.total_count AS employees_per_company,
c.active_count AS active_classes,
p.active_count AS active_instructors
FROM company a
LEFT JOIN (SELECT company_id, class_status, COUNT(*) as total_count, SUM(class_status = 'ACTIVE') as active_count
FROM classes_by_company
GROUP BY company_id) c
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, person_status, COUNT(*) as total_count, SUM(person_status = 'ACTIVE') as active_count
FROM person
GROUP BY company_id) p
ON a.company_id = p.company_id
SQLFIDDLE
Very small change...table alias's (or subqueries) cannot be the same
SELECT a.id, a.company_id, a.status, c.class_status, p.person_status,
c.total_count AS classes_per_company,
p.total_count AS employees_per_company,
ca.active_count AS active_classes,
pa.active_count AS active_instructors
FROM company a
LEFT JOIN (SELECT company_id, COUNT(*) as total_count
FROM classes_by_company
GROUP BY company_id) c
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM classes_by_company
WHERE class_status = 'ACTIVE'
GROUP BY company_id) ca
ON a.company_id = c.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as total_count
FROM person
GROUP BY company_id) p
ON a.company_id = p.company_id
LEFT JOIN (SELECT company_id, COUNT(*) as active_count
FROM person
WHERE person_status = 'ACTIVE'
GROUP BY company_id) pa
ON a.company_id = c.company_id
Related
I have 5 tables in Mysql and i need a combined result from all the tables
The tables are as follows
1)library_book_relation
CREATE TABLE db.library_book_relation (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
library_id int ,
book_item_id int ,
seller_price decimal(10,2) ,
pack_quantity decimal(10,2) ,
is_discontinue tinyint ,
total_quantity decimal(10,2) ,
) ;
2)book_item
CREATE TABLE db.`book_item` (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
book_id int ,
size_unit_id tinyint ,
size decimal(10,2) ,
is_active boolean ,
is_deleted boolean ,
) ;
3) book
CREATE TABLE db.book(
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(100) ,
author_id int ,
description varchar(256) ,
is_active boolean ,
is_deleted boolean ,
) ;
4) author
CREATE TABLE db.author(
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(100) ,
image varchar(256) ,
description varchar(256) ,
is_active tinyint ,
is_deleted tinyint ,
) ;
5)size_unit
CREATE TABLE db.size_unit (
id tinyint NOT NULL PRIMARY KEY,
name varchar(100) ,
is_active tinyint ,
) ;
i want a combined result where i get
author id
author name
count of books for the author in library
i got all 3 in different queries but dont know how to combine the result
to get author id and name
select id,name FROM author where id IN( select e.id from author e inner join book r on e.id = r.author_id where r.id IN( select q.id from book q inner join book_item w on q.id = w.book_id where w.id IN( Select s.id from book_item s inner join library_book_relation d on s.id=d.book_item_id where d.library_id = 1 )))
to get count of books for author in library
(Select COUNT(*) FROM book where author_id IN ( select e.id from author e inner join book r on e.id = r.author_id where r.id IN( select q.id from book q inner join book_item w on q.id = w.book_id where w.id IN( Select s.id from book_item s inner join library_book_relation d on s.id=d.book_item_id where d.library_id = 1 ))) GROUP BY author_id)
The easiest way is to join all tables, group by author and library and count:
select b.author_id, a.name, lbr.library_id, count(*)
from book b
join book_item bi on bi.book_id = b.id
join library_book_relation lbr on lbr.book_item_id = bi.id
join author a on a.id = b.author_id
group by b.author_id, a.name, lbr.library_id
order by b.author_id, a.name, lbr.library_id;
Per SQL standard you only need to group by b.author_id, lbr.library_id, because you want one result row per author and library. MySQL should be able to do this, so you can remove a.name from the group by clause. (There is only one name per author ID.)
You can also count before joining with the authors table. It is considered good style to aggregate before joining and can help a lot (or be even necessary) in more complicated queries.
select c.author_id, a.name, c.library_id, c.books
from
(
select b.author_id, lbr.library_id, count(*) as books
from book b
join book_item bi on bi.book_id = b.id
join library_book_relation lbr on lbr.book_item_id = bi.id
group by b.author_id, lbr.library_id
) c
join author a on a.id = c.author_id
order by c.author_id, a.name, c.library_id;
Above queries do not count zeros, though. They just show how often to find authors in the libraries. Here is how to count how many books author #1 has in library #123:
select a.id, a.name, coalesce(b.books, 0)
from author a
left join
(
select b.author_id, count(*) as books
from book b
join book_item bi on bi.book_id = b.id
join library_book_relation lbr on lbr.book_item_id = bi.id
where lbr.library_id = 123
group by b.author_id
) c on c.author_id = a.id
where a.id = 1;
Or with the subquery in the select clause:
select
a.id,
a.name,
(
select count(*)
from book b
join book_item bi on bi.book_id = b.id
join library_book_relation lbr on lbr.book_item_id = bi.id
where lbr.library_id = 123
and b.author_id = a.id
) as books
from author a
where a.id = 1;
At last a query to get all authors and all libraries including zeros:
select a.id as author_id, a.name, l.id as library_id, coalesce(c.books, 0)
from author a
cross join library l
left join
(
select b.author_id, lbr.library_id, count(*) as books
from book b
join book_item bi on bi.book_id = b.id
join library_book_relation lbr on lbr.book_item_id = bi.id
group by b.author_id, lbr.library_id
) c on c.author_id = a.id and c.library_id = l.id
order by a.id, l.id;
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
...
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
I have following query which gives correct result but I want to use not exist instead of not in.
select cust_name from customer
where cust_id not in
(select cust_id from ord where ord_id in
(select ord_id from orderitem where prod_id in
(select prod_id from product
WHERE PROD_DESCRIP = 'Guide to Tennis')))
Furthermore I have tried -
select cust_name from customer
where cust_id not exists
(select cust_id from ord where ord_id in
(select ord_id from orderitem where prod_id in
(select prod_id from product
WHERE PROD_DESCRIP = 'Guide to Tennis')))
I am not sure whether it is true or not
You don't put the column name before NOT EXISTS, it's just WHERE NOT EXISTS (subquery).
You then need to make it a correlated subquery, comparing the cust_id in the outer table with the cust_id in the inner table.
select cust_name
from customer AS c
where not exists
(select 1
from ord AS o
where ord_id in
(select ord_id from orderitem where prod_id in
(select prod_id from product WHERE PROD_DESCRIP = 'Guide to Tennis'))
and o.cust_id = c.cust_id)
In general, it's better to use JOIN than WHERE xxx IN -- MySQL tends to optimize it better.
select cust_name
from customer AS c
where not exists
(select 1
from ord AS o
join orderitem AS oi ON o.ord_id = oi.ord_id
join product AS p ON oi.prod_id = p.prod_id
where p.prod_descrip = 'Guide to Tennis'
and o.cust_id = c.cust_id)
And you can do the NOT EXISTS using a LEFT JOIN pattern.
select cust_name
from customer AS c
left join ord AS o ON o.cust_id = c.cust_id
left join orderitem AS oi ON o.ord_id = oi.ord_id
left join product AS p ON oi.prod_id = p.prod_id AND p.prod_descrip = 'Guide to Tennis'
where o.cust_id IS NULL
Problem
I have 3 tables: customers, companies and calls.
A customer has many Companies, and a Company has many calls. (both one to many).
The current status of a Company is the result of the last call for the company (MAX(created_at)).
Now I want a list of all companies for a customer with the columns of the last call in the results.
Needed Result
The result should be:
company.*, lastcall.*,
There could be calls with the same created_at date. Then there should be only 1 row in de result.
Not all companies has a call yet, the company should still be in the result and the columns of the call should be NULL. (left join)
Tables
customers
- id (int, primary key)
- name (varchar)
- address (varchar)
- city (varchar)
companies
- id (int, primary key)
- customer_id (int)
- name (varchar)
- address (varchar)
- city (varchar)
calls
- id (int, primary key)
- company_id (int)
- result (varchar)
- created_at (datetime)
Attempt
A query which didn't work I came up with is:
SELECT * FROM companies co
LEFT JOIN calls ca ON co.id = ca.company_id
WHERE co.customer_id = ?
GROUP BY co.id
HAVING ca.created_at = (SELECT max(ll.created_at) FROM calls ll WHERE ll.company_id = co.id)
you should just join a select so that way you arent trying to re evaluate the select.
SELECT co.id, co.label, ca.result, ca.id, t.date_created as most_recent
FROM companies co
LEFT JOIN
( SELECT MAX(created_at) as date_created, company_id
FROM calls
GROUP BY company_id
) t ON t.company_id = co.id
JOIN calls ca ON ca.company_id = t.company_id AND t.date_created = ca.created_at
WHERE co.customer_id = ?
EDIT:
the issue is you have more than one call per company at the max date. to test this just pull out one customer and company and look at the results.
SELECT co.id, co.label, ca.result, ca.id, ca.created_at as most_recent_date
FROM companies co
LEFT JOIN
( SELECT MAX(created_at) as date_created, company_id
FROM calls
GROUP BY company_id
) t ON t.company_id = co.id
JOIN calls ca ON ca.company_id = t.company_id AND t.date_created = ca.created_at
WHERE co.customer_id = ? AND co.id = ?
run this query and specify a specific company. look at the move_recent_date column and see if the date is the same for each row and if it is the max date
You can do this by joining on calls twice, the first time being in a subquery where you retrieve the last call date for each company:
SELECT * FROM companies co
LEFT JOIN (SELECT company_id, MAX(created_at) AS last_call FROM calls GROUP BY company_id) AS last_calls ON last_calls.company_id = co.id
LEFT JOIN calls ca ON ca.company_id = last_calls.company_id AND ca.created_at = last_calls.last_call
WHERE co.customer_id = ?
GROUP BY co.id
It looks like I found the answer.
This one gives the correct result, and still fast enough (0.27 seconds)
SELECT co.*, v.*
FROM companies co
LEFT JOIN
(
SELECT
ca.*
FROM calls ca
JOIN
(
SELECT
company_id,
MAX(created_at) AS max_created_at
FROM calls
GROUP BY company_id
) t
ON ca.company_id = t.company_id AND ca.created_at = t.max_created_at
GROUP BY company_id
) v ON co.id = v.company_id
Thanks everybody!
You should make a subselect to get max created from calls and use that as join condition to company and calls tables.
SELECT
co.id AS company_id,
co.name AS company_name,
ca.id AS lastcall_id,
ca.result AS lastcall_result
FROM companies AS co
LEFT JOIN calls AS ca
ON co.id = ca.company_id
INNER JOIN
(
SELECT
company_id,
MAX(created_at) AS max_created_at
FROM calls
GROUP BY company_id
) AS max_created_per_company
ON ca.company_id = max_created_per_company.company_id
AND ca.created_at = max_created_per_company.created_at
WHERE co.customer_id = ?
You could double join to the lastcall table, like in this example:
select companies.id, companies.name , lastcall.id, lastcall.result from companies
inner join (select max(created_at) as lastcall, company_id from calls group by company_id) maxcalls
on (companies.id = maxcalls.company_id)
inner join lastcall on (lastcall = created_at and companies.id = lastcall.company_id)
where customer_id = ?
select companies.* , ca.* from companies
inner join (select max(created_at) as lastcall, company_id from calls group by company_id) maxcalls on (companies.id = maxcalls.company_id)
inner join calls ca on (maxcalls.lastcall = ca.created_at and companies.id = ca.company_id)
where customer_id = ?