Convert One to Many to One to One Relationship MySQL - mysql

I have two MySQL tables we can call Foo and Bar.
Both tables have a column called PrizeGroupId. the goal is to create a one-to-one relationship between these columns, and I have created stored procedures to add/edit Foo that update the corresponding row via the one-to-one relationship in Bar.
The problem lies in the fact that the data wasn't always structured this way and I need to write a script to convert the data from it's previous state (which I'm about to describe) to a one-to-one relationship based off of PrizeGroupId.
Previously, multiple rows in Foo could have the same PrizeGroupId such that there was a one-to-many relationship between entries in Bar to Foo based off of PrizeGroupId. The script that I need to write has to break apart every one-to-many instance of this nature into many (almost identical) one-to-one relationships between Foo and Bar.
In principle, I want to:
Iterate through Foo
See if the current row's PrizeGroupId is not unique in Foo.
Assign it a unique value (perhaps the current items primary key)
Add a row in Bar with the new PrizeGroupId. Copy over all of the old row's other data into this new row such that it is "nearly identical".
After all is said and done, remove the old one-to-many row from Bar.
I understand the problem and how I could do this in pseudocode in a programming language, however I am still learning MySQL and am not sure how to go about solving a problem of this nature.
If you can provide me with help through MySQL code and/or what steps I can take/read about to go about solving this problem that would be, or at least point me to the kind of reading/SO question related to this kind of problem that would be appreciated, although I had a difficult time finding particular resources on my own.

What you are asking for is not that hard. Some of your thinking is getting in the way. First, one almost never iterates in SQL. SQL is not that kind of language. Everything in SQL is done via sets of something.
Your approach can be:
Identify the set of rows where the PrizeGroupId is already unique and move them to a new copy of the table.
To create a table, you can use "create table foo2 like foo;". Very useful.
To identify the rows where PrizeGroupId is already unique, use something like:
create table test_30602977 (id int primary key, other int);
insert into test_30602977 values (1, 1), (2, 2), (3, 2);
select other, count(*) as count from test_30602977 group by other having count = 1;
The rows left in the original table do not have a unique PrizeGroupId. Change the PrizeGroupId value so that they are unique.
Merge the two sets to reconstruct the table with the original rows and with PrizeGroupId unique.
One reason that this is hard is because if you had created the tables with the one-to-one join, you would have used the pk to join the tables. The pk is already unique so why use something else. Once you have the tables separated and the PrizeGroupId is unique, you might want to think about setting the pk of foo to the pk of bar and then removing the PrizeGroupId column.

What is required is for the Bar table to contain a single record for each record in Foo, and for the PrizeGroupId field in Foo to be unqiue. As Foo.Id is already unique, it makes sense to use that as the foreign key.
Running a query like SELECT Foo.id, Bar.* FROM Foo INNER JOIN Bar USING (PrizeGroupId) will give us a single record for each record in Foo, along with the data from the corresponding record from Bar. So, if we were to replace the data in Bar with what is returned by this query, and then use Foo.Id for PrizeGroupId, we'd acheive what is required.
Create a temporary table with the same structure as Bar - something like CREATE TABLE Bar_copy LIKE Bar
Fill the temporary table with one record for each record in Foo, joined to the corresponding record in Bar - you'll need to list all the columns in Bar, for example - INSERT INTO Bar_copy (id, field1, field2, field3) SELECT f.id, b.field1, b.field2, b.field3 FROM Foo AS f INNER JOIN Bar AS b USING (PrizeGroupId)
Clear the existing PrizeGroupID field from Foo - UPDATE Foo SET PrizeGroupId = NULL
Empty the existing Bar table and refill it with the records from the temporary table - INSERT INTO Bar (id, field1, field2, field3) SELECT id, field1, field2, field3 FROM Bar_copy
Update the foreign key values in Foo - UPDATE Foo SET PrizeGroupId = id
Obviously, take a back-up first!

Related

How to merge two db with same structure but different data

I'm working with phpmyadmin and I have to merge two db with same structure but different data.
The db have relation between tables (foreign key).
The data in two db may have same id, and so their foreign key.
I would like to know if it's possible merge the two db keeping all data, so, if a row already "exist", insert it with new id and update its foreign key.
thanks a lot
No easy way unfortunately. If you have TableA as a foreign key to TableB, you will need to
1) Insert data from source tableA to target tableA
2) create a (temp) table to store the mapping between source tableA ids and target tableA ids
3) Use this mapping table when inserting data from tableB to convert the tableA ids to the new ones in the target db
... and so on. It can get quite hairy if you have a deep hierarchy of tables, but hopefully you get the idea. Take backups before you start.
Another idea that you might want to consider is using a cursor:
Assume table A is the one that you want to keep and table B is the one you want to remove.
Declare a cursor for table B and select all the records.
Loop each record selected from the cursor and check.
Case 1: If the ID is exists on table A, insert the record to table A with same details.
Case 2: If the ID is exists on table B, insert the record and modify the ID and foreign key.
Once all the records have been checked, drop table B.
Sorry, I just can give an idea at the moment.

