Average values from different table on join - mysql

CREATE TABLE `reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `reviews` (`id`, `average`, `house_id`) VALUES
(1, '10.00', 1),
(2, '10.00', 1);
ALTER TABLE `reviews`
ADD PRIMARY KEY (`id`);
ALTER TABLE `reviews`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
CREATE TABLE `dummy_reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `dummy_reviews` (`id`, `average`, `house_id`) VALUES
(0, '2.00', 1);
ALTER TABLE `dummy_reviews`
ADD PRIMARY KEY (`id`);
AND the query
SELECT
AVG(r.average) AS avg1,
AVG(dr.average) AS avg2
FROM
reviews r
LEFT JOIN
dummy_reviews dr ON r.house_id = dr.house_id
the result is
avg1 avg2
10.000000 2.000000
All good by now but (10 + 2) / 2 = 6 ... wrong result
I need (10+10+2) / 3 = 7,33 ... How can I get this result?
SQLFiddle

You have values joined and as such you wont have 3 rows, you will have 2. What you need is a union so you can have all rows from your average tables and do the calculation from it. Like this:
select avg(average) from
(select average from reviews
union all
select average from dummy_reviews
) queries
See it here: http://sqlfiddle.com/#!9/e0b75f/3

Jorge's answer is the simplest approach (and I duly upvoted it). In response to your comment, you can do the following:
select ( (coalesce(r.suma, 0) + coalesce(d.suma, 0)) /
(coalesce(r.cnt, 0) + coalesce(d.cnt, 0))
) as overall_average
from (select sum(average) as suma, count(*) as cnt
from reviews
) r cross join
(select sum(average) as suma, count(*) as cnt
from dummy_reviews
) d;
Actually, I suggest this not only because of your comment. Under some circumstances, this could be the better performing code.

Related

3 tables joins on One to many

