I have 2 tables:
$sql = "CREATE TABLE $media_table (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(50) NOT NULL,
`title` varchar(255) DEFAULT NULL,
`description` varchar(2000) DEFAULT NULL,
`playlist_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `playlist_id` (`playlist_id`),
) $charset_collate;";
$sql = "CREATE TABLE $taxonomy_table (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(10) NOT NULL,
`title` varchar(500) NOT NULL,
`media_id` int(11) NOT NULL,
`playlist_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `media_id` (`media_id`),
CONSTRAINT `mvp_taxonomy_ibfk_1` FOREIGN KEY (`media_id`) REFERENCES {$media_table} (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) $charset_collate;";
Let say I want to select all rows from media_table where playlist_id=5 and title from taxonomy_table="sport, football".
I could run 2 queries, first get all media_id from taxonomy_table where title="..." AND playlist_id="5", then second query select all rows from media_table WHERE id IN (ids).
Does this belongs to some kind of JOIN query maybe?
I tried this but I am not getting desired results:
$query = "SELECT * FROM {$media_table}
INNER JOIN {$taxonomy_table}
ON {$media_table}.id = {$taxonomy_table}.media_id
WHERE {$taxonomy_table}.type='tag' AND {$taxonomy_table}.title IN ($arg) AND {$taxonomy_table}.playlist_id=%d
ORDER BY {$media_table}.order_id";
It seems like its mixing all columns from both tables into the results, but I only want to retrieve rows from media_table that have title(s) from taxonomy_table.
It seems like its mixing all columns from both tables into the results, but I only want to retrieve rows from media_table that have title(s) from taxonomy_table.
Reason is that the join enables you to select data from both tables.
The trick in this case is to change the asterisk (*) to specific columns or to use a prefix for the asterisk.
examples for using a prefix:
$query = "SELECT {$media_table}.* FROM {$media_table}
INNER JOIN {$taxonomy_table}
ON {$media_table}.id = {$taxonomy_table}.media_id
`WHERE {$taxonomy_table}.type='tag' AND {$taxonomy_table}.title IN ($arg) AND` {$taxonomy_table}.playlist_id=%d
ORDER BY {$media_table}.order_id";
or
$query = "SELECT t1.* FROM {$media_table} t1
INNER JOIN {$taxonomy_table} t2
ON t1.id = t2.media_id
WHERE t2.type='tag' AND t2.title IN ($arg) AND t2.playlist_id=%d
ORDER BY t1.order_id";
UPDATE:
You've raised two "new" question in the comment:
From your use-cases I don't see any reason for using a join here.
1: retrieve rows from media_table that have ALL required title(s) from
taxonomy_table
From my point of view there is no simple solution with just using SQL (except maybe really hacky string-operations in SQL).
Easiest solution might be something like this:
$countTitles = count(explode(",", $args))
$query = "SELECT media_id from {$media_table} WHERE $countTitles = (
SELECT count(media_id) from {$taxonomy_table} WHERE type='tag' AND title IN ($arg) AND playlist_id=%d
)"
2: retrieve rows from media_table that ANY required title(s) from
taxonomy_table.
This is just a simple in-clause.
$query = "SELECT * FROM {$media_table}
WHERE media_id IN (
SELECT media_id FROM {$taxonomy_table} WHERE type='tag' AND title IN ($arg) AND playlist_id=%d
)
";
use a union instead of join with a nested / join query
$query = "SELECT * FROM {$media_table}
INNER JOIN {$taxonomy_table}
ON {$media_table}.id = {$taxonomy_table}.media_id
WHERE {$taxonomy_table}.type='tag' AND {$taxonomy_table}.title IN ($arg) AND {$taxonomy_table}.playlist_id=%d
ORDER BY {$media_table}.order_id
union SELECT * FROM {$media_table}
WHERE media_id IN (
SELECT media_id FROM {$taxonomy_table} WHERE type='tag' AND title IN ($arg) AND playlist_id=%d
)
";
Related
I have my tables like this (currently)
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user_opts` (
`user_id` bigint(20) NOT NULL,
`opt1` varchar(64) DEFAULT NULL,
`opt2` TINYINT(4) DEFAULT NULL,
`opt3` varchar(64) DEFAULT NULL,
KEY `user_id_idx` (`user_id`)
);
I want to be able to do queries like this:
SELECT DISTINCT name
FROM users
WHERE
id = 1 AND (
EXISTS ( SELECT 1 FROM user_opts WHERE user_opts.user_id = users.id AND user_opts.opt1 = 'a' AND user_opts.opt3 = 'c') OR
EXISTS ( SELECT 1 FROM user_opts WHERE user_opts.user_id = users.id AND user_opts.opt1 = 'b' AND user_opts.opt2 = 1)
);
and this:
SELECT DISTINCT name
FROM users
WHERE
id = 1 AND (
EXISTS ( SELECT 1 FROM user_opts WHERE user_opts.user_id = users.id AND user_opts.opt1 = 'a' AND user_opts.opt3 = 'e') AND
EXISTS ( SELECT 1 FROM user_opts WHERE user_opts.user_id = users.id AND user_opts.opt1 = 'b' AND user_opts.opt2 = 1)
);
The obvious problem I'm starting to have is that the more users the queries goes slower and slower. I know I could refactor the first type of the queries (using OR) by JOINing the table, but the JOIN itself would be slow since I can't have a PK on the user_opts table.
How could I restructure my data (and the queries) so I can do efficient/fast searches? Preferably, if possible, I would like to keep the same queries for both AND and OR types, just switching the condition between the two.
DB Fiddle url
Thanks!
You can use aggregation:
select user_id
from user_opts uo
where opt3 = 'c' or opt2 = 1
group by user_id
having sum(opt3 = 'c') >= 1 and
sum(opt2 = 1) >= 1;
This handles the case when the two options are set on the same row in user_opts.
Adding these two indexes would speed up the EXISTS:
INDEX(user_id, opt1, opt3)
INDEX(user_id, opt1, opt2)
Your schema is a variant on EAV, which is notoriously inefficient and clumsy. Is there a good reason not to have opt2 and opt3 in users?
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.
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.
I have 2 tables, one is called items, and the other is called sliders.
I want to call all the columns where sliders.item_id = items.id ,
and both are published so items.published=1 and sliders.published=1. But also I want to call sliders.item_id's that are NULL.
So basicly is like a right join where i get all related record that match the constrain but also records on the sliders table that don't correspond to items table.
In few words the point is this: i want to get ALL sliders that belongs to items (sliders.item_id=items.id) AND (sliders.published=1 AND items.published=1) BUT also sliders where item_id=null.
I have made a working query, but it does not satisfy me.
select *
from items
right join sliders
on items.id = 27
and items.id = sliders.item_id
and items.published = 1
where sliders.published = 1
THE TABLES
CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
`item_description` text NOT NULL,
`published` int(11) NOT NULL,
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `sliders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pic_path` text NOT NULL,
`item_id` int(11) DEFAULT NULL,
`published` int(11) NOT NULL,
PRIMARY KEY (`id`)
)
First i want it in basic MySQL query because i can convert in cakephp later.
try this query,
SELECT *
FROM items
RIGHT JOIN sliders
ON items.id = sliders.item_id
WHERE items.published = 1 AND sliders.published = 1
when u got all record from table which are published from both :
$SQL = "SELECT *
FROM items
RIGHT JOIN sliders
ON items.id=sliders.id
WHERE items.published=1 AND sliders.published=1";
And when u want to get particular id record..
$id=27
$QRY = "SELECT *
FROM items
RIGHT JOIN sliders
ON items.id=sliders.id
WHERE items.published=1 AND sliders.published=1 AND items.id= $id";
and if you want to get more then one id records then...
$ids="27,28,29,30,50,55";
$QRY = "SELECT *
FROM items
RIGHT JOIN sliders
ON items.id=sliders.id
WHERE items.published=1 AND sliders.published=1 AND items.id in($ids)";
in last query u fetch records which is published in both table and id from the above.
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.