Suppose I have two tables :
CREATE TABLE `test_a` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And
CREATE TABLE `test_b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`A_id` int(11) NOT NULL,
`Amount` float NOT NULL,
`cat` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `test_b_ibfk_1` (`A_id`),
CONSTRAINT `test_b_ibfk_1` FOREIGN KEY (`A_id`) REFERENCES `test_a` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
In test_a I entered data
1, A
2, B
3, C
In second table I entered following data:
1, 1, 4.78, 1
2, 2, 77, 1
3, 1, 0.22, 1
4, 2, 13, 1
The task is: to select sum of all amounts, grouped by a_id, all data from test_a must be appear.
Thus, I’m using right outer join.
When I ran following query:
SELECT a.name, sum(b.amount)
FROM test_b AS b RIGHT OUTER JOIN test_a AS a ON b.A_id = a.id
GROUP BY b.A_id;
That gives a result set as desired. Like this:
C -- null
A -- 5
B -- 90
But when I used WHERE clause:
SELECT a.name, sum(b.amount)
FROM test_b AS b RIGHT OUTER JOIN test_a AS a ON b.A_id = a.id
**where b.cat =1**
GROUP BY b.A_id;
I get the result:
A -- 5
B -- 90
My question is: How to achieve same result with WHERE clause. (I want ‘C – null’, to be appear)
Thank in advance!!!
You've got to include your condition of the joined table b in the ON condition:
SELECT a.name, sum(b.amount)
FROM
test_b AS b
RIGHT OUTER JOIN
test_a AS a
ON
b.A_id = a.id AND b.cat =1
GROUP BY b.A_id;
Because if used in the WHERE clause it changes the OUTER JOIN implicitly to an INNER JOIN.
Related
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
Think this is probably fairly simple but cannot find the correct search terms, so if this is duplicated then great cause im sure there will be an answer somewhere.
I have the following tables setup
CREATE TABLE IF NOT EXISTS `customer` (
`id` int(6) unsigned auto_increment NOT NULL,
`name` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `billing_run` (
`id` int(6) unsigned auto_increment NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `invoice` (
`id` int(6) unsigned auto_increment NOT NULL,
`billing_run_id` int(6) unsigned NOT NULL,
`customer_id` int(6) unsigned NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (billing_run_id) REFERENCES billing_run(id),
FOREIGN KEY (customer_id) REFERENCES customer(id)
) DEFAULT CHARSET=utf8;
with the following data
insert into customer (name) values ('test customer');
insert into billing_run (date) values ('2019-01-01 12:00:00');
insert into billing_run (date) values ('2019-02-01 12:00:00');
insert into billing_run (date) values ('2019-03-01 12:00:00');
insert into invoice (customer_id,billing_run_id) values (1,1);
SQLFiddle here -> http://sqlfiddle.com/#!9/a54162/5
And i want to get the customer records that do not have an invoice related to billing_run with id of 2
My query
select c.id from customer c
left join invoice i on i.customer_id = c.id
left join billing_run br on br.id = i.billing_run_id and br.id = 2
where i.id is null
returns 0 records. Why ?
First you join the table customer (1 row) with the table invoice (1 row).
This join will return 1 row because there is a match between the columns in the ON clause:
on i.customer_id = c.id
(both i.customer_id and c.id have the value 1 in your sample data).
So there is not any row with i.id is null.
The next join to the table billing_run does not affect the first 2 joined tables.
So the condition:
where i.id is null
returns no rows.
The correct condition (which you had in the original fiddle) is:
where br.id is null
because the join to the table billing_run will return a non matching row for the condition:
on br.id = i.billing_run_id and br.id = 2
because there is no i.billing_run_id = 2 in invoice.
You will want to do an exclusive where clause this will return the 1 row that you want.
select * from customer c
where c.id not in (Select customer_id from invoice i LEFT JOIN billing_run br on
i.billing_run_id=br.id WHERE br.id=2 and br.id is not null)
http://sqlfiddle.com/#!9/a54162/14
You don't need the billing_run table. So I think you intend:
select c.id
from customer c left join
invoice i
on i.customer_id = c.id and i.billing_run_id = 2
where i.id is null
I'm using MySQL 5.5. with two tables in it:
DROP TABLE IF EXISTS `events_dictionary`;
CREATE TABLE `events_dictionary` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_dictionary` VALUES (1, 'Light'),(2, 'Switch'),(3, 'on'),(4, 'off');
DROP TABLE IF EXISTS `events_log`;
CREATE TABLE `events_log` (
`log_id` bigint(20) NOT NULL AUTO_INCREMENT,
`event_name_id` int(11) NOT NULL DEFAULT '0',
`event_param1` int(11) DEFAULT NULL,
`event_value1` int(11) DEFAULT NULL,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_log` VALUES (1, 1, 2, 3),(2, 1, 2, 4);
Table events_dictionary contains names for events_log events names,params and values.
So, my question is - how could i select data from event_log table with columns event_name_id, event_param1, event_value1 mapped to name values from events_dictionary table?
I tried to do this query:
SELECT name, event_param1, event_value1
FROM events_log
JOIN events_dictionary ON events_log.event_name_id = events_dictionary.id;
But, in this case i see only event_name_id replaced with values from events_dictionary, like this:
name | event_param1 | event_value1
Light | 1 | 1
Light | 1 | 2
And i want to replace event_param1, and event_value1 with names from events_dictionary too.
Thanks in advance!
You need to join to the events_dictionary multiple times
SELECT a.name, b.name, c.name
FROM events_log
JOIN events_dictionary a ON events_log.event_name_id = a.id
JOIN events_dictionary b ON events_log.event_param1 = b.id
JOIN events_dictionary c ON events_log.event_value1 = c.id;
PS
Your example for the event_log isn't that helpful , instead insert the values (1,1,2,3),(2,1,2,4) to turn the switch on and off for the light.
DS
You can use correlated subqueries:
SELECT name,
(SELECT t.name
FROM events_dictionary AS t
WHERE t.id = event_param1) AS param_name,
(SELECT t2.name
FROM events_dictionary AS t2
WHERE t2.id = event_value1) AS event_name
FROM events_log AS el
JOIN events_dictionary AS ed ON el.event_name_id = ed.id;
Demo here
Using MySQL 5.7.17-log
We have a number of report queries using a dependent subquery in the select statement that is returning null when there should be a value. I know this style can be refactored into a left join but I wanted to understand the reason we are having this problem and possibly file a bug with MySQL. Here is the simplest way to reproduce this:
CREATE TABLE tableA (
id varchar(36) NOT NULL,
col2 int(10) unsigned zerofill NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
;
CREATE TABLE tableB (
id varchar(36) NOT NULL,
col2 int(10) unsigned zerofill NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE tableC (
refA varchar(36) NOT NULL,
refB varchar(36) NOT NULL,
PRIMARY KEY (refA, refB),
CONSTRAINT fkCA FOREIGN KEY (refA) REFERENCES tableA (id),
CONSTRAINT fkCB FOREIGN KEY (refB) REFERENCES tableB (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE tableD (
refA varchar(36) NOT NULL,
refB varchar(36) NOT NULL,
col3 int(10) unsigned zerofill NOT NULL,
PRIMARY KEY (refA, refB),
CONSTRAINT fkDA FOREIGN KEY (refA) REFERENCES tableA (id),
CONSTRAINT fkDB FOREIGN KEY (refB) REFERENCES tableB (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into tableA values ('123',5)
;
insert into tableB values ('234',6)
;
insert into tableC values ('123','234')
;
insert into tableD values ('123','234',7)
;
select a.id as 'aid',
b.id as 'bib',
( select d.col3 from tableD d where d.refA=a.id and d.refB=b.id ) as 'shouldBe7'
from tableA a
inner join tableC c on c.refA = a.id
inner join tableB b on c.refB = b.id
where a.id='123'
group by a.id, b.id
;
The select statement produces ['123','234',null] although I would expect ['123','234',7]. If you remove the where clause in the statement (where a.id='123'), the expected result - ['123','234',7] is what you get.
I did an explain on the whole select statement and saw that the "ref" uses a func which I did a SHOW WARNINGS and the dependent subquery is comparing the id against an empty string.
(/* select#2 */ select dev.d.col3 from dev.tableD d where ((dev.d.refA = '') and (dev.d.refB = dev.b.id))) AS shouldBe7
Seems like a 5.7 bug...I've filed http://bugs.mysql.com/87915 . the db-fiddle tool was helpful - thanks James!
This ran out of the box for me on 10.1.25-MariaDB locally:
Tried this 5.6 fiddle and it worked.
Tried this 5.7 fiddle and it also worked
I had a look at your code but couldn't see any obvious causes of this error, have you tried on other 5.7 instances to rule out any possible local causes?
Regards,
James
Try this instead...there's always more than one way to skin a cat.
select a.id as 'aid',
b.id as 'bib',
d.col3 as 'shouldBe7'
from tableA a
inner join tableC c on c.refA = a.id
inner join tableB b on c.refB = b.id
inner join ( select col3, refA, refB from tableD ) as d
on d.refA=a.id and d.refB=b.id
where a.id='123'
group by a.id, b.id
;
I am trying to execute the following query
SELECT `id`,
`name`,
`ownerid`,
`creationdata`,
`motd`,
(SELECT Count(*)
FROM guild_membership a,
players_online b
WHERE a.player_id = b.player_id
AND a.guild_id = id) AS `online`,
(SELECT Max(b.level)
FROM guild_membership a,
players b
WHERE a.player_id = b.id
AND a.guild_id = id) AS `toplevel`,
(SELECT Min(a.level)
FROM players a,
guild_membership b
WHERE a.id = b.player_id
AND b.guild_id = id) AS `lowlevel`
FROM `guilds`
WHERE `name` = 'Wideswing Poleaxe'
LIMIT 1;
The tables used in here are the followin
CREATE TABLE IF NOT EXISTS `players` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`group_id` int(11) NOT NULL DEFAULT '1',
`account_id` int(11) NOT NULL DEFAULT '0',
`level` int(11) NOT NULL DEFAULT '1',
...
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE,
KEY `vocation` (`vocation`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `guilds` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`ownerid` int(11) NOT NULL,
`creationdata` int(11) NOT NULL,
`motd` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY (`name`),
UNIQUE KEY (`ownerid`),
FOREIGN KEY (`ownerid`) REFERENCES `players`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `guild_membership` (
`player_id` int(11) NOT NULL,
`guild_id` int(11) NOT NULL,
`rank_id` int(11) NOT NULL,
`nick` varchar(15) NOT NULL DEFAULT '',
PRIMARY KEY (`player_id`),
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
I am trying to get the MAX level and MIN level on the players table inside one guild
However I am always getting toplevel and lowlevel the same value and tis always the lowest level
I am not sure what is wrong
First thing I notice is: you are using LIMIT without ORDER BY. So from the guilds table you expect to find more than one entry for name = 'Wideswing Poleaxe', but only look at the first the DBMS happens to find. Is this desired?
Next thing I see is the out-dated join syntax. Where did you get this from? A twenty year old book? No, stop, twenty years ago this syntax was already made redundant, so it must be even older ;-) Use explicit joins instead (JOIN ... ON ...)
As to your subqueries: You are comparing with id without any qualifier, so the DBMS will take this to be guild_membership.id or players_online resp. players.id, where you really want it to be guild.id. This should explain that you get unexpected values.
As to how the query is built: You could join to the aggregated player data instead. And use alias names that match the tables.
select
guilds.id,
guilds.name,
guilds.ownerid,
guilds.creationdata,
guilds.motd,
players.online,
players.toplevel,
players.lowlevel
from guilds
left join
(
select
gms.guild_id,
max(p.level) as toplevel,
min(p.level) as lowlevel,
sum((select count(*) from players_online po where po.player_id = p.id)) as online
from guild_membership gms
join players p on p.id = gms.player_id
group by gms.guild_id
) players on players.guild_id = guilds.id
where guilds.name = 'Wideswing Poleaxe';
You can change the left outer join (left join) to an inner join (join), if you don't need to see guilds without any player.
I think the problem is here: a.guild_id = id
The id being used is from players, not guilds, as it is still part of the sub-query.
You shouldn't need all those subqueries, JOINs are almost always faster and should usually be first technique tried.
Try this...
SELECT `id`, `name`, `ownerid`, `creationdata`, `motd`
, COUNT(po.player_id) AS online
, MAX(p.level) AS toplevel
, MIN(p.level) AS lowlevel
FROM `guilds` AS g
LEFT JOIN guild_membership AS gm ON g.id = gm.guild_id
LEFT JOIN players AS p ON gm.player_id = p.player_id
LEFT JOIN players_online AS po ON gm.player_id = po.player_id
WHERE g.`name` = 'Wideswing Poleaxe'
;
COUNT only counts non-null values; similarly MAX, MIN, and most other aggregate functions ignore null values (only returning null if only null values were processed).
You should consider modifying your query like
SELECT g.`id`,
g.`name`,
g.`ownerid`,
g.`creationdata`,
g.`motd`,
(SELECT Count(*)
FROM guild_membership a,
players_online b
WHERE a.player_id = b.player_id
AND a.guild_id = id) AS `online`,
(SELECT Max(b.level)
FROM players b join guild_membership a on a.player_id = b.id
AND a.guild_id = g.id) AS `toplevel`,
(SELECT Min(a.level)
FROM players a join
guild_membership b on a.id = b.player_id
AND b.guild_id = g.id) AS `lowlevel`
FROM `guilds` g
WHERE g.`name` = 'Wideswing Poleaxe'
LIMIT 1;