Issues with MySQL inserting columns below desired location

I have been having some frustration attempting to add data values to this table students. I have all the other data values and have dropped and created the column student_id. However, when trying to add the data with this query:
insert into students(student_id) values('1'),('2'),('3'),('4'),('5');
The data does not insert correctly, as it creates new columns below the first 5 which contain data.
It must be because of my not null values, but I can't not have the not null identifier.
Is there a query command that allows me to change data within already existing value-filled columns? I have been unsuccessful in finding this so far.
Here are some images to explain the problem further.
The query I have made to add my values to the table:
The data was inserted but as it is underneath the columns I need to map with a foreign key, I cannot use the column as the top 5 values are still my not null default, which is required to let me create the foreign key
Looks like you already have your records initially created without the student_id field, you want to UPDATE the current records but you're actually INSERTING new records.
You're meant to update your students with update statements such as "UPDATE students SET student_id = X where condition = Y"
Then it looks like your student_id is your primary key which you should set to AUTO_INCREMENT value.
Regards
INSERT is the wrong command since you want to update existing rows. The problem here lies within the fact that the order of the rows is nondeterministic and I think you cannot update them in one statement. One solution would be as follows:
UPDATE students SET student_id = 1 WHERE first_name = 'Berry';
UPDATE students SET student_id = 2 WHERE first_name = 'Darren';
I hope you really do have only 5 columns to update :-)

Remove Dupes with checks on secondary fields first

I have a table with a field (Name) I'd like to create a unique index on, however it seems there are existing duplicates. I dont' want to just get rid of dupes since some might have information in other fields that I need. Essentially I have:
ID
ParentID
Name
Code
RelatedID
So Goal 1 is I want to keep the record that has values in the secondary fields other then ID and Name. In most cases this will be one of the dupes only.
Goal 2 is in case two identical Names both have values but in different fields I want to 'merge' those since it is remotely possible one duplicate will have values in one key field and one in the other.
Finally Goal 3 is in the case that two names both have values in a key field I'd probably want to manually review those first.
It seems to me my first step as I read this would be Goal 3; manually review duplicates where Name Field is identical, and more then one record has a non-Null/non-empty value in a key field.
Once I address this the goal would be to 'mere' the remaining records i.e keep one record with Name and any non-null/non-empty key fields from the others.
Any thoughts much appreciated.
Sounds like a solid plan - hope you have a development environment you can dry run it in.
Here is some code that may help you along
Starting with Step 3.
This statement should help you find which records need to be reviewed.
SELECT *
FROM (
SELECT name,
GROUP_CONCAT(DISTINCT parentID) AS parentID,
GROUP_CONCAT(DISTINCT code) AS code,
GROUP_CONCAT(DISTINCT RelatedID) AS RelatedID,
FROM foo
GROUP BY name
HAVING COUNT(*)>1) as summarized
WHERE parentID LIKE '%,%'
OR code LIKE '%,%'
OR RelatedID LIKE '%,%';
Anything that comes up in that query you will probably have to manually fix after figuring out why there are multiple values for the same field.
Once those fixes are in place, it's times for the merge. I would create a holding / temporary table with the correct values. MAX should take care of the logic to choose non-null values
CREATE TABLE foo_values
SELECT name, MAX(parentID) as parentID, MAX(code) AS code, MAX(RelatedID) AS RelatedID.
FROM foo
GROUP BY name
HAVING COUNT(*)>1;
In theory, now you have the merged values. You can remove the duplicate name rows using whatever technique you are most comfortable with(See here) while adding your unique index. Finally, update the secondary fields by JOINing back to foo values.

