Create view with two reference to the same table - mysql

I have two tables
CREATE TABLE `persons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `marriage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`place` varchar(45) DEFAULT NULL,
`spouse1` int(11) NOT NULL,
`spouse2` int(11) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `sp1` FOREIGN KEY (`id`) REFERENCES `persons` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `sp2` FOREIGN KEY (`id`) REFERENCES `persons` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
Now I would like to create the view with place of marriage and full names of both spouse. How can I do it? I tried like this (obviously not correctly):
CREATE
ALGORITHM = UNDEFINED
DEFINER = `root`#`localhost`
SQL SECURITY DEFINER
VIEW `mar` AS
select
`marriage`.`place` AS `place`,
`persons`.`first_name` AS `first_name_sp1`,
`persons`.`first_name` AS `first_name_sp2`
from
(`persons`
join `marriage`)
where
((`marriage`.`spouse1` = `persons`.`id`)
and (`marriage`.`spouse2` = `persons`.`id`))

The select that you want needs to mention persons twice:
select m.place, p1.first_name as first_name_sp1,
p2.first_name as first_name_sp2
from marriage m join
persons p1
on m.spouse1 = p1.id join
persons p2
on m.spouse2 = p2.id;
You can put this in a view just by putting create view mar as before it.
And note the use of table aliases to distinguish between the two tables. This is a case where table aliases are required. However, they often make a query easier to write and to read.

JOIN to Persons Twice, using aliases, and I would suggest replacing the WHERE joins with an ANSI JOIN:
select
m.place AS place,
p1.first_name` AS first_name_sp1,
p2.first_name AS first_name_sp2
from
marriage m
INNER JOIN persons p1
ON m.spouse1 = p1.id
INNER JOIN persons p2
ON m.spouse2 = p2.id

Related

How can i optimize the mysql COUNT() query with many-to-many relationship(through the 3rd table)

Here is the query that i execute:
SELECT COUNT(*)
FROM (SELECT `p`.*
FROM `shop_products` `p` LEFT JOIN
`shop_tag_assignments`
ON `p`.`id` = `shop_tag_assignments`.`product_id` LEFT JOIN
`shop_tags`
ON `shop_tag_assignments`.`tag_id` = `shop_tags`.`id`
WHERE `p`.`status`=1
GROUP BY `p`.`id`
) `c`
This query takes about 300 milliseconds(i think it's too long..)
EXPLAIN QUERY:
EXPLAIN QUERY IMAGE
DB tables dump:
1k records in shop_tags table
CREATE TABLE `shop_tags` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`label` text COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `shop_tags`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `idx-shop_tags-slug` (`slug`),
ADD KEY `id` (`id`);
ALTER TABLE `shop_tags`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1162;
COMMIT;
Table structure for shop_tag_assignments:
224k records in shop_tag_assignments table
CREATE TABLE `shop_tag_assignments` (
`product_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `shop_tag_assignments`
ADD PRIMARY KEY (`product_id`,`tag_id`),
ADD KEY `idx-shop_tag_assignments-product_id` (`product_id`),
ADD KEY `idx-shop_tag_assignments-tag_id` (`tag_id`),
ADD KEY `_index_name` (`product_id`,`tag_id`),
ADD KEY `__index_name` (`tag_id`,`product_id`);
ALTER TABLE `shop_tag_assignments`
ADD CONSTRAINT `fk-shop_tag_assignments-product_id` FOREIGN KEY (`product_id`) REFERENCES `shop_products` (`id`) ON DELETE CASCADE,
ADD CONSTRAINT `fk-shop_tag_assignments-tag_id` FOREIGN KEY (`tag_id`) REFERENCES `shop_tags` (`id`) ON DELETE CASCADE;
COMMIT;
Mysql version:
5.7.16-10-log
Get rid of the subquery. And don't use backticks so much:
SELECT COUNT(DISTINCT p.id)
FROM shop_products p LEFT JOIN
shop_tag_assignments sta
ON p.id = sta.product_id LEFT JOIN
shop_tags st
ON sta.tag_id = st.id
WHERE p.status = 1;
For this query, you want indexes on shop_products(status, id).
Next, your query is counting the number of rows returned by the inner subquery after aggregation by p.id. You are using LEFT JOIN, so nothing is being filtered out. That means that the joins are really redundant. I think this does the same thing:
SELECT COUNT(*)
FROM shop_products p
WHERE p.status = 1;
This should be much faster than your version . . . however, your version is pretty fast so you might not notice a really big difference.

