MySQL - using "as" in inner select - mysql

I have three tables I am selecting from and two of them have same column named "id". It is language.id and route.id
If I run this query
SELECT *
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
It runs fine, it cuts language.id, which is what I want
However I want to use it as inner select, but when I do this
SELECT * FROM (
SELECT *
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
) T1
It ends with Error Code: 1060. Duplicate column name 'id'
I have tried renaming it, this query runs fine showing noneed_id column as expected
SELECT *, language.id as noneed_id
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
But once again, using it as inner select ends with same error
SELECT * FROM (
SELECT *, language.id as noneed_id
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
) T1
I do not understand this behaviour, anyone can point me out?
PS : I know I can do something like this to make it work, but I would like to not name all tables/columns
SELECT * FROM (
SELECT route.*, route_has_languages.*, language.short_name, language.long_name
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
) T1
If someone wants to try it, you can run this
CREATE TABLE IF NOT EXISTS `language` (
`id` INT NOT NULL AUTO_INCREMENT,
`short_name` VARCHAR(7) NOT NULL,
`long_name` VARCHAR(15) NOT NULL,
`name` VARCHAR(63) NOT NULL,
`flag` TEXT NULL,
`language_order` INT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `short_name_UNIQUE` (`short_name` ASC),
UNIQUE INDEX `long_name_UNIQUE` (`long_name` ASC))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `route` (
`id` INT NOT NULL AUTO_INCREMENT,
`length` DOUBLE NOT NULL,
`estimated_time` INT NOT NULL,
`accessibility` TINYINT(1) NOT NULL,
`version` INT NOT NULL,
`north_east_point_lat` DOUBLE NOT NULL,
`north_east_point_lng` DOUBLE NOT NULL,
`south_west_point_lat` DOUBLE NOT NULL,
`south_west_point_lng` DOUBLE NOT NULL,
`available` TINYINT(1) NOT NULL DEFAULT 1,
`store_id` VARCHAR(255) NULL,
`color` VARCHAR(63) NULL,
PRIMARY KEY (`id`)
)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `route_has_languages` (
`route_id` INT NOT NULL,
`languages_id` INT NOT NULL,
`description` TEXT NOT NULL,
`voice` VARCHAR(63) NOT NULL,
`info_at_poi` TEXT NOT NULL,
PRIMARY KEY (`route_id`, `languages_id`),
INDEX `fk_route_has_languages_languages1_idx` (`languages_id` ASC),
INDEX `fk_route_has_languages_route1_idx` (`route_id` ASC),
CONSTRAINT `fk_route_has_languages_route1`
FOREIGN KEY (`route_id`)
REFERENCES `audioguide`.`route` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_route_has_languages_languages1`
FOREIGN KEY (`languages_id`)
REFERENCES `audioguide`.`language` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;

As long as you use * in both SELECT statements, you will have this error.
Renaming it like: , language.id as noneed_id will only add a column, but it will not remove the id column(s).
A solution can be to rename the fields in the database. For example, route.id to route_id etc.

Lets check the Problem-
Your first query-
SELECT *
FROM route
LEFT JOIN route_has_languages
ON route.id = route_has_languages.route_id
LEFT JOIN language
ON language.id = route_has_languages.languages_id
WHERE route_has_languages.languages_id = 1
Here mysql will follow fields order as per table order means first table all fields then 2nd table all fields and so on as per below-
In your second table you are using sub-query means mysql will treat same and povide results as per below-
id (first table), col12,col13,col14..., id(2nd table),col22,col23,col24..., other tables columns.....
means your results are -
id,col11,col12,col13,...,id,col21,col22,col23,....
Now your outer query will fetch results from here and will get id column 2 times and will throw error.
Solution
Either you have to mention all columns in sub-query and need to keep different alias for common fields or need to change column name in one table.

Related

select taking 9.+ seconds. how to re-write it better?

I have this select:
select t.id, c.user, t.title, pp.foto, t.data from topics t
inner join cadastro c on t.user = c.id
left join profile_picture pp on t.user = pp.user
left join (
select c.topic, MAX(c.data) cdata from comments c
group by c.topic
)c on t.id = c.topic
where t.community = ?
order by ifnull(cdata, t.data) desc
limit 15
I want to select topics and order them by their date or the date of the topic comments, if it has comments.
Unfortunately, this is taking more than 9 seconds.
I don't think the problem here is indexing, but the way I am writing the select itself.
`topics` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` INT(11) UNSIGNED NOT NULL,
`title` varchar(100) NOT NULL,
`description` varchar(1000),
`community` INT(11) UNSIGNED NOT NULL,
`data` datetime NOT NULL,
`ip` varchar(20),
PRIMARY KEY (`id`),
FOREIGN KEY (`user`) REFERENCES cadastro (`id`),
FOREIGN KEY (`community`) REFERENCES discussion (`id`)
)
`comments` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` INT(11) UNSIGNED NOT NULL,
`comment` varchar(1000) NOT NULL,
`topic` INT(11) UNSIGNED NOT NULL,
`data` datetime NOT NULL,
`ip` varchar(20),
`delete` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
FOREIGN KEY (`user`) REFERENCES cadastro (`id`),
FOREIGN KEY (`topic`) REFERENCES topics (`id`)
)
Your EXPLAIN gives you a strong hint. The first row in that results says, using temporary, using filesort implying that it's not using a an index.
It might be possible to improve this query by adding indexes and removing some conditionals, but I think in this case a better solution exists.
Why not add a new column to topics that indicates the last time a comment was added? (like a last_modified). Every time a comment gets added, just update that column for that topic as well.
It's effectively denormalizing this. I think this a valid usecase and it's always going to be faster than fixing this messy query.
You are performing a full table scan on the table comments on every query. How many rows does it have? At least create the following index:
comments (topic, data);
to avoid reading the whole table every time.
I know you've said you don't think the problem is indexing, but 9 out of 10 times I've had this problem that's exactly what it's been down to.
Ensure you have an index created on each table that you're using in the query and include the columns specified in the join.
Also, as NiVeR said, don't use the same alias multiple times.
Here's a refactoring of that query, unsure if I've mixed up or missed a column name/alias or two though.
select t.id, c.user, t.title, pp.foto, t.data from topics t
inner join cadastro c on t.user = c.id
left join profile_picture pp on t.user = pp.user
left join (
select com.topic, MAX(com.data) comdata from comments com
group by com.topic
)com1 on t.id = com1.topic
where t.community = ?
order by ifnull(com1.comdata, t.data) desc
limit 15

Improve speed of MySQL query with 5 left joins

Working on a support ticketing system with not a lot of tickets (~3,000). To get a summary grid of ticket information, there are five LEFT JOIN statements on custom field table (j25_field_value) containing about 10,000 records. The query runs too long (~10 seconds) and in cases with a WHERE clause, it runs even longer (up to ~30 seconds or more).
Any suggestions for improving the query to reduce the time to run?
Four tables:
j25_support_tickets
CREATE TABLE `j25_support_tickets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL DEFAULT '0',
`user_id` int(11) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`message` text,
`modified_date` datetime DEFAULT NULL,
`priority_id` tinyint(3) unsigned DEFAULT NULL,
`status_id` tinyint(3) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3868 DEFAULT CHARSET=utf8
j25_support_priorities
CREATE TABLE `j25_support_priorities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
j25_support_statuses
CREATE TABLE `j25_support_statuses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
j25_field_value (id, ticket_id, field_id, field_value)
CREATE TABLE `j25_support_field_value` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ticket_id` int(11) DEFAULT NULL,
`field_id` int(11) DEFAULT NULL,
`field_value` tinytext,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=10889 DEFAULT CHARSET=utf8
Also, ran this:
SELECT LENGTH(field_value) len FROM j25_support_field_value ORDER BY len DESC LIMIT 1
note: the result = 38
The query:
SELECT DISTINCT t.id as ID
, (select p.title from j25_support_priorities p where p.id = t.priority_id) as Priority
, (select s.title from j25_support_statuses s where s.id = t.status_id) as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
ALTER TABLE j25_support_field_value
ADD INDEX (`ticket_id`,`field_id`,`field_value`(50))
This index will work as a covering index for your query. It will allow the joins to use only this index to look up the values. It should perform massively faster than without this index, since currently your query would have to read every row in the table to find what matches each combination of ticket_id and field_id.
I would also suggest converting your tables to InnoDB engine, unless you have a very explicit reason for using MyISAM.
ALTER TABLE tablename ENGINE=InnoDB
As above - a better index would help. You could probably then simplify your query into something like this too (join to the table only once):
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, case when v.field_id=1 then v.field_value else null end as IssueType
, case when v.field_id=2 then v.field_value else null end as Version
, case when v.field_id=3 then v.field_value else null end as UserType
, case when v.field_id=4 then v.field_value else null end as Company
, case when v.field_id=5 then v.field_value else null end as RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value v ON t.id = v.ticket_id
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
You can do away with the subqueries for starters and just get them from another join. You can add an index to j25_support_field_value
alter table j25_support_field_value add key(id, field_type);
I assume there is an index on id in j25_support_tickets - if not and if they are unique, add a unique index alter table j25_support_tickets add unique key(id); If they're not unique, remove the word unique from that statement.
In MySQL, a join usually requires an index on the field(s) that you are using to join on. This will hold up and produce very reasonable results with huge tables (100m+), if you follow that rule, you will not go wrong.
are the ids in j25_support_tickets unique? If they are you can do away with the distinct - if not, or if you are getting exact dupicates in each row, still do away with the distinct and add a group by t.id to the end of this:
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
Switch to InnoDB.
After switching to InnoDB, make the PRIMARY KEY for j25_support_field_value be (ticket_id, field_id) (and get rid if id). (Tacking on field_value(50) will hurt, not help.)
A PRIMARY KEY is a UNIQUE KEY, so don't have both.
Use VARCHAR(255) instead of the nearly-equivalent TINYTEXT.
EAV schema sucks. My ran on EAV.

MySQL MAX and MIN

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;

LEFT JOIN two tables so that left table data is filtered but right table data is displayed even if left table data does not exist

To allow our customers to store some of their own data along with our data structure I have created two extra tables:
CREATE TABLE external_columns
(
`id` INT(11) PRIMARY KEY NOT NULL,
`column` VARCHAR(30) NOT NULL,
`sid` INT(11) NOT NULL,
`bid` INT(11) NOT NULL,
`label` VARCHAR(30) NOT NULL,
`table` VARCHAR(30) NOT NULL,
`default` TINYTEXT NOT NULL
);
CREATE TABLE external_data
(
`id` INT(11) PRIMARY KEY NOT NULL,
`extcol_id` INT(11) NOT NULL,
`sid` INT(11) NOT NULL,
`bid` INT(11) NOT NULL,
`data` MEDIUMTEXT NOT NULL,
`row_id` INT(11) NOT NULL,
CONSTRAINT `external_data_external_columns_id_fk`
FOREIGN KEY (extcol_id) REFERENCES external_columns (id)
);
CREATE UNIQUE INDEX combinedUniqueIndex
ON external_data (extcol_id, sid, bid, row_id);
sid and bid are system values that identify the customer the data belongs to. row_id refers to the primary key of table referenced in table.
To get data for a certain row I have created this prepared statement:
SELECT `data`.*, `columns`.`column`, `columns`.`default`
FROM `external_columns` as `columns`
LEFT JOIN `external_data` as `data`
ON `columns`.`id` = `data`.`extcol_id`
WHERE (
`columns`.`sid` = :sid
AND `columns`.`bid` = :bid
AND `data`.`row_id` = :row_id
AND `columns`.`table` = :tableName
)
This works fine as long as for each external_column there is an entry in external_data for the given :row_id. But I want to make sure that there is always a row for each column, even if there is no data for the given :row_id. Is there a way to do this with one query?
Very close, by placing AND data.row_id = :row_id in your WHERE, you have effectively written an INNER JOIN as nulled data.row_ids won't match.
You should move this condition to the LEFT JOIN conditions:
SELECT `data`.*, `columns`.`column`, `columns`.`default`
FROM `external_columns` as `columns`
LEFT JOIN `external_data` as `data`
ON `data`.`extcol_id`= `columns`.id
AND `data`.`row_id` = :row_id
WHERE `columns`.`sid` = :sid
AND `columns`.`bid` = :bid
AND `columns`.`table` = :tableName
Personal Preferences:
Don't need the WHERE parentheses and I always tend to put the table conditions for a JOIN in the JOIN conditions where applicable and JOIN table on the LHS to make indexing options more obvious..
No difference for INNER JOINs but essential for certain LEFT JOINs.

MySQL update a table and select from the same table in a subquery

I have table of link
CREATE TABLE `linktable` (
`id ` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`idParent` BIGINT(20) UNSIGNED NOT NULL,
`Role` ENUM('Contacts','Expert','...') NULL DEFAULT NULL,
`idChild` BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (`idt`),
UNIQUE INDEX `UK_Parent_Child_Role` (`idParent`, `idChild`, `Role`)
)
I want to update this table and don’t break the unique key.
With other database I make something like this :
Update linktable lt1 Set lt1.Parent = :ziNew Where lt1.idParent = :ziOld
and not exists (select * from linktable lt2 where lt2.idParent = :ziNew and lt1.role = lt2.role and lt1.idChild = lt2.idChild);
How to make this with MySQL ?
Using your same syntax for variables, you would do this with a join:
Update linktable lt1 left outer join
(select *
from linktable lt2
where lt2.idParent = :ziNew
) lt2
on lt1.role = lt2.role and lt1.idChild = lt2.idChild
Set lt1.Parent = :ziNew
Where lt1.Parent =:ziOld and lt2.idParent is null;
The problem in MySQL is that the subquery is one the same table as the updated table. If it were a different table, then the original form with not exists would still work.