Mysql 5.7 - get multiple values from sub select - mysql

In the query below I am "JOINING" another table where i.isPrimary > 0 and if all i.isPrimary are 0 I just get the first result.
The result set from the query is as expected, but I want to bring more values from each subselect.
I am getting the error: SQL Error (1241): Operand should contain 1 column(s).
How can this query be rewritten in order to get more results from each subselect?
Thanks
-- borrowed from https://stackoverflow.com/q/7745609/808921
CREATE TABLE IF NOT EXISTS `ResearchEntity` (
`id` int(6) unsigned NOT NULL,
`name` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `ResearchEntity` (`id`, `name`) VALUES
('1', 'one'),
('2', 'two'),
('3', 'three');
CREATE TABLE IF NOT EXISTS `ProfileImageEntity` (
`id` int(6) unsigned NOT NULL,
`isPrimary` int(1) unsigned NOT NULL,
`value` varchar(200) NOT NULL,
`researchId` int(2) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `ProfileImageEntity` (`id`,`isPrimary`, `value`,`researchId`) VALUES
('1', 0, 'not primary',1),
('2', 0, 'not primary',1),
('3', 1, 'primary!!!',1),
('4', 0, 'primary!!!',2),
('5', 0, 'not primary',2),
('6', 0, 'not primary',2)
;
CREATE TABLE IF NOT EXISTS `UserNameEntity` (
`id` int(6) unsigned NOT NULL,
`isPrimary` int(1) unsigned NOT NULL,
`value` varchar(200) NOT NULL,
`researchId` int(2) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `UserNameEntity` (`id`,`isPrimary`, `value`,`researchId`) VALUES
('1', 0, 'first one, should be returned',1),
('2', 0, 'not primary',1),
('3', 0, 'primary',1),
('4', 1, 'primary',3),
('5', 0, 'not primary',3),
('6', 0, 'not primary',3);
SQL FIDDLE
: http://sqlfiddle.com/#!9/028218/1
SELECT r.*,
(SELECT i.id FROM ProfileImageEntity i WHERE i.researchId = r.id ORDER BY i.isPrimary DESC, i.id ASC LIMIT 1 ) AS primaryImageId,
(SELECT i.id FROM UserNameEntity i WHERE i.researchId = r.id ORDER BY i.isPrimary DESC, i.id ASC LIMIT 1 ) AS primaryImageId
FROM ResearchEntity r
ORDER BY id DESC;

What I understood from your question and comment that you want more columns from sub Query which is not possible. So try this query:
It is easy in MySql 8 but you are using MySql 5.7 where it a little bit tricky So try this:
select
t1.*,
t2.id AS primaryImageId,
t2.value AS primaryImageValue,
t3.id AS primaryUserId,
t3.value AS primaryUserValue
from ResearchEntity t1
left join (
SELECT *,
IF(researchId=#last,#_seq:=#_seq+1,#_seq:=1) AS rn,
#last:=researchId
FROM ProfileImageEntity , (SELECT #_seq:=1, #last:=0) r
ORDER BY researchId,isPrimary DESC, id ASC
) t2 on t1.id=t2.researchId and t2.rn=1
left join (
SELECT *,
IF(researchId=#last,#_seq:=#_seq+1,#_seq:=1) AS rn,
#last:=researchId
FROM UserNameEntity , (SELECT #_seq:=1, #last:=0) r
ORDER BY researchId,isPrimary DESC, id ASC
) t3 on t1.id=t3.researchId and t3.rn=1
order by t1.id
DEMO
In MySql 8 using row_number()
with cte as (
SELECT *,
row_number() over (partition by researchId ORDER BY isPrimary DESC, id ASC) rn
FROM ProfileImageEntity
),
cte1 as (
sELECT *,
row_number() over (partition by researchId ORDER BY isPrimary DESC, id ASC) rn
FROM UserNameEntity
)
select
t1.*,
t2.id AS primaryImageId,
t2.value AS primaryImageValue,
t3.id AS primaryUserId,
t3.value AS primaryUserValue
from ResearchEntity t1 left join cte t2 on t1.id=t2.researchId and t2.rn=1
left join cte1 t3 on t1.id=t3.researchId and t3.rn=1

try left join
SELECT r.*,i.id FROM ResearchEntity r left join ProfileImageEntity i on r.id = i.researchId
ORDER BY i.isPrimary,i.id DESC;

you just need to left join 2 times
SELECT r.*,i.id,j.id FROM ResearchEntity r left join ProfileImageEntity i on r.id = i.researchId left join UserNameEntity j on r.id=j.researchId ORDER BY i.isPrimary,i.id DESC;

Related

Average values from different table on join

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.

MySQL limit result based on value in joined table

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

Mysql compare sum of columns to columns in another table

How can I select a row from another table based on the sum of column from the left table
SELECT Group_concat(c.cartid SEPARATOR ',') AS CartIDs,
Sum(c.grandtotal) AS Sum,
r.percentage
FROM carts c
LEFT JOIN rebates r
ON Sum(c.grandtotal) >= r.fromamountpurchased
AND Sum(c.grandtotal) <= r.toamountpurchased
WHERE c.ispaid = '1'
AND c.addedtorebates = '0'
GROUP BY c.customerid
But this doesn't work. I also tried HAVING also doesn't work.
Is it possible in one query?
Thanks!
UPDATE:
CREATE TABLE IF NOT EXISTS `carts` (
`CartID` bigint(20) NOT NULL AUTO_INCREMENT,
`CustomerID` bigint(20) NOT NULL,
`GrandTotal` decimal(10,2) NOT NULL,
`IsPaid` enum('0','1','2') NOT NULL,
`AddedToRebates` enum('0','1') NOT NULL,
PRIMARY KEY (`CartID`)
)
INSERT INTO `carts` (`CartID`, `CustomerID`, `GrandTotal`, `IsPaid`,
`AddedToRebates`, ) VALUES
(71, 28, '57450.00', '1', '0' ),
(73, 28, '57450.00', '1', '0');
CREATE TABLE IF NOT EXISTS `rebates` (
`RebateID` bigint(20) NOT NULL AUTO_INCREMENT,
`Percentage` varchar(255) NOT NULL COMMENT 'in %',
`FromAmountPurchased` decimal(10,2) NOT NULL,
`ToAmountPurchased` decimal(10,2) NOT NULL,
`CashEquivalent` decimal(10,2) NOT NULL,
PRIMARY KEY (`RebateID`)
)
INSERT INTO `rebates` (`RebateID`, `Percentage`, `FromAmountPurchased`,
`ToAmountPurchased`, `CashEquivalent`) VALUES
(1, '5', '50000.00', '69999.00', '3000.00'),
(2, '10', '70000.00', '79999.00', '5000.00'),
(3, '15', '80000.00', '89999.00', '6000.00'),
(4, '20', '90000.00', '99999.00', '7000.00'),
(5, '25', '100000.00', '150000.00', '8000.00'),
(6, '0', '0.00', '49999.00', '0.00');
Try this:
select q1.CartIDs, q1.total, r.percentage
from
(select group_concat(c.cartid) as CartIDs, sum(c.grandtotal) as total
from carts c
where c.ispaid = '1'
and c.addedtorebates = '0'
group by c.customerid ) q1
left join rebates r
on q1.total >= r.fromamountpurchased
and q1.total <= r.toamountpurchased
Here is a demo fiddle for you: http://sqlfiddle.com/#!9/d27f5/3
You cannot use aggregate functions like SUM() in the join predicate, so in this instance, a subquery is useful
You can achieve your result with a sub query. Please note that this sub query requires an additional scan of carts.
SELECT GROUP_CONCAT(c.CartID SEPARATOR ',') AS CartIDs, SUM(c.GrandTotal) as Sum, r.Percentage
FROM carts c
INNER JOIN (
SELECT SUM(GrandTotal) as grandTotal, CustomerID
FROM carts
GROUP BY CustomerID
) cSums ON cSums.CustomerID = c.CustomerID
LEFT JOIN rebates r ON cSums.grandTotal >= r.FromAmountPurchased AND cSums.grandTotal <= r.ToAmountPurchased
WHERE c.IsPaid = '1' AND c.AddedToRebates = '0' GROUP BY c.CustomerID

SQL: Take only the latest entry for each "group"?

Here I created some table to test with:
CREATE TABLE IF NOT EXISTS `test` (
`index` varchar(255) NOT NULL,
`index2` varchar(255) NOT NULL,
`date` date NOT NULL,
`somenumber` int(10) NOT NULL,
PRIMARY KEY (`index`,`index2`,`date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `test` (`index`, `index2`, `date`, `somenumber`) VALUES
('kevin', 'little', '2013-06-11', 1),
('kevin', 'little', '2013-07-03', 5),
('maria', 'smith', '2013-07-01', 3),
('martin', 'luther', '2013-07-04', 13),
('martin', 'luther', '2013-07-05', 14);
Now I want to get the latest somenumber for everyone, ordered by somenumber DESC. Here is my attempt:
SELECT * FROM `test` GROUP BY `index`, `index2` ORDER BY `somenumber` DESC
The problem is that this query does always take one somenumber for every group, but it's not always the latest.
(I know the indexnames don't make too much sense here, but I thought it would be an easier-to-read example than using random number-indexes)
Hope this will help you
SELECT *
FROM
(SELECT * FROM `test` ORDER BY `index`, `date` DESC) as temp
GROUP BY `index`, `index2`
ORDER BY `somenumber` DESC
Try this:
SELECT t1.`INDEX`, t1.`INDEX2`, t1.`SOMENUMBER` FROM TEST t1 INNER JOIN
(SELECT `INDEX`, `INDEX2`, MAX(`DATE`) DATE FROM TEST
GROUP BY `INDEX`, `INDEX2`) t2 ON t1.INDEX = t2.INDEX AND t1.INDEX2 = t2.INDEX2 AND t1.DATE = t2.DATE

Missing SQL entries when combining LEFT/INNER JOIN + GROUP BY

I have the following MySQL structure (minimized a lot):
CREATE TABLE `site_movies` (
`id` int(10),
`title` varchar(90),
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
INSERT INTO `site_movies` VALUES(1, 'Borrowers, The');
INSERT INTO `site_movies` VALUES(2, 'Louis C.K.: Chewed Up');
INSERT INTO `site_movies` VALUES(3, 'Louis C.K.: Shameless');
INSERT INTO `site_movies` VALUES(4, 'Vinni-Pukh');
CREATE TABLE `site_movies_directors` (
`id` mediumint(8),
`name` varchar(255),
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
CREATE TABLE `site_movies_directors_connections` (
`movie_id` mediumint(8),
`director_id` mediumint(8)
) ENGINE=MyISAM;
CREATE TABLE `site_movies_seen` (
`object_id` int(10),
`date` varchar(10),
`rating` tinyint(2)
) ENGINE=MyISAM;
INSERT INTO `site_movies_seen` VALUES(1, '0', 4);
INSERT INTO `site_movies_seen` VALUES(2, '1293821757', 5);
INSERT INTO `site_movies_seen` VALUES(3, '1293821758', 7);
INSERT INTO `site_movies_seen` VALUES(4, '0', 6);
And then the following query (also minimized a lot):
SELECT m.title, s.date
FROM site_movies_seen s
INNER JOIN site_movies m ON s.object_id = m.id
LEFT JOIN site_movies_directors_connections AS mdc ON ( m.id = mdc.movie_id )
GROUP BY mdc.movie_id, s.date
ORDER BY s.date ASC
Prints:
title date
Borrowers, The 0
Louis C.K.: Chewed Up 1293821757
Louis C.K.: Shameless 1293821758
Notice that "Vinni-Pukh" is missing because it is the second entry in the _seen table with date = 0. How can I include all entires, even when several entires have the same timestamp?
Change your group by statement to this:
GROUP BY m.id, s.date
Your join condition says m.id = mdc.movie_id, so you might think that these two fields are always equal and therefore it doesn't matter whether you write m.id or mdc.movie_id in your GROUP BY. This is not true because you are using a LEFT JOIN, not an INNER JOIN. This means that mdc.movie_id can be NULL, and all NULL entries go into the same group.
Also, since you aren't selecting any columns from the site_movies_directors_connections you should omit it from the query completely.
SELECT m.title, s.date
FROM site_movies_seen s
INNER JOIN site_movies m ON s.object_id = m.id
GROUP BY m.id, s.date
ORDER BY s.date