Im having some troubles learning about joins, im working with 2 One-to-many relation ships:
In this case, i have novels with many chapters and many ratings
I need to get the novels information plus a count of chapters associated to each novel and an avarage of the ratings of each novel and im trying this:
SELECT n.id
, n.nvl_title
, COUNT(c.id) AS nvl_chapters
, AVG(nr.rate_value) as nvl_rating
, MAX(c.createdAt) AS nvl_last_update
FROM novels n
left
JOIN novels_ratings nr
ON nr.novel_id = n.id
left
JOIN chapters c
ON c.nvl_id = n.id
AND c.chp_status = 'Active'
WHERE n.nvl_status IN ("Active", "Finished")
GROUP
BY n.id;
Working only with the chapters the query seems to work very fine but if I add the line "left JOIN novels_ratings nr ON nr.novel_id = n.id" the chapters count increment to many ratings the novel have.
For example: A novel with 2 chapters and 2 rating returns 4 chapters in total.
Any help will be fully apreciated.
If there is something I miss to explain, please, let me know and i will try to clarify.
I'veen working with some ugly querys that do the job but as soon as the chapters table begin to have MANY registers I have been forced to learn more optical querys
EDIT
I have create a small database, enough to make some tests on the query:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE `chapters` (
`id` int(11) NOT NULL,
`nvl_id` int(20) DEFAULT NULL,
`chp_title` varchar(250) DEFAULT NULL,
`chp_status` varchar(8) NOT NULL DEFAULT 'Active',
`createdAt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `chapters` (`id`, `nvl_id`, `chp_title`, `chp_status`, `createdAt`) VALUES
(1, 1, 'generic chapter 1', 'Active', '0000-00-00 00:00:00'),
(2, 1, 'generic chapter 2', 'Active', '0000-00-00 00:00:00');
CREATE TABLE `novels` (
`id` int(20) NOT NULL,
`nvl_title` varchar(250) DEFAULT NULL,
`nvl_status` varchar(8) NOT NULL DEFAULT 'Active'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `novels` (`id`, `nvl_title`, `nvl_status`) VALUES
(1, 'generic novel', 'Active');
CREATE TABLE `novels_ratings` (
`id` int(20) NOT NULL,
`novel_id` int(20) DEFAULT NULL,
`rate_value` int(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `novels_ratings` (`id`, `novel_id`, `rate_value`) VALUES
(1, 1, 3),
(2, 1, 4);
ALTER TABLE `chapters`
ADD PRIMARY KEY (`id`);
ALTER TABLE `novels`
ADD PRIMARY KEY (`id`);
ALTER TABLE `novels_ratings`
ADD PRIMARY KEY (`id`);
ALTER TABLE `chapters`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
ALTER TABLE `novels`
MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `novels_ratings`
MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;
Thank you very much!
I think the simplest way to do this is here:
SELECT n.id, n.nvl_title,
COUNT(c.id) AS nvl_chapters,
(select AVG(nr.rate_value) from novels_ratings nr where nr.novel_id = n.id) as nvl_rating,
MAX(c.createdAt) AS nvl_last_update
FROM novels n
left JOIN chapters c ON c.nvl_id = n.id AND c.chp_status = 'Active'
WHERE n.nvl_status IN ("Active", "Finished")
GROUP BY n.id;
Very straightforward, and it should perform well too.
This is a complete solution (finally). Since MySQL does not implement FULL JOIN the solution uses a LEFT JOIN paired with a RIGHT JOIN instead.
You can do:
with
r as (
select n.id, avg(nr.rate_value) as nvl_rating
from novels n
join novels_ratings nr on nr.novel_id = n.id
group by n.id
),
c as (
select n.id, count(c.id) as nvl_chapters, max(c.createdAt) as nvl_last_update
from novels n
join chapters c on c.nvl_id = n.id and c.chp_status = 'Active'
group by n.id
)
select r.id, r.nvl_rating, c.*
from r
left join c on c.id = r.id
UNION ALL
select c.id, r.nvl_rating, c.*
from r
right join c on c.id = r.id
where r.id is null

MySQL - GROUP results BY a single column alongside ORDER BY and LIMIT

I want to select a list of tasks from my database. The tasks have a category_id. I want to get a singlur task per category_id. So if I, for example, had 10 tasks that are linked to like 6 categories that would result in 6 results. The 6 results I want are determined by their id, the lowest id among the GROUP BY is the correct record for that GROUP. Also the maximum result set can be no larger than 20 ('LIMIT').
SELECT * FROM `task` WHERE `datetime`<NOW() `task_status_id`=1 GROUP BY `category_id` ORDER BY `id` ASC LIMIT 20
What is wrong with the above query, I got no clue, I'm also at a loss getting any google results for this.
ADDED LATER
http://sqlfiddle.com/#!9/fa39cf
CREATE TABLE `category` (
`id` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `category` (`id`) VALUES
(1),
(2),
(3);
CREATE TABLE `task` (
`id` int(10) UNSIGNED NOT NULL,
`category_id` int(10) UNSIGNED NOT NULL,
`task_status_id` int(10) UNSIGNED DEFAULT '1',
`datetime` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `task` (`id`, `category_id`, `task_status_id`, `datetime`) VALUES
(3, 2, 1, '2018-07-24 11:20:26'),
(4, 2, 1, '2018-07-24 11:20:26'),
(5, 3, 1, '2018-07-24 11:21:35'),
(6, 3, 1, '2018-07-24 11:21:35');
You can try first finding the smallest id for each category and then joining it with the task table to get the remaining details.
SELECT t.* FROM task t
JOIN (SELECT category_id, min(id) id from task group by category_id) tc
ON (t.id = tc.id)
LIMIT 20

MySQL join three tables, sum of item join to main table

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.

{mysql} select with a cross join

im currently a little bit confused about my sql select.
i got something like:
CREATE TABLE IF NOT EXISTS `a` (
`id` int(11) NOT NULL,
`ip` int(11) unsigned NOT NULL,
`name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
with data of:
INSERT INTO `a` (`id`, `ip`, `name`) VALUES
(1, 2147483647, 'foobar'),
(2, 2372224735, 'foobar2');
so i would like append another table to the result of
select * from a
means:
select * from a cross join (select * from b where ip <= a.ip order by ip desc limit 1)
but it isnt working, i have no idea how to fix it :/
any ideas?
Thanks advance!
select * from a cross join (select * from b where ip <= a.ip order by ip desc limit 1) aaa
I just addded aaa to be the name of the derived table which is required even if it is not referenced.

Dynamic IN+IF clause with GROUP_CONCAT?

I am filtering buyers (many) for a given property (one) with the main condition of
if the buyer requires specific exact # of bathrooms (buyers.bathroom_exact='1'), match it to the property's bathroom value.
Buyers can select multiple exact bathroom matches. Here's a sample schema:
CREATE TABLE `buyers` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`bathroomreq` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `buyers_bathrooms` (
`buyer_id` int(10) unsigned NOT NULL DEFAULT '0',
`number` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `buyers` ( name, bathroomreq ) VALUES ( 'John Doe', 1);
INSERT INTO `buyers` ( name, bathroomreq ) VALUES ( 'John Smith', 1);
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 8 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 9 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 10 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 2, 5 );
The only way I can think of doing this is using a column which has an IF clause to see if the bathroomreq is 1, then match the group_concat of the buyers specified bathrooms:
SELECT IF ( bathroomreq = '1', 8 IN (
SELECT GROUP_CONCAT(number) AS bathrooms
FROM buyers_bathrooms
WHERE buyers.id = buyers_bathrooms.buyer_id
) , 1 ) AS bathroom_match,
bathroomreq,
id, name
FROM buyers
So this property basically has 8 bathrooms and that's why 8 is hardcoded in there.
Here's a mysql fiddle:
http://sqlfiddle.com/#!2/508cc/1
Is this the only way of solving the problem and is it reliable? Can I do it an alternative way with something like a dynamic INNER JOIN if bathroomreq is 1? Basically, can I avoid having to branch based on bathroom_match in my application code and filter this elegantly through SQL alone?
I think you can do what you want with an exists clause:
select b.bathroomreq, b.id, b.name,
(b.bathroomreq <> 1 or
exists (select 1
from buyers_bathrooms bb
where bb.buyer_id = b.id and bb.number = 8
)
) as bathroom_match
from buyers b;