Related
I have two tables, the first one contains a limit column. The number in this column must be used to limit the number of records received from the second table.
Is it possible to do this in just one query?
Below my tables and DEMO:
# Create table a
CREATE TABLE `a` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`limit` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
# Create table b
CREATE TABLE `b` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`master` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
# Fill table a
INSERT INTO `a` (`id`, `limit`)
VALUES
(1, 3);
# Fill table b
INSERT INTO `b` (`id`, `name`, `master`)
VALUES
(1, 'record 1', 'groupA'),
(2, 'record 2', 'groupB'),
(3, 'record 3', 'groupA'),
(4, 'record 4', 'groupB'),
(5, 'record 5', 'groupC'),
(6, 'record 6', 'groupC'),
(7, 'record 7', 'groupC'),
(8, 'record 8', 'groupA'),
(9, 'record 9', 'groupD'),
(10, 'record 10', 'groupD');
Query I tested:
SELECT b.*
FROM b
JOIN a ON a.id = 1
GROUP BY b.master
LIMIT 3
This selects only 3 records.
But now I want the limit to be read from table a. I tried to limit like this, but that fails:
SELECT b.*
FROM b
JOIN a ON a.id = 1
GROUP BY b.master
LIMIT a.limit
EDIT:
I've updated the question including the group by statement
You cannot use user-defined MySQL variables or table fields in the LIMIT clause. What you can do is use a variable to enumerate records of table b. Then use this variable to apply the limit:
SELECT t.id, t.name
FROM (
SELECT id, name, #rn := #rn + 1 AS rn
FROM b
CROSS JOIN (SELECT #rn := 0) AS v
ORDER BY id) AS t
INNER JOIN a ON a.id = 1 AND t.rn <= a.`limit`;
Demo here
Edit:
Here's a version that handles groups. It limits the records of b to those groups having the biggest population:
SELECT b.id, b.name, b.master
FROM b
INNER JOIN (
SELECT master, #rn := #rn + 1 AS rn
FROM b
CROSS JOIN (SELECT #rn := 0) AS v
GROUP BY master
ORDER BY COUNT(*) DESC) AS t ON b.master = t.master
INNER JOIN a ON a.id = 1 AND t.rn <= a.`limit`;
Demo here
I have three MySQL tables:
For example
A Table is menu ID, name
B table is customer_order ID, order_date
C table is order_item ID, menu_item_id, customer_order_id, order_quantity
I try to output name, sum(order_quantity) in this month
Currently i have two separate query which working ok, but the second query is inside of foreach loop, which seem not so good.
First query which output all the menu items:
$results = $wpdb->get_results( "SELECT * FROM menu WHERE post_id = $pid ORDER BY sort_order ");
Second query will output total of each item sold on each month:
$total = $wpdb->get_col( "SELECT SUM(oi.order_item_quantity)
from order_item as oi
INNER JOIN customer_order as ho ON ho.ID = oi.order_id
WHERE oi.order_item_id = $subC->ID AND YEAR(ho.order_date) = $current_year AND MONTH(ho.order_date) = $current_month ");
I try to merge the two queries into one query, which has taken me whole day but still not able to solve it, can anyone give me some help please.
update
thanks Rene.
Select m.name, m.name as name, sum(oi.order_item_quantity) as sold_monthly from menu as m left join order_item as oi on oi.order_item_id = m.ID left join cusomter_order as co on co.ID = oi.order_id where m.post_id = 110 group by m.ID, m.name
this will output
name sold_monthly
Sushi Lunch Special NULL
Sushi Lunch 19
Sashimi Lunch 61
jason NULL
egg roll NULL
if i add YEAR(co.order_date) = 2016 AND MONTH(co.order_date) = 9
which i only get
name sold_monthly
Sushi Lunch 7
Sashimi Lunch 14
how can i keep sushi lunch special, jason, egg roll, the null item, when i add the YEAR(co.order_date) = 2016 AND MONTH(co.order_date) = 9.
here i try
(year(co.order_date) = 2016 and month(co.order_date) = 10) or sold_monthly is null
which give me a query error
update
thanks Rene again
it's working now
(year(co.order_date) = 2016 and month(co.order_date) = 10) or co.order_date is null
finally solve it, upper have little bug, when i change business_id which may not catch the result i want, so i am add a subquery to it.
Select m.*, p.sold_monthly from menu as m left join ( SELECT SUM(oi.order_item_quantity) as sold_monthly, oi.order_item_id as ID, oi.order_item_name from order_item as oi LEFT JOIN cusomter_order as ho ON ho.ID = oi.order_id WHERE ho.business_id = $pid AND (year(ho.order_date) = $current_year and month(ho.order_date) = $current_month) OR ho.order_date is NULL GROUP by oi.order_item_id )p on p.ID = m.ID where m.post_id = $pid
So you're trying to get a list per post_id limited by the selected month.
The following query will yield that for the following sample data.
SELECT m.ID as ID, m.Name as Name, SUM(oi.order_quantity) as Quantity
FROM menu as m
LEFT JOIN order_item as oi ON oi.menu_item_id = m.ID
LEFT JOIN customer_order as co ON co.ID = oi.customer_order_id
WHERE m.post_id = 0 AND YEAR(co.order_date) = 2016 AND MONTH(co.order_date) = 9 OR co.order_date is NULL
GROUP BY m.ID,m.Name,m.sort_order
ORDER BY m.sort_order
Sample Data
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
DROP TABLE IF EXISTS `customer_order`;
CREATE TABLE `customer_order` (
`ID` int(11) NOT NULL,
`order_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_german2_ci;
TRUNCATE TABLE `customer_order`;
INSERT INTO `customer_order` (`ID`, `order_date`) VALUES
(1, '2016-09-06 00:00:00'),
(2, '2016-09-13 00:00:00'),
(3, '2016-08-09 00:00:00'),
(4, '2016-09-19 00:00:00');
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`ID` int(11) NOT NULL,
`sort_order` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`Name` varchar(20) COLLATE utf8_german2_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_german2_ci;
TRUNCATE TABLE `menu`;
INSERT INTO `menu` (`ID`, `sort_order`, `post_id`, `Name`) VALUES
(2, 0, 0, 'Test 1'),
(4, 1, 0, 'Test 2'),
(5, 2, 0, 'Test 3');
DROP TABLE IF EXISTS `order_item`;
CREATE TABLE `order_item` (
`ID` int(11) NOT NULL,
`menu_item_id` int(11) NOT NULL,
`customer_order_id` int(11) NOT NULL,
`order_quantity` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_german2_ci;
TRUNCATE TABLE `order_item`;
INSERT INTO `order_item` (`ID`, `menu_item_id`, `customer_order_id`, `order_quantity`) VALUES
(1, 2, 1, 1),
(2, 2, 2, 3),
(3, 4, 1, 1),
(4, 4, 2, 4),
(5, 2, 3, 3),
(6, 4, 3, 1),
(7, 2, 4, 4);
ALTER TABLE `customer_order`
ADD PRIMARY KEY (`ID`);
ALTER TABLE `menu`
ADD PRIMARY KEY (`ID`),
ADD KEY `idx_pid` (`post_id`);
ALTER TABLE `order_item`
ADD PRIMARY KEY (`ID`),
ADD KEY `idx_coid` (`customer_order_id`),
ADD KEY `idx_miid` (`menu_item_id`);
ALTER TABLE `customer_order`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
ALTER TABLE `menu`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
ALTER TABLE `order_item`
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8;
ALTER TABLE `order_item`
ADD CONSTRAINT `CostomerOrderConstrain` FOREIGN KEY (`customer_order_id`) REFERENCES `customer_order` (`ID`),
ADD CONSTRAINT `MenuItemConstrain` FOREIGN KEY (`menu_item_id`) REFERENCES `menu` (`ID`);
Good luck integrating the query, let me know if it worked.
Update: Updated sample data to reproduce the actual problem. Updated the Solution Query.
I have 3 tables, but data is only fetch from 2 tables.
I'm trying to get the lowest bids for selected items and display user name with the lowest bid.
Currently query works until when we display user name, it shows wrong user name, which does not match the bid.
Below is working example of structure and query.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE `bid` (
`id` int(11) NOT NULL,
`amount` float NOT NULL,
`user_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
INSERT INTO `bid` (`id`, `amount`, `user_id`, `item_id`) VALUES
(1, 9, 1, 1),
(2, 5, 2, 1),
(3, 4, 3, 1),
(4, 3, 4, 1),
(5, 4, 2, 2),
(6, 22, 5, 1);
-- --------------------------------------------------------
CREATE TABLE `item` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `item` (`id`, `name`) VALUES
(1, 'chair'),
(2, 'sofa'),
(3, 'table'),
(4, 'box');
-- --------------------------------------------------------
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `user` (`id`, `name`) VALUES
(1, 'James'),
(2, 'Don'),
(3, 'Hipes'),
(4, 'Sam'),
(5, 'Zakam');
ALTER TABLE `bid`
ADD PRIMARY KEY (`id`);
ALTER TABLE `item`
ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
ADD PRIMARY KEY (`id`);
ALTER TABLE `bid`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=7;
ALTER TABLE `item`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
Query 1:
SELECT b.id, b.item_id, MIN(b.amount) as amount, b.user_id, p.name
FROM bid b
LEFT JOIN user p ON p.id = b.user_id
WHERE b.item_id in (1, 2)
GROUP BY b.item_id
ORDER BY b.amount, b.item_id
Results:
| id | item_id | amount | user_id | name |
|----|---------|--------|---------|-------|
| 5 | 2 | 4 | 2 | Don |
| 1 | 1 | 3 | 1 | James |
Explanation of query:
Get the selected items (1, 2).
get the lowest bid for thous items - MIN(b.amount)
display user names, who has given the bid - LEFT JOIN user p on p.id = b.user_id (this is not working or I'm doing something wrong)
[Note] I can't use sub-query, I'm doing this in doctrine2 (php code) which limits mysql sub-query
No, you are not necessarily fetching the user_id who has given the bid. You group by item_id, so you get one result row per item. So you are aggregating and for every column you say what value you want to see for that item. E.g.:
MIN(b.amount) - the minimum amount of the item's records
MAX(b.amount) - the maximum amount of the item's records
AVG(b.amount) - the avarage amount of the item's records
b.amount - one of the amounts of the item's records arbitrarily chosen (as there are many amounts and you don't specify which you want to see, the DBMS simply choses one of them)
This said, b.user_id isn't necessarily the user who made the lowest bid, but just one random user of the users who made a bid.
Instead find the minimum bids and join again with your bid table to access the realted records:
select bid.id, bid.item_id, bid.amount, user.id as user_id, user.name
from bid
join
(
select item_id, min(amount) as amount
from bid
group by item_id
) as min_bid on min_bid.item_id = bid.item_id and min_bid.amount = bid.amount
join user on user.id = bid.user_id
order by bid.amount, bid.item_id;
You can solve this using a subquery. I am not 100% sure if this is the most efficient way, but at least it works.
SELECT b1.id, b1.item_id, b1.amount, b1.user_id, p.name
FROM bid b1
LEFT JOIN user p ON p.id = b1.user_id
WHERE b1.id = (
SELECT b2.id
FROM bid b2
WHERE b2.item_id IN (1, 2)
ORDER BY b2.amount LIMIT 1
)
This first selects for the lowest bid with for item 1 or 2 and then uses the id of that bid to find the information you need.
Edit
You are saying that Doctrine does not support subqueries. I have not used Doctrine a lot, but something like this should work:
$subQueryBuilder = $entityManager->createQueryBuilder();
$subQuery = $subQueryBuilder
->select('b2.id')
->from('bid', 'b2')
->where('b2.item_id IN (:items)')
->orderBy('b2.amount')
->setMaxResults(1)
->getDql();
$queryBuilder = $entityManager->createQueryBuilder();
$query = $queryBuilder
->select('b1.id', 'b1.item_id', 'b1.amount', 'b1.user_id', 'p.name')
->from('bid', 'b1')
->leftJoin('user', 'p', 'with', 'p.id = b1.user_id')
->where('b1.id = (' . $subQuery . ')')
->setParameter('items', [1, 2])
->getQuery()->getSingleResult();
This is more of a concept question I guess.
* discussion
discussionId
message
timestamp
* comment
commentId
message
timestamp
* tweet
tweetId
message
timestamp
Then on a page I list the first let's say 10 entries and with next will load the next 10. All normal there.
My question is: Is there a way to limit each query of the UNION or I can only do the LIMIT/OFFSET on the result of the UNION? I'm pondering because I was imagining if each table has like 1K (I know the number is very low), the query would return 3K results and then grab only 10. When the number of rows gets bigger, it wouldn't slow down the performance? Or even if in the future there are more tables that I want to do the UNION, it wouldn't be better to get x of each one of then and in the end get the desired 10? But I can't figure it out how I'll know which one was the last one of the limit/offset of the queries... so I'm afraid I would have to select all and use the limit on the UNION.
So instead of using
(SELECT
message,
timestamp
FROM
discussion
)
UNION
(SELECT
message,
timestamp
FROM
comment
)
UNION
(SELECT
message,
timestamp
FROM
tweet
)
LIMIT $offset, $limit
Is it possible to have
(SELECT
message,
timestamp
FROM
discussion
LIMIT $offset, $limit
)
UNION
(SELECT
message,
timestamp
FROM
comment
LIMIT $offset, $limit
)
UNION
(SELECT
message,
timestamp
FROM
tweet
LIMIT $offset, $limit
)
LIMIT $offset, $limit
Thanks for your help!
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Message
(`id` int, `type_id` int, `message` varchar(10), `timestamp` int)
;
INSERT INTO Message
(`id`, `type_id`, `message`, `timestamp`)
VALUES
(1, 1, 'message1', 12345678),
(2, 2, 'message2', 12345679),
(3, 3, 'message3', 12345680)
;
CREATE TABLE Type
(`id` int, `type` varchar(10))
;
INSERT INTO Type
(`id`, `type`)
VALUES
(1, 'discussion'),
(2, 'comment'),
(3, 'tweet')
;
CREATE TABLE DiscussionDetail
(`id` int, `message_id` int, `from_user` varchar(10), `to_user` varchar(10))
;
INSERT INTO DiscussionDetail
(`id`, `message_id`, `from_user`, `to_user`)
VALUES
(1, 1, 'Peter', 'Anna')
;
CREATE TABLE CommentDetail
(`id` int, `message_id` int, `post_id` varchar(5), `user_id` varchar(5))
;
INSERT INTO CommentDetail
(`id`, `message_id`, `post_id`, `user_id`)
VALUES
(1, 2, 'post4', 'user6')
;
CREATE TABLE TweetDetail
(`id` int, `message_id` int, `twitteracct` varchar(6))
;
INSERT INTO TweetDetail
(`id`, `message_id`, `twitteracct`)
VALUES
(1, 3, 'myacct')
;
Query 1:
SELECT Message.*, type,
case when dd.id is not null then from_user
when cd.id is not null then post_id
when td.id is not null then twitteracct else '' end as detail1,
case when dd.id is not null then to_user
when cd.id is not null then user_id else '' end as detail2
FROM Message
INNER JOIN Type ON Type_Id = Type.id
LEFT OUTER JOIN DiscussionDetail dd ON dd.message_id = Message.id AND Type_Id = 1
LEFT OUTER JOIN CommentDetail cd ON cd.message_id = Message.id AND Type_Id = 2
LEFT OUTER JOIN TweetDetail td ON td.message_id = Message.id AND Type_Id = 3
ORDER BY timestamp LIMIT 1,1
Results:
| id | type_id | message | timestamp | type | detail1 | detail2 |
|----|---------|----------|-----------|---------|---------|---------|
| 2 | 2 | message2 | 12345679 | comment | post4 | user6 |
You can also create a VIEW for this :
CREATE VIEW AllMessages AS
SELECT Message.*, type,
case when dd.id is not null then from_user
when cd.id is not null then post_id
when td.id is not null then twitteracct else '' end as detail1,
case when dd.id is not null then to_user
when cd.id is not null then user_id else '' end as detail2
FROM Message
INNER JOIN Type ON Type_Id = Type.id
LEFT OUTER JOIN DiscussionDetail dd ON dd.message_id = Message.id AND Type_Id = 1
LEFT OUTER JOIN CommentDetail cd ON cd.message_id = Message.id AND Type_Id = 2
LEFT OUTER JOIN TweetDetail td ON td.message_id = Message.id AND Type_Id = 3
And then :
SELECT *
FROM AllMessages
ORDER BY timestamp LIMIT 1,1
SQL Fiddle
I like your question and I myself asked that once. I used the second solution of retrieving all and then paginate what I needed. Now, the problem is the performance, so here I will try to improve the performance of retrieving the rows, with several thousands of rows (this solution have to be tested, but I think it will work smoothly).
The tricky performance idea: Create a new table, only with ids of the tables, look for the timestamp (or the column that every table share and you use for ordering) and then filter by that. This way, you will have this:
SELECT insert_time
FROM all_tables_order
ORDER BY insert_time DESC
LIMIT 0,5
From there, you take the boundaries (First and last row result, to take the insert_time, you may see that in http://sqlfiddle.com/#!9/043c1/29), and then you create the big SQL, with that limits:
SELECT *
FROM (
SELECT t.id as id,
t.tweet as message,
t.insert_time as insert_time
FROM tweets t
WHERE insert_time <= '2015-08-06 21:53:30'
AND insert_time >= '2015-08-06 21:51:34'
UNION ALL
SELECT c.id as id,
c.`comment` as message,
c.insert_time as insert_time
FROM comments c
WHERE insert_time <= '2015-08-06 21:53:30'
AND insert_time >= '2015-08-06 21:51:34'
UNION ALL
SELECT m.id as id,
m.message as message,
m.insert_time as insert_time
FROM messages m
WHERE insert_time <= '2015-08-06 21:53:30'
AND insert_time >= '2015-08-06 21:51:34'
) AS myWholeTable
ORDER BY insert_time
This should be quite fast, despite the fact you're doing two queries, because your results are indexed, and you're only ordering small results: http://sqlfiddle.com/#!9/043c1/30
Your database schema should have something like the following, with the triggers there to update the indexes table:
Tables and datas:
CREATE TABLE IF NOT EXISTS `all_tables_order` (
`id` int(11) DEFAULT NULL,
`insert_time` timestamp NULL DEFAULT NULL,
`table_name` enum('comments','tweets','messages') DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `all_tables_order` (`id`, `insert_time`, `table_name`) VALUES
(1, '2015-08-06 21:50:52', 'messages'),
(2, '2015-08-06 21:51:34', 'comments'),
(1, '2015-08-06 21:52:10', 'tweets'),
(2, '2015-08-06 21:52:46', 'messages'),
(2, '2015-08-06 21:53:07', 'tweets'),
(3, '2015-08-06 21:53:30', 'comments'),
(1, '2015-08-03 21:53:39', 'comments');
CREATE TABLE IF NOT EXISTS `comments` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`comment` tinytext CHARACTER SET latin1,
`insert_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `Índice 1` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;
INSERT INTO `comments` (`id`, `comment`, `insert_time`) VALUES
(2, 'c1', '2015-08-06 21:51:34'),
(3, 'c3', '2015-08-06 21:53:30'),
(1, 'c2', '2015-08-03 21:53:39');
CREATE TABLE IF NOT EXISTS `messages` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`message` tinytext,
`insert_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `Índice 1` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `messages` (`id`, `message`, `insert_time`) VALUES
(1, 'm1', '2015-08-06 21:50:52'),
(2, 'm2', '2015-08-06 21:52:46');
CREATE TABLE IF NOT EXISTS `tweets` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`tweet` tinytext,
`created_by` int(11) DEFAULT NULL,
`insert_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
KEY `Índice 1` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;
INSERT INTO `tweets` (`id`, `tweet`, `created_by`, `insert_time`) VALUES
(1, 't1', 23, '2015-08-06 21:52:10'),
(2, 't2', 25, '2015-08-06 21:53:07');
Triggers:
DELIMITER //
CREATE TRIGGER `comments_before_insert` AFTER INSERT ON `comments` FOR EACH ROW BEGIN
insert into all_tables_order set insert_time = new.insert_time, id = new.id, table_name = 'comments';
END//
DELIMITER ;
CREATE TRIGGER `messages_after_insert` AFTER INSERT ON `messages` FOR EACH ROW BEGIN
insert into all_tables_order set insert_time = new.insert_time, id = new.id, table_name = 'messages';
END//
DELIMITER ;
DELIMITER //
CREATE TRIGGER `tweets_after_insert` AFTER INSERT ON `tweets` FOR EACH ROW BEGIN
insert into all_tables_order set insert_time = new.insert_time, id = new.id, table_name = 'tweets';
END//
DELIMITER ;
I have the following sample database set up -
CREATE TABLE IF NOT EXISTS `companies`(
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`company` varchar(75) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `companies` (`id`, `company`) VALUES
(1, 'Acme Widget Company'),
(2, 'Intrepid Inc.'),
(3, 'Allied Corp.');
CREATE TABLE IF NOT EXISTS `companies_customers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`customer_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `companies_customers` (`id`, `company_id`, `customer_id`) VALUES
(1, 2, 1),
(2, 2, 2),
(3, 2, 4),
(4, 1, 3),
(5, 1, 1);
CREATE TABLE IF NOT EXISTS `customers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`firstname` varchar(25) NOT NULL,
`lastname` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `customers` (`id`, `firstname`, `lastname`) VALUES
(1, 'John', 'Smith'),
(2, 'Sue', 'Jones'),
(3, 'David', 'Flanders'),
(4, 'Kathy', 'Freeman');
CREATE TABLE IF NOT EXISTS `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` int(11) NOT NULL,
`amount` decimal(10,0) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `orders` (`id`, `customer_id`, `amount`) VALUES
(1, 1, 500),
(2, 3, 1000),
(3, 1, 250),
(4, 4, 800),
(5, 4, 100);
I need to write a query which retrieves a list of all company names, a count of the number of customers in each company, and a sum of the customers orders in each company, like this -
Company Total Customers All Orders Total
Acme Widget Company 2 750
Intrepid Inc. 3 1650
Allied Corp. 0 0
I nearly have it resolved with the following SQL -
SELECT company AS 'Company', customersCount AS 'Total Customers', customerOrdersTotal AS 'All Orders Total'
FROM
( SELECT cc.customer_id, SUM(innerQuery.ordersTotal) customerOrdersTotal
FROM (SELECT cu.id customerId, SUM(amount) ordersTotal
FROM customers cu
JOIN orders o ON o.customer_id = cu.id
GROUP BY customerId
) innerQuery
JOIN companies_customers cc ON innerQuery.customerId = cc.customer_id
GROUP BY cc.customer_id
) inner_1
RIGHT JOIN
( SELECT cc.id, c.company, COUNT(*) customersCount
FROM companies c
JOIN companies_customers cc ON c.id = cc.company_id
GROUP BY c.id
) inner_2
ON inner_1.customer_id = inner_2.id
It does not print out the company (Allied) without a customer or total. So close, I just need a nudge in the right direction. Thanks.
Since the orders are linked to the companies via the customers, I don't think you need to perform two separate subqueries and join them; rather, I think you can just write:
SELECT companies.company AS "Company",
IFNULL(COUNT(DISTINCT companies_customers.customer_id), 0) AS "Total Customers",
IFNULL(SUM(orders.amount), 0) AS "All Orders Total"
FROM companies
LEFT
JOIN companies_customers
ON companies_customers.company_id = companies.id
LEFT
JOIN orders
ON orders.customer_id = companies_customers.customer_id
GROUP
BY companies.id
;
Edited to add: That said, I have to say that the schema doesn't really make sense to me. You have a many-to-many relationship between customers and companies — so, for example, John Smith is a customer of Acme Widget Company and of Intrepid Inc. — but then orders are just a property of the customer, not of the company. This means that if an order belongs to John Smith, then it necessarily belongs both to Acme Widget Company and to Intrepid Inc.. I don't think that can be right. Instead of having a customer_id field, I think orders needs to have a companies_customers_id field.
I have 3 table that to keep team,tournament_round AND score_team_member about competition TEAM_A Vs TEAM_B ,multiple of round.(1,2,3...n) and multiple of members of team there is score by oneself.This code above are useful very much.
SELECT team.name AS "TEAM",team.id,
IFNULL(COUNT(DISTINCT `tournament_round`.id), 0) AS "TotalWin",
IFNULL(SUM(`score_team_member`.`score`)/(select count(*) from `team_member`where team_id=team.id group by team_id ), 0) AS "ScoreofTeam"
FROM `team`
LEFT
JOIN `tournament_round`
ON `tournament_round`.team_winner_id = `team`.id
LEFT
JOIN `score_team_member`
ON `score_team_member`.team_id = `team`.id
WHERE `team`.thematch_id='6' AND `team`.`category1`='MEP'
GROUP BY `team`.id ORDER by `TotalWin`DESC ,`ScoreofTeam` DESC
Sample out put click here JPG
Table Designer JPG