How to delete data in two linked tables (many-to-many relationship) with one query?

There are tables films,actors and the table binding them films_actors.
CREATE code:
CREATE TABLE `actors` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(50) NOT NULL,
`surname` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=9
;
CREATE TABLE `films` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`release` SMALLINT(4) NOT NULL,
`format` VARCHAR(10) NOT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=6
;
CREATE TABLE `films_actors` (
`id_film` INT(11) NOT NULL,
`id_actor` INT(11) NOT NULL,
INDEX `FK_films_actors_actors` (`id_actor`),
INDEX `FK_films_actors_films` (`id_film`),
CONSTRAINT `FK_films_actors_actors` FOREIGN KEY (`id_actor`) REFERENCES `actors` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `FK_films_actors_films` FOREIGN KEY (`id_film`) REFERENCES `films` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
If I delete data from the tables actors orfilms, then the data will automatically be deleted from the films_actors table, the question is whether one query can delete data from all three tables at once?
Here is the query that I tried to write, but without success.
delete
from films, actors
where actors.id in (
select films_actors.id_actor from films_actors
where films_actors.id_film = 5
) and films.id = films_actors.id_film
You could use an delete join and use a subquery in join for manage the IN clause
delete films.*, actors.*
from films
INNER JOIN (
select films_actors.id_actor, id_film
from films_actors
where films_actors.id_film = 5
) t ON films.id = t.id_film
INNER JOIN actors ON actors.id = t.id_actor
You should be able to do:
delete f, a, fa
from films f join
films_actors fa
on fa.id_film = f.id join
actors a
on fa.id_actor = a.id
where f.id = 5;
This seems dangerous, because actors could be in other films. I assume you only really want to delete from f and fa.

mysql view users vs admin users syntax inquiry

Setting up JDBC security realm with Glassfish and I came across this link that provides user control access (admin vs view users). here is the link for this tutorial.
http://jugojava.blogspot.com.eg/2011/02/jdbc-security-realm-with-glassfish-and.html?showComment=1459000091065#c232448315667617784
Few things I didn't understand in the guide and I hope if you can help. What do the lines below mean? (full sql code at the end of of the question)
KEY `fk_users_has_groups_groups1` (`group_id`),
KEY `fk_users_has_groups_users` (`user_id`),
I don't see any table named u but the code is using u.*. The code at the very bottom doesn't have "u" table in it. However, I see the reference to "u" in the inner Join. Can I do select u. based on this INNER JOIN?
CREATE VIEW `v_user_role` AS
SELECT u.username, u.password, g.group_name
FROM `user_groups` ug
INNER JOIN `users` u ON u.user_id = ug.user_id
INNER JOIN `groups` g ON g.group_id = ug.group_id;
Here is the full Code
CREATE TABLE `groups` (
`group_id` int(10) NOT NULL,
`group_name` varchar(20) NOT NULL,
`group_desc` varchar(200) DEFAULT NULL,
PRIMARY KEY (`group_id`)
);
CREATE TABLE `users` (
`user_id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(10) NOT NULL,
`first_name` varchar(20) DEFAULT NULL,
`middle_name` varchar(20) DEFAULT NULL,
`last_name` varchar(20) DEFAULT NULL,
`password` char(32) NOT NULL,
PRIMARY KEY (`user_id`)
);
CREATE TABLE `user_groups` (
`user_id` int(10) NOT NULL,
`group_id` int(10) NOT NULL,
PRIMARY KEY (`user_id`,`group_id`),
KEY `fk_users_has_groups_groups1` (`group_id`),
KEY `fk_users_has_groups_users` (`user_id`),
CONSTRAINT `fk_groups` FOREIGN KEY (`group_id`) REFERENCES `groups` (`group_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
);
CREATE VIEW `v_user_role` AS
SELECT u.username, u.password, g.group_name
FROM `user_groups` ug
INNER JOIN `users` u ON u.user_id = ug.user_id
INNER JOIN `groups` g ON g.group_id = ug.group_id;
INSERT INTO `groups`(`group_id`,`group_name`,`group_desc`) VALUES
(1,'USER','Regular users'),
(2,'ADMIN','Administration users');
INSERT INTO `users`(`user_id`,`username`,`first_name`,`middle_name`,`last_name`,`password`) VALUES
(1,'john','John',NULL,'Doe','6e0b7076126a29d5dfcbd54835387b7b'), /*john123*/
(2,'admin',NULL,NULL,NULL,'21232f297a57a5a743894a0e4a801fc3'); /*admin*/
INSERT INTO `user_groups`(`user_id`,`group_id`) VALUES (1,1),(2,1),(2,2);
1) KEY indicates that those fields are indexed. The index is not unique. See create index documentation.
2) The "u" is an alias, specifically it is a table alias. You can use aliases to refer to a table or field name under a different name. An alias is specific to a query. The alias is defined in the from clause:
`users` u

