I was up all last night trying to crack this but with no luck so I'm hoping you guys can help as I'm all out of ideas:
I have two parent tables that I want to populate a Junction table from:
Brides:
create table if not exists `Brides` (
`BrideID` INT not null auto_increment,
`MaidenName` varchar(10) unique,
primary key (`BrideID`)
) engine=InnoDB;
insert into Brides (MaidenName)
values ('Smith'),
('Jones')
;
Churches:
create table if not exists `Churches` (
`ChurchID` INT not null auto_increment,
`ChurchName` varchar(10) unique,
primary key (`ChurchID`)
) engine=InnoDB;
insert into Churches (ChurchName)
values ('St Marys'),
('St Albans')
;
I am trying to populate the ID variables for Junction table Marriages by indirectly referencing the unique names in each parent table. In addition, I'm looking to include MarriedName to identify if a Bride marries more than once:
Marriages:
create table if not exists 'Marriages' (
'BrideID' INT not null,
'ChurchID' INT not null,
'MarriedName' TEXT not null
primary key ('BrideID','ChurchID','MarriedName')
INDEX `fk_Marriages_Brides1_idx` (`BrideID` ASC),
INDEX `fk_Marriages_Churches1_idx` (`ChurchID` ASC),
CONSTRAINT `fk_Marriages_Brides1`
FOREIGN KEY (`BrideID`)
REFERENCES `Brides` (`BrideID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_Marriages_Churches1`
FOREIGN KEY (`Church_ID`)
REFERENCES `Churches` (`ChurchID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
I'm trying to do something like the below pseudo-code (although I'm pretty sure it wouldn't be the smart way to do it anyway as must be slow with with so many sub-queries):
insert into Marriages (Bride_ID, Church_ID, MarriedName)
select b.BrideID, c.ChurchID, m.MarriedName
from (values (Bride,Church,MarriedName)
('Smith','St Marys','Johnson'),
('Jones','St Albans','Peterson')
) m
join Brides b
on a.MaidenName=m.Bride
join Churches c
on m.Church=c.ChurchName;
Any help/insight/corrections you have would be greatly appreciated!
Give this a try:
INSERT INTO Marriages
SELECT b.BrideID, c.ChurchID, 'Johnson'
FROM Brides b, Churches c
WHERE b.MaidenName='Smith' AND c.ChurchName='St Marys'
Just for completeness, I wanted to share this solution in case it helps anyone else....First and last step requires no changes and just the 'raw' data in the second step needs adding to:
create table if not exists `_marriages` (
`BrideName` varchar(10) null,
`ChurchName` varchar(10) null,
`MarriedName` varchar(10) NULL,
primary key (`BrideName`,`ChurchName`,`MarriedName`))
engine=InnoDB
;
insert into `_marriages` (BrideName,ChurchName,MarriedName)
values
('Smith','St Albans','Johnson'),
('Jones','St Marys','Peterson')
;
insert into `Marriages` (Bride_ID, Church_ID, MarriedName)
select distinct b.BrideID, c.ChurchID, a.MarriedName
from _marriages a
inner join Brides b
on (a.BrideName=b.MaidenName)
left join Churches c
on a.ChurchName=c.ChurchName
;
...as always though, if anyone has any advancement on a better way to do it please let us know!
Related
I want to get data just from only one specific user but I get data from both users. Why is that? I don't understand. How can I solve this?.
I have three tables:
/*User*/
CREATE TABLE `User` (
`IDUser` INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`IDUser`)
);
/*Category*/
CREATE TABLE `Category` (
`IDCategory` CHAR(3) NOT NULL,
`FK_User` INT NOT NULL,
`CategoryName` VARCHAR(40) NOT NULL,
PRIMARY KEY (`IDCategory`, `FK_User`)
);
/*Product*/
CREATE TABLE `Product` (
`IDProduct` VARCHAR(18) NOT NULL,
`FK_User` INT NOT NULL,
`ProductName` VARCHAR(150) NOT NULL,
`FK_Category` CHAR(3) NOT NULL,
PRIMARY KEY (`IDProduct`, `FK_User`)
);
ALTER TABLE `Product` ADD FOREIGN KEY (`FK_User`) REFERENCES `User`(`IDUser`);
ALTER TABLE `Product` ADD FOREIGN KEY (`FK_Category`) REFERENCES `Category`(`IDCategory`);
ALTER TABLE `Category` ADD FOREIGN KEY (`FK_User`) REFERENCES `User`(`IDUser`);
insert into User(Name) values('User1');
insert into User(Name) values('User2');
insert into Category(IDCategory,FK_User,CategoryName) values('CT1',1,'Category1User1');
insert into Category(IDCategory,FK_User,CategoryName) values('CT1',2,'Category1User2');
If two different users insert both the same product with the same ID:
insert into Product values('001',1,'shoe','CT1');
insert into Product values('001',2,'shoe','CT1');
Why do I keep getting data from both users if I try a query like this one:
SELECT P.IDProduct,P.ProductName,P.FK_Category,C.CategoryName
FROM Product P inner join Category C on P.FK_Category=C.IDCategory
WHERE P.FK_User=1
this is the result I get:
You are getting two rows because both categories have the same IDCategory value which is the value you are JOINing on. You need to also JOIN on the FK_User values so that you don't also get User2's category values:
SELECT P.IDProduct,P.ProductName,P.FK_Category,C.CategoryName
FROM Product P
INNER JOIN Category C ON P.FK_Category=C.IDCategory AND P.FK_User = C.FK_User
WHERE P.FK_User=1
You need to add p.FK_User=C.Fk_User this condition in your join clause
SELECT P.IDProduct,P.ProductName,P.FK_Category,C.CategoryName
FROM Product P inner join Category C
on P.FK_Category=C.IDCategory and p.FK_User=C.Fk_User
WHERE P.FK_User=1
A PRIMARY KEY is a UNIQUE key. Shouldn't CategoryID be unique? That is, shouldn't Category have PRIMARY KEY(CategoryId)?
(Check other tables for a similar problem.)
I have this tables from the MySql database i'm working with:
create table employees (
num_pass int(5) not null,
name varchar(40),
primary key (num_pass)
)engine=innodb;
create table laboratories (
code int(10) not null,
name varchar(40),
primary key (codi),
)engine=innodb;
create table areas (
code int(5) not null,
codeLab int(10) not null,
level enum('H','M','L'),
primary key (code, codeLab),
foreign key (codeLab) references laboratories(code)
)engine=innodb;
create table qualifieds (
num_pass int(5) not null,
area_assigned int(5),
lab int(5),
primary key (num_pass),
foreign key (num_pass) references employees(num_pass),
foreign key (area_assigned, lab) references areas (code, codeLab)
)engine=innodb;
Now i want to get what areas have more than three qualified employees. Specifically, i want to get area code along with the name of the laboratory, laboratory and ordered by region.
I tried to use this command in order to get the code of the area
select b.code
from employees e, areas b, qualifieds q
where e.num_pass=q.num_pass
and 3 < (select count(b1.code)
from areas b1, qualifieds q1, employers e1
where e1.num_pass=q1.num_pass
and q1.area_assigned=b1.code
and q1.lab=b1.codeLab);
But all what i get is a list of all the area codes repeated as many times as the number of employees (i have areas with code 1,2,3,4 and 6 employees, what i get is the sequence 1,2,3,4 repeated 6 times).Any idea about how to get the information i need?
You can use GROUP BY and HAVING to find a group with more than 3 records...
select areas_assigned, lab, count(num_pass)
from qualifieds
group by areas_assigned, lab
having count(num_pass)>3;
If you then need to expand on this, then add joins...
select q.areas_assigned, l.name, count(q.num_pass)
join laboratories l on l.lab = q.lab
from qualifieds q
group by q.areas_assigned, l.name
having count(q.num_pass)>3;
I have a table describing changes that has been made to an end_customers table. When someone changes and end_customer we create a new row in the end_customers table and add a row to end_customer_history table where end_customer_parent_id is the ID of the old end_customer, and end_customer_child_id is the ID of the new end_customer.
End Customer Table:
CREATE TABLE `end_customers` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`reference_person` varchar(255) DEFAULT NULL,
`phone_number` varchar(255) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`social_security_number` varchar(255) DEFAULT NULL,
`comment` longtext,
`token` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=101107 DEFAULT CHARSET=utf8;
End Customer History Table:
CREATE TABLE `end_customer_history` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`end_customer_parent_id` bigint(20) NOT NULL,
`end_customer_child_id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_end_customer_parent` (`end_customer_parent_id`),
KEY `FK_end_customer_child` (`end_customer_child_id`),
KEY `FK_user` (`user_id`),
CONSTRAINT `end_customer_history_old_ibfk_1` FOREIGN KEY (`end_customer_parent_id`) REFERENCES `end_customers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `end_customer_history_old_ibfk_2` FOREIGN KEY (`end_customer_child_id`) REFERENCES `end_customers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `end_customer_history_old_ibfk_3` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8;
We are now refactoring the schema so that changes made to end_customers table directly edits the row instead of creating a new row, and puts a copy of the old data in a end_customer_history_new table that has the same schema as end_customers.
I need to migrate all old data to this new table.
So for each end_customer I have, I need to check if it has an entry in end_customer_history as a end_customer_child_id (it has been modified), and then check if that entrys parent is also present in end_customer_history as a child, and then check if that entrys parent is also present in end_customer_history as a child, and so forth until there are no more rows.
How do I do this in one migration SQL script?
Unlike Oracle, MySQL does not provide the functionality to recursively query parent-child hierarchy. You can write a self join query (or inner queries) if you know the level beforehand. If not, you have to perform a single query and handle resursiveness in the application or stored procedure.
Here is an example of querying a parent child hierarchy if you know the level already, and here is an example of how to do it in Oracle (for your reference).
In the solution I came up with, it doesn't use any stored functions to loop over rows, because I could never get it to work. It takes the latest version of an end customer, and then joins similar rows using a token that we know is always the same and unique.
-- Move data from old end customer history to the new schema
-- May remove some redundant history, this will be ok
-- If an end customer has been changed more than once, all changes will have the same date (latest change) and the same user responsible for change
-- This is also ok
-- Insert the latest version of changed end_customers into new history table
INSERT INTO end_customer_history (name, reference_person, phone_number, email, social_security_number, comment, end_customer_id, user_id, date)
SELECT
ec.name, ec.reference_person, ec.phone_number, ec.email, ec.social_security_number, ec.comment, ech.end_customer_child_id, ech.user_id, ech.date
FROM end_customer_history_old AS ech
JOIN end_customers AS ec ON ec.id = ech.end_customer_parent_id
WHERE ech.end_customer_child_id IN (SELECT end_customer_id FROM orders);
-- Remove all the old data
DROP TABLE end_customer_history_old;
-- Insert all versions of an end_customer based on the latest history, joined on token (always the same)
INSERT INTO end_customer_history (name, reference_person, phone_number, email, social_security_number, comment, end_customer_id, user_id, date)
SELECT
parent.name, parent.reference_person, parent.phone_number, parent.email, parent.social_security_number, parent.comment, ech.end_customer_id, ech.user_id, ech.date
FROM end_customer_history AS ech
JOIN end_customers AS ec ON ec.id = ech.end_customer_id
JOIN end_customers AS parent ON parent.token = ec.token AND parent.id <> ec.id;
-- This unfortunately creates duplicates of some rows, so it looks like changes have been made multiple times (with no new data)
-- Create a temporary table that copies over unique rows from end_customer_history
CREATE TABLE temp AS
SELECT * FROM end_customer_history
GROUP BY
name, reference_person, phone_number, email, social_security_number, comment, end_customer_id, user_id, date;
-- Clear the end_customer_history table all together
TRUNCATE TABLE end_customer_history;
-- Copy over filtered unique history rows
INSERT INTO end_customer_history (name, reference_person, phone_number, email, social_security_number, comment, end_customer_id, user_id, date)
SELECT
name, reference_person, phone_number, email, social_security_number, comment, end_customer_id, user_id, date
FROM temp;
-- Remove temporary table
DROP TABLE temp;
I've came across an issue and I cant think of a way to solve it.
I need to insert country names in several languages into a table on my mysql db.
I found these links link1 (en) , link2 (de) etc but I dont know how to proceed in order to finally have a table looking like this:
CREATE TABLE `country` (
`id` varchar(2) NOT NULL,
`en` varchar(64) NOT NULL,
`de` varchar(64) NOT NULL,
...
...
PRIMARY KEY (`id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;
Well, I finally figured it out so I'm posting to maybe help others.
I created 2 tables (country_en) and (country_de) and then ran the following statement:
DROP table if exists `countries`;
CREATE TABLE `countries` (
id varchar(2), el varchar(100), de varchar(100)
);
INSERT INTO `countries`
SELECT country_en.id, el, de
FROM country_en
JOIN country_de ON (country_en.id = country_de.id);
which creates the table countries and joins the other 2 tables on their common key id
I can suggest you another table design. Create languages table, and modify a little country table: add lang_id field and create foreign key - FOREIGN KEY (lang_id)
REFERENCES languages (id). Then populate languages and country tables.
For example:
CREATE TABLE languages(
id VARCHAR(2) NOT NULL,
name VARCHAR(64) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
CREATE TABLE country(
id VARCHAR(2) NOT NULL,
lang_id VARCHAR(2) NOT NULL DEFAULT '',
name VARCHAR(64) NOT NULL,
PRIMARY KEY (id, lang_id),
CONSTRAINT FK_country_languages_id FOREIGN KEY (lang_id)
REFERENCES languages (id) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE = INNODB;
-- Populate languages
INSERT INTO languages VALUES
('en', 'English'),
('de', 'German');
-- Populate names from 'en' table
INSERT INTO country SELECT id, 'en', name FROM country_en;
-- Populate names from 'de' table
INSERT INTO country SELECT id, 'de', name FROM country_de;
...Where country_en and country_deare tables from your links.
I have two tables, locations and location groups
CREATE TABLE locations (
location_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(63) UNIQUE NOT NULL
);
INSERT INTO locations (name)
VALUES
('london'),
('bristol'),
('exeter');
CREATE TABLE location_groups (
location_group_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
location_ids VARCHAR(255) NOT NULL,
user_ids VARCHAR(255) NOT NULL,
name VARCHAR(63) NOT NULL,
);
INSERT INTO location_groups (location_ids, user_ids, name)
VALUES
('1', '1,2,4', 'south east'),
('2,3', '2', 'south west');
What I am trying to do is return all location_ids for all of the location_groups where the given user_id exists. I'm using CSV to store the location_ids and user_ids in the location_groups table. I know this isn't normalised, but this is how the database is and it's out of my control.
My current query is:
SELECT location_id
FROM locations
WHERE FIND_IN_SET(location_id,
(SELECT location_ids
FROM location_groups
WHERE FIND_IN_SET(2,location_groups.user_ids)) )
Now this works fine if the user_id = 1 for example (as only 1 location_group row is returned), but if i search for user_id = 2, i get an error saying the sub query returns more than 1 row, which is expected as user 2 is in 2 location_groups. I understand why the error is being thrown, i'm trying to work out how to solve it.
To clarify when searching for user_id 1 in location_groups.user_ids the location_id 1 should be returned. When searching for user_id 2 the location_ids 1,2,3 should be returned.
I know this is a complicated query so if anything isn't clear just let me know. Any help would be appreciated! Thank you.
You could use GROUP_CONCAT to combine the location_ids in the subquery.
SELECT location_id
FROM locations
WHERE FIND_IN_SET(location_id,
(SELECT GROUP_CONCAT(location_ids)
FROM location_groups
WHERE FIND_IN_SET(2,location_groups.user_ids)) )
Alternatively, use the problems with writing the query as an example of why normalization is good. Heck, even if you do use this query, it will run more slowly than a query on properly normalized tables; you could use that to show why the tables should be restructured.
For reference (and for other readers), here's what a normalized schema would look like (some additional alterations to the base tables are included).
The compound fields in the location_groups table could simply be separated into additional rows to achieve 1NF, but this wouldn't be in 2NF, as the name column would be dependent on only the location part of the (location, user) candidate key. (Another way of thinking of this is the name is an attribute of the regions, not the relations between regions/groups, locations and users.)
Instead, these columns will be split off into two additional tables for 1NF: one to connect locations and regions, and one to connect users and regions. It may be that the latter should be a relation between users and locations (rather than regions), but that's not the case with the current schema (which could be another problem of the current, non-normalized schema). The region-location relation is one-to-many (since each location is in one region). From the sample data, we see the region-user relation is many-many. The location_groups table then becomes the region table.
-- normalized from `location_groups`
CREATE TABLE regions (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(63) UNIQUE NOT NULL
);
-- slightly altered from original
CREATE TABLE locations (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(63) UNIQUE NOT NULL
);
-- missing from original sample
CREATE TABLE users (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(63) UNIQUE NOT NULL
);
-- normalized from `location_groups`
CREATE TABLE location_regions (
`region` INT UNSIGNED,
`location` INT UNSIGNED UNIQUE NOT NULL,
PRIMARY KEY (`region`, `location`),
FOREIGN KEY (`region`)
REFERENCES regions (id)
ON DELETE restrict ON UPDATE cascade,
FOREIGN KEY (`location`)
REFERENCES locations (id)
ON DELETE cascade ON UPDATE cascade
);
-- normalized from `location_groups`
CREATE TABLE user_regions (
`region` INT UNSIGNED NOT NULL,
`user` INT UNSIGNED NOT NULL,
PRIMARY KEY (`region`, `user`),
FOREIGN KEY (`region`)
REFERENCES regions (id)
ON DELETE restrict ON UPDATE cascade,
FOREIGN KEY (`user`)
REFERENCES users (id)
ON DELETE cascade ON UPDATE cascade
);
Sample data:
INSERT INTO regions
VALUES
('South East'),
('South West'),
('North East'),
('North West');
INSERT INTO locations (`name`)
VALUES
('London'),
('Bristol'),
('Exeter'),
('Hull');
INSERT INTO users (`name`)
VALUES
('Alice'),
('Bob'),
('Carol'),
('Dave'),
('Eve');
------ Location-Region relation ------
-- temporary table used to map natural keys to surrogate keys
CREATE TEMPORARY TABLE loc_rgns (
`location` VARCHAR(63) UNIQUE NOT NULL
`region` VARCHAR(63) NOT NULL,
);
-- Hull added to demonstrate correctness of desired query
INSERT INTO loc_rgns (region, location)
VALUES
('South East', 'London'),
('South West', 'Bristol'),
('South West', 'Exeter'),
('North East', 'Hull');
-- map natural keys to surrogate keys for final relationship
INSERT INTO location_regions (`location`, `region`)
SELECT loc.id, rgn.id
FROM locations AS loc
JOIN loc_rgns AS lr ON loc.name = lr.location
JOIN regions AS rgn ON rgn.name = lr.region;
------ User-Region relation ------
-- temporary table used to map natural keys to surrogate keys
CREATE TEMPORARY TABLE usr_rgns (
`user` INT UNSIGNED NOT NULL,
`region` VARCHAR(63) NOT NULL,
UNIQUE (`user`, `region`)
);
-- user 3 added in order to demonstrate correctness of desired query
INSERT INTO usr_rgns (`user`, `region`)
VALUES
(1, 'South East'),
(2, 'South East'),
(2, 'South West'),
(3, 'North West'),
(4, 'South East');
-- map natural keys to surrogate keys for final relationship
INSERT INTO user_regions (`user`, `region`)
SELECT user, rgn.id
FROM usr_rgns AS ur
JOIN regions AS rgn ON rgn.name = ur.region;
Now, the desired query for the normalized schema:
SELECT DISTINCT loc.id
FROM locations AS loc
JOIN location_regions AS lr ON loc.id = lr.location
JOIN user_regions AS ur ON lr.region = ur.region
;
Result:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+