Inserting into a table from an incompatible table

I have a MySql table called Person, and one day I accidentally deleted someone from this table. I have a backup table, called PersonBak so I was going to restore my deletion from the backup. However, in the course of moving forward on my application I renamed all the fields in Person, except for the primary key, PersonID. Now Person and PersonBak have the same data, but only one matching column name.
Is there any way to restore my missing person to Person from PersonBak without doing a lot of work? I have quite a few columns. Of course I could just do the work now, but I can imagine this coming up again.
Is there some way to tell MySql that these are really the same table, with the columns in the same order, just different column names? Or any way at all to do this without writing out specifics of which columns in PersonBak match which ones in Person?
If the column datatypes are the same between the tables, the column count is the same, and they are all in the same order, then MySQL will do all of the work for you:
INSERT INTO t1 SELECT * FROM t2;
The column names are ignored. The server uses ordinal position only, to decide how to line up the from/to columns.
What about this:
insert into Person(id, col11, col12) (select id, col21, col22 from personBak where id=5)
person schema:
columns (id, col11, col12)
personBak schema:
columns (id, col21, col22)
Look at Mysql SELECT INTO and you can specify the field names & create an insert statement

MySQL: LIKE Query Help?

I have a column in my table called student_id, and I am storing the student IDs associated with a particular record in that column, delimited with a | character. Here are a couple sample entries of the data in that column:
243|244|245
245|1013|289|1012
549|1097|1098|245|1099
I need to write a SQL query that will return records that have a student_id of `245. Any help will be greatly appreciated.
Don't store multiple values in the student_id field, as having exactly one value for each row and column intersection is a requirement of First Normal Form. This is a Good Thing for many reasons, but an obvious one is that it resolves having to deal with cases like having a student_id of "1245".
Instead, it would be much better to have a separate table for storing the student IDs associated with the records in this table. For example (you'd want to add proper constraints to this table definition as well),
CREATE TABLE mytable_student_id (
mytable_id INTEGER,
student_id INTEGER
);
And then you could query using a join:
SELECT * FROM mytable JOIN mytable_student_id
ON (mytable.id=mytable_student_id.mytable_id) WHERE mytable_student_id.student_id = 245
Note that since you didn't post any schema details regarding your original table other than that it contains a student_id field, I'm calling it mytable for the purpose of this example (and assuming it has a primary key field called id -- having a primary key is another requirement of 1NF).
#Donut is totally right about First Normal Form: if you have a one-to-many relation you should use a separate table, other solutions lead to ad-hoccery and unmaintainable code.
But if you're faced with data that are in fact stored like that, one common way of doing it is this:
WHERE CONCAT('|',student_id,'|') LIKE '%|245|%'
Again, I agree with Donut, but this is the proper query to use if you can't do anything about the data for now.
WHERE student_id like '%|245|%' or student_id like '%|245' or student_id like '245|%'
This takes care of 245 being at the start, middle or end of the string. But if you aren't stuck with this design, please, please do what Donut recommends.