c00000fd :
Say, if I define my tables as such:
create table `usrs` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`fnm` TINYTEXT,
`lnm` TINYTEXT
);
insert into `usrs` (`fnm`, `lnm`)
VALUES
('John', 'Doe'), -- 1
('Mary', 'Smith'), -- 2
('Peter', 'Pan'), -- 3
('Michael', 'Jackson'); -- 4
create table `pmts` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`uid` INT UNSIGNED, -- id in `usrs` table
`amt` DOUBLE,
`flgs` INT UNSIGNED
);
insert into `pmts` (`uid`, `amt`, `flgs`)
VALUES
('3', '0.99', 0),
('1', '1.50', 0x80),
('3', '2', 0x80),
('3', '0.99', 0),
('4', '1.30', 0),
('3', '2.40', 0),
('1', '2.55', 0x80);
create table `downloads` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`uid` INT UNSIGNED, -- id in `usrs` table
`app` TINYTEXT
);
insert into `downloads` (`uid`, `app`)
VALUES
('2', 'Candy Crush'),
('2', 'Skype'),
('3', 'Word'),
('2', 'Calc'),
('4', 'Doom'),
('3', 'Notepad');
Then when I run this query, that among other things counts pmts
, downloads
and special flgs
for each usr
:
-- this query contains additional clauses that were used in my production query
SELECT t1.*,
COUNT(t2.`id`) as cPmts, -- count `pmts` for each user
SUM(IF((t2.`flgs`&0x80)<>0, 1, 0)) as cPmtFlgs, -- count `pmts` with `flgs` equal to 0x80 for each user
COUNT(t3.`id`) as cDwds -- count `downloads` for each user
FROM `usrs` t1
LEFT JOIN `pmts` t2 ON t1.`id`=t2.`uid`
LEFT JOIN `downloads` t3 ON t1.`id`=t3.`uid`
WHERE t1.`id` > 0 -- just to simulate some condition
GROUP BY t1.`id`
ORDER BY t1.`fnm`, t1.`lnm` ASC
LIMIT 0, 10
the results I receive, marked in red, are wrong:
Why?
Nick :
Your problem is that because Peter Pan
has multiple payments and multiple downloads, his rows are being duplicated in the JOIN
(you can see this if you remove the GROUP BY
and replace the aggregation functions in the query with a simple SELECT *
[demo]). The way to work around this is to perform the aggregations in derived tables and JOIN
those results instead:
SELECT t1.*,
COALESCE(t2.cPmts, 0) AS cPmts, -- count `pmts` for each user
COALESCE(t2.cPmtFlgs, 0) AS cPmtFlgs, -- count `pmts` with `flgs` equal to 0x80 for each user
COALESCE(t3.cDwds, 0) AS cDwds -- count `downloads` for each user
FROM `usrs` t1
LEFT JOIN (SELECT uid,
COUNT(*) AS cPmts,
SUM(IF((`flgs`&0x80)<>0, 1, 0)) AS CpmtFlgs
FROM `pmts`
GROUP BY uid) t2 ON t1.`id`=t2.`uid`
LEFT JOIN (SELECT uid, COUNT(*) AS cDwds
FROM `downloads`
GROUP BY uid) t3 ON t1.`id`=t3.`uid`
WHERE t1.`id` > 0 -- just to simulate some condition
ORDER BY t1.`fnm`, t1.`lnm` ASC
LIMIT 0, 10
Output:
id fnm lnm cPmts cPmtFlgs cDwds
1 John Doe 2 2 0
2 Mary Smith 0 0 3
4 Michael Jackson 1 0 1
3 Peter Pan 4 1 2
Guess you like
Origin http://10.200.1.11:23101/article/api/json?id=395317&siteId=1