I have tables (bar, baz) which have 1 or more rows relating to another table (foo). When I join both bar & baz to foo I get results for each row of each table.
http://sqlfiddle.com/#!9/1c13f2/1/0
CREATE TABLE foo (`id` int, `value` varchar(5));
INSERT INTO foo (`id`, `value`) VALUES
(1, 'two'),
(2, 'two'),
(3, 'one');
CREATE TABLE bar (`id` int, `foo_id` int, `value` int);
INSERT INTO bar (`id`, `foo_id`, `value`) VALUES
(1, 1, 1),
(2, 1, 1),
(3, 2, 1),
(4, 2, 1),
(5, 3, 1);
CREATE TABLE baz (`id` int, `foo_id` int, `value` int);
INSERT INTO baz (`id`, `foo_id`, `value`) VALUES
(1, 1, 1),
(2, 1, 1),
(3, 2, 1),
(4, 2, 1),
(5, 3, 1);
The query:
SELECT foo.value, SUM(bar.value), SUM(baz.value)
FROM foo
JOIN bar ON bar.foo_id = foo.id
JOIN baz ON baz.foo_id = foo.id
GROUP BY foo.id
Result:
value SUM(bar.value) SUM(baz.value)
two 4 4
two 4 4
one 1 1
Expected result:
value SUM(bar.value) SUM(baz.value)
two 2 2
two 2 2
one 1 1
The result is the expected behavior, from a cross (semi-Cartesian) product, multiple rows from bar matched to multiple rows from baz.
To avoid this, we can pre-aggregate counts from bar and baz, and then do the join.
Also consider, what result is expected when there are matching rows in bar but no matching rows in baz. Do we want to return the total from bar? With the current query, we wouldn't get total from bar. (In the example data, consider what the query will return after row id=5 is deleted from baz.)
I'd write the query like this:
SELECT foo.value
, IFNULL( r.tot_bar_value ,0) AS tot_bar_value
, IFNULL( z.tot_baz_value ,0) AS tot_baz_value
FROM foo
LEFT
JOIN ( -- aggregate total from bar
SELECT bar.foo_id
, SUM(bar.value) AS tot_bar_value
FROM bar
GROUP BY bar.foo_id
) r
ON r.foo_id = foo.id
LEFT
JOIN ( -- aggregate total from bar
SELECT baz.foo_id
, SUM(baz.value) AS tot_baz_value
FROM baz
GROUP BY baz.foo_id
) z
ON z.foo_id = foo.id
Note that we are using outer joins, to handle the case when there not matching rows in either bar or baz.
For testing, we can run separately just the SELECT query inside the parens, to see what is returned.
Related
I have 3 related tables. Adults, Children and AC. Adults contains an INT column to count high school seniors. Children contains a column with year of highs school graduation. AC links the adult.id to the children.id.
CREATE TABLE adults (
id INT,
name VARCHAR(10),
seniors INT DEFAULT 0
) ;
INSERT INTO adults (id, name) VALUES
(1, 'adam'),
(2, 'bob');
CREATE TABLE children (
id INT,
name VARCHAR(10),
grad VARCHAR(4)
) ;
INSERT INTO children (id, name, grad) VALUES
(1, 'sally', '2016'),
(2, 'johnny', '2017'),
(3, 'eric', '2016'),
(4, 'billy', '2016'),
(5, 'rachel', '2016');
CREATE TABLE pc (
id INT,
a_id INT,
c_id INT
) ;
INSERT INTO pc (id, a_id, c_id) VALUES
(1, 1, 1),
(2, 1, 2),
(3, 1, 3),
(4, 2, 3),
(5, 2, 2);
SQLFiddle: http://sqlfiddle.com/#!2/89281e
So I want to update adults.seniors to the count of '2016' children they're linked to. So adult #1 would be "2" (sally and eric), and adult #2 "1" (eric).
The real data will be run across 25,000+ children being matched up to 40,000+ parents with a row count on the "pc" table above 3,000,000 rows - so looking for efficiency. I started working down this path but a) it's not working for obvious reasons and b) I doubt it would be efficient...
UPDATE adults a SET
seniors = (
SELECT p.a_id, count(*)
FROM pc p
INNER JOIN children c ON c.id = p.c_id
WHERE c.grad = '2016'
GROUP BY p.c_id)
WHERE p.a_id = a.id;
I'm thinking there has to be a better way of doing this with joins but can't seem to wrap my head around it.
You should be looking for this update statement:
UPDATE adults a
JOIN
(SELECT
p.a_id, COUNT(*) childrencount
FROM
pc p
INNER JOIN children c ON c.id = p.c_id
WHERE
c.grad = '2016'
GROUP BY p.a_id) c ON (a.id = c.a_id)
SET
seniors = c.childrencount;
How do I sort foo's based on bar.val
create table foo (id, name)
create table bar (id, foo_id, val)
foo has 1-to-1 relationship with bar
and some foo's will not have bar's
insert into foo (1, 'A');
insert into bar (1, 1, 'Active');
insert into foo (2, 'B');
insert into bar (2, 2, 'InActive');
insert into foo (3, 'C');
// 3 doesn't have a bar
insert into foo (4, 'D');
insert into bar (3, 4, 'InActive');
I think this is what you are looking for:
SELECT f.id, f.name, b.val
FROM foo f LEFT JOIN bar b
ON f.id = b.foo_id
ORDER BY COALESCE(b.val, 'Archived') DESC
For those foo records which do not have a corresponding record in the bar table, they will be assigned a default value of "Archived" for the purpose of ordering the result set.
Given the following DataBase:
CREATE TABLE album ( id int );
INSERT INTO album (id) VALUES
(1),
(2),
(3),
(4);
CREATE TABLE icon_album ( albumID int, current int );
INSERT INTO icon_album (albumID, current) VALUES
(1, 1),
(1, 1),
(2, 1),
(2, 0),
(3, 0),
(3, 0);
I would like to get the following result
albums: id status
1 1
2 0
3 0
4 0
What is the MySql query that would give me the correct result?
P.S. 1: This is my second question for this problem. This first question did not yield a working solution
Is this what you're looking for?
SELECT a.id, IF(i.current IS NULL, 0, current) AS status
FROM album a LEFT JOIN
(
SELECT albumID, MIN(current) AS current
FROM icon_album
GROUP BY albumID
) i ON a.id = i.albumID
Try Like this
"SELECT albumID AS id, if(SUM(current)>1,1,0) AS status FROM icon_album GROUP BY albumID"
I am trying to retrieve a single picture set by the user as the primary picture from table as below:
SELECT p.*, ph.* FROM place AS p
INNER JOIN photo as ph
ON p.place_id = ph.place_id
WHERE ph.primary_pic = 'X';
But not all user has set their primary picture, resulting in the query does not return anything.
IF(query is empty)
//perform SQL again with primary_pic = ''
Is there any ways or syntax that could be use to query this with one single SQL statement?
I was working in MS SQL but I don't see anything MS-specific except for the table variables. So if you change that to existing tables, it should run on MySQL too. (I am not sure but I guess MySQL should have EXCEPT set operation.)
-- sample data start
declare #place as table (plid int, plname nvarchar(100))
declare #photo as table (phid int, phname nvarchar(100), plid int, primary_pic nvarchar(1))
insert into #place values (1, 'aaa')
insert into #place values (2, 'bbb')
insert into #photo values (1, 'aaa_1.jpg', 1, '')
insert into #photo values (2, 'aaa_2.jpg', 1, 'X')
insert into #photo values (3, 'aaa_3.jpg', 1, '')
insert into #photo values (4, 'aaa_4.jpg', 1, '')
insert into #photo values (5, 'bbb_1.jpg', 2, '')
insert into #photo values (6, 'bbb_2.jpg', 2, '')
insert into #photo values (7, 'bbb_3.jpg', 2, '')
insert into #photo values (8, 'bbb_4.jpg', 2, '')
-- sample data end
-- note: #place and #photo are table variables in MS SQL
select p.*, ph2.*
from #place p inner join #photo ph2 on p.plid = ph2.plid
inner join (
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic <> 'X'
except
select distinct plid from #photo where primary_pic = 'X') hasnoprimary
on hasnoprimary.plid = ph.plid
group by ph.plid, ph.primary_pic
union
select ph.plid, ph.primary_pic, min(ph.phid) phid
from #photo ph inner join
(select distinct plid from #photo where primary_pic = 'X') hasprimary
on hasprimary.plid = ph.plid
where primary_pic = 'X'
group by ph.plid, ph.primary_pic
) trickypart on trickypart.phid = ph2.phid
Method: (1) get two lists of place IDs. One for which there is a primary photo and another for which there isn't (this is where I used EXCEPT). (2) join the photos table to both of them separately to get the photo IDs. For the first list, it is what was marked with X, for the second it is the minimum of all photo IDs. (3) make a union of the two. (4) join it back to places and photos.
In MS SQL it works ang gives the following for the sample data above:
plid plname phid phname plid primary_pic
----------- -------------- ----------- -------------- ----------- -----------
1 aaa 2 aaa_2.jpg 1 X
2 bbb 5 bbb_1.jpg 2
I have a table like this:
someid somestring
1 Hello
1 World
1 Blah
2 World
2 TestA
2 TestB
...
Currently I'm grouping by the id and concatenating the strings, so I end up with this:
1 Hello,World,Blah
2 World,TestA,TestB
...
Is it possible to do a second grouping so that if there are multiple entries that end up with the same string, I can group those too?
Yes, just put your current query in an inner select and apply a new GROUP BY to the outer select. Note that you will probably want to use ORDER BY of GROUP_CONCAT to ensure that the strings are always concatenated in the same order.
SELECT somelist, COUNT(*) FROM
(
SELECT
someid,
GROUP_CONCAT(somestring ORDER BY somestring) AS somelist
FROM table1
GROUP BY someid
) AS T1
GROUP BY somelist
Result:
'Blah,Hello,World', 1
'TestA,TestB,World', 2
Here's the test data I used:
CREATE TABLE table1 (someid INT NOT NULL, somestring NVARCHAR(100) NOT NULL);
INSERT INTO table1 (someid, somestring) VALUES
(1, 'Hello'),
(1, 'World'),
(1, 'Blah'),
(2, 'World'),
(2, 'TestA'),
(2, 'TestB'),
(3, 'World'),
(3, 'TestB'),
(3, 'TestA');