Is the use of cursor needed to achieve the following query?

Consider the database:
A Company has zero or more Branch companies
A Company has zero or more Emails
A Branch has zero or more Emails
And consider the following instance of that database:
1 Company with 2 Branches
The Company has 1 Email
Each branch has 2 Emails
(5 Emails in total)
If you prefer, check the graphical representation of that instance.
Query
I'd like to make a query (MySQL) that retrieves all e-mails related to a given company (5, in the example). I couldn't find a solution, so after a bit of googling I found out that there's a thing called cursor.
Question
Is the use of cursor needed to achieve the query above? Would it be a good solution? What would be your solution to the query above?
(tables are defined below)
My idea was to retrieve all branches for a given company (that query I could make), but from there I don't know how to iterate through the branches (result of the query) and get all the e-mails.
Tables definitions in MySQL
-- Database tables
CREATE TABLE `company` (
`c_id` INT(11) NOT NULL AUTO_INCREMENT,
`c_name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`c_name`),
UNIQUE KEY `c_id` (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
CREATE TABLE `branch` (
`b_id` INT(11) NOT NULL AUTO_INCREMENT,
`c_id` INT(11) NOT NULL,
`b_name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`b_name`),
UNIQUE KEY `b_id` (`b_id`),
KEY `c_id` (`c_id`),
CONSTRAINT `branch_ibfk_1` FOREIGN KEY (`c_id`)
REFERENCES `company` (`c_id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
CREATE TABLE `email` (
`e_id` INT(11) NOT NULL AUTO_INCREMENT,
`e_addr` VARCHAR(45) NOT NULL,
PRIMARY KEY (`e_addr`),
UNIQUE KEY `e_id` (`e_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
CREATE TABLE `c_email` (
`c_id` INT(11) NOT NULL,
`e_id` INT(11) NOT NULL,
PRIMARY KEY (`c_id`,`e_id`),
KEY `e_id` (`e_id`),
CONSTRAINT `c_email_ibfk_1` FOREIGN KEY (`c_id`)
REFERENCES `company` (`c_id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `c_email_ibfk_2` FOREIGN KEY (`e_id`)
REFERENCES `email` (`e_id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `b_email` (
`b_id` INT(11) NOT NULL,
`e_id` INT(11) NOT NULL,
PRIMARY KEY (`b_id`,`e_id`),
KEY `e_id` (`e_id`),
CONSTRAINT `b_email_ibfk_1` FOREIGN KEY (`b_id`)
REFERENCES `branch` (`b_id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `b_email_ibfk_2` FOREIGN KEY (`e_id`)
REFERENCES `email` (`e_id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- Example of query
-- Retrieving all e-mails of a given branch
SELECT e_addr
FROM email AS _em
JOIN b_email AS _be ON _be.e_id = _em.e_id
JOIN branch AS _br ON _br.b_id = _be.b_id
WHERE b_name = #bra2;
-- How to retrieve all e-mails related to a given company
-- (e-mails from the company itself and also from all of its branches)?
-- ?
No, you don't need a cursor for this. You can do this with SQL -- much better than a cursor. It is something like:
select *
from ((select ce.c_id, e.email
from c_email ce join
email e
on e.e_id = ce.e_id
) union all
(select b.c_id, e.email
from branch b join
b_email be
on b.b_id = be.b_id join
email e
on be.e_id = e.e_id
)
) e
where ce.c_id = X;
EDIT:
You can add b_id into the second subquery but not the first. Perhaps you could do:
select *
from ((select ce.c_id, e.email, NULL as b_id
from c_email ce join
email e
on e.e_id = ce.e_id
) union all
(select b.c_id, e.email, b.b_id
from branch b join
b_email be
on b.b_id = be.b_id join
email e
on be.e_id = e.e_id
)
) e
where ce.c_id = X
order by c_id, b_id;

What is the best way to query data between two tables which is not directly related

What is the best way to query data between two tables which is not directly related, but through third or even fourth table, that's related using foreign keys?
Something like 1 -> 2 -> 3 -> 4.
I saw many very different suggestions, but would like to find out what could be the best way in such situation, where I try to maintain relational database model but also avoid super complicated SQL queries.
Here is example that matches my case:
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`city_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_city_id` FOREIGN KEY (`city_id`)
REFERENCES `city` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
CREATE TABLE `city` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`country_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_country_id` FOREIGN KEY (`country_id`)
REFERENCES `country` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
CREATE TABLE `country` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`continent_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_continent_id` FOREIGN KEY (`continent_id`)
REFERENCES `continent` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
CREATE TABLE `continent` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
Lets say I want to get all users from specific Continent
SELECT name FROM user WHERE city_id IN (
SELECT id FROM city WHERE country_id IN (
SELECT id FROM country WHERE continent_id = 1 ) ) );
It looks fine, I guess, but what if I want to get all users from specific City
SELECT name FROM user WHERE city_id IN (
SELECT id FROM city WHERE country_id IN (
SELECT id FROM country WHERE continent_id = (
SELECT continent_id FROM country WHERE id = (
SELECT country_id FROM city WHERE id = 1 ) ) ) );
Is this efficient?
Maybe it's possible to achieve the same with JOIN?
I tried to avoid adding all values to user table, as to maintain that relation city->country->continent and involve relations to do the job, but maybe in this case it's just not worth doing so? ..maybe not efficient, and it's better to redesign database?
Should be something like this and you may want to analyse (with explain why an JOIN is better vs subquery) EXPLAIN manual page http://dev.mysql.com/doc/refman/5.7/en/explain.html
Please note that you can only trust INNER JOIN with filters like below with LEFT JOIN or RIGHT JOIN you can get wrong results..
For all users within an continent with continent_id = 1
SELECT
user.name
FROM
user
INNER JOIN
city
ON
user.city_id = city.id
INNER JOIN
country
ON
city.country_id = country.id
AND
country.continent_id = 1
;
For all users within an city with city_id = 1
SELECT
user.name
FROM
user
INNER JOIN
city
ON
user.city_id = city.id
AND
city.id = 1
see http://sqlfiddle.com/#!2/1c40e/23
Inner joins
-- get users to cities
from
City c
inner join Users u
on c.Id = u.City_Id
-- users to continents
from
Users u
inner join City ci
on u.City_id = ci.Id
inner join Country co
on ci.Country_Id = co.Id
inner join Continent con
on co.Continent_Id = con.Id