how to change data in db from string to id - Rails, Mysql - mysql

Hello I have maybe easy problem maybe not...
History: I wanted to replace yaml file with statuses to db(mysql) and i had in user table column: status. When i replacing logic to db i have created model Status, created table and configured relationship with user...
Describe problem: When i created status_id in user table i have 2 columns: "status" and "status_id". The column "status" is string and have a lot of string value for example "confirmed". How to (using seed migration with statuses) and fill the "status_id" column. I mean if "status" column have value "confirmed" i'd like to have in "status_id" column value: 1.
statuses table:
id name
1 confirmed
2 not confirmed
3 something else
Users table
id status status_id
1 confirmed empty
2 confirmed empty
3 confirmed empty
4 not confirmed empty
5 not confirmed empty
6 something else empty
7 something else empty
User belongs_to :status
Status has_many :users
question: Why i didnt just change name and type in "status" column on
"status_id" with data type: "id"?
answer: Because i need to deploy it using capistrano to production
server and i cant losing data and remove data from status column.

Given the following (your) sample data:
create table statuses (id int, name char(20));
insert into statuses (id, name) values
(1, 'confirmed'),
(2, 'not confirmed'),
(3, 'something else');
create table users (id int, status char(20), status_id int);
insert into users (id, status) values
(1,'confirmed'),
(2,'confirmed'),
(3,'confirmed'),
(4,'not confirmed'),
(5,'not confirmed'),
(6,'something else'),
(7,'something else');
select * from users;
+------+----------------+-----------+
| id | status | status_id |
+------+----------------+-----------+
| 1 | confirmed | NULL |
| 2 | confirmed | NULL |
| 3 | confirmed | NULL |
| 4 | not confirmed | NULL |
| 5 | not confirmed | NULL |
| 6 | something else | NULL |
| 7 | something else | NULL |
+------+----------------+-----------+
7 rows in set (0.00 sec)
This update statement updates the status_id column in users with the appropriate values from statuses:
update users u
set u.status_id=(select s.id from statuses s where u.status=s.name);
Query OK, 7 rows affected (0.01 sec)
Rows matched: 7 Changed: 7 Warnings: 0
select * from users;
+------+----------------+-----------+
| id | status | status_id |
+------+----------------+-----------+
| 1 | confirmed | 1 |
| 2 | confirmed | 1 |
| 3 | confirmed | 1 |
| 4 | not confirmed | 2 |
| 5 | not confirmed | 2 |
| 6 | something else | 3 |
| 7 | something else | 3 |
+------+----------------+-----------+
7 rows in set (0.00 sec)
Hope this is what you asked for, because my answer does neither involve ruby, yaml, nor rails.

This sounds like a one-off data migration which can be handled in several different ways. One way is to write a one-off script or rake task which will update all the users.
some_task.rake
desc 'Add status id to existing users'
namespace :users do
task assign_status: :environment do
statuses = {}
Status.all.each {|status| statuses[status.name] = status.id}
User.all.each do |user|
user.update_attribute(status_id: statuses[user.status])
end
end
end
If you have a lot of users, you may want to use find_each which is more performant as it uses batching. You may also want to add some kind of progress reporting in the rake task.
This approach allows you to test the task against a copy of your existing database.

Related

MySQL: How to make sure update is always executed before select?

I am creating a web app that lets N number of users to enter receipt data.
A set of scanned receipts is given to users, but no more than 2 users should work on the same receipt.
i.e. User A and User B can work on receipt-1, but User C can not work on it(Another receipt, say receipt-2, should be assigned to the User C).
The table structure I am using looks similar to the following.
[User-Receipt Table]
+------------+--------------+
| user_id | receipt_id |
+------------+--------------+
| 000000001 | R0000000000 |
| 000000001 | R0000000001 |
| 000000001 | R0000000002 |
| 000000002 | R0000000000 |
| 000000002 | R0000000001 |
+------------+--------------+
[Receipt Table]
+-------------+--------+
| receipt_id | status |
+-------------+--------+
| R0000000000 | 0 |
| R0000000001 | 1 |
| R0000000002 | 0 |
| R0000000003 | 2 |
+-------------+--------+
★status 0:not assigned 1:assigned to a user 2: assigned to 2 users
select receipts from the receipt table whose status is not equal to '2'
insert the receipts fetched from the step 1 along with a user to whom receipts are assigned.
update the receipt status(0->1 or 1->2)
This is how I plan to achieve the above requirement.
The problem with this approach is that there could be a chance that the select(step1) is executed right before the update(step3) is executed.
If this happens, the receipts with status 2 might be fetched and assigned to another user, which does not meet the requirement.
How can I make sure that this does not happen?
For all purposes, use transactions :
START TRANSACTION
your SQL commands
COMMIT
Transactions either let all your statements executed or not executed at all and performs implicitly a lock on the updated row which is more efficient than the second approach
You can also do it using LOCK TABLE

Unique value combination in MySQL

I'm building a simple friendship table with 3 columns: id, user1, user2
Once a user becomes friends with another user, their ids will be added to the table like this:
+----+-------+-------+
| id | user1 | user2 |
+----+-------+-------+
| 1 | 15 | 9 |
+----+-------+-------+
| 2 | 9 | 32 |
+----+-------+-------+
The above table is ok but in some cases, a user might want to become friends with a user they are already friends with, resulting in the following table:
+----+-------+-------+
| id | user1 | user2 |
+----+-------+-------+
| 1 | 15 | 9 |
+----+-------+-------+
| 2 | 9 | 32 |
+----+-------+-------+
| 3 | 9 | 15 |
+----+-------+-------+
In this table, index 1 and 3 are in conflict with each other (2 & 3 are NOT) and I would therefore like an error returned upon insertion (duplicate entry). Is there a way to do this?
When inserting into this table, before you issue the query, you should always make sure that user1 has the smaller of the two user IDs. Then the situation you described will never happen.
You can implement this either in your application code, or as a stored procedure that you execute to insert a new relation between two users.
Let me offer another perspective. You might want to keep the friends table as a reciprocal relationship. So, both directions would be stored in the table. The correct table would look like:
----+-------+-------+
| id | user1 | user2 |
+----+-------+-------+
| 1 | 15 | 9 |
+----+-------+-------+
| 2 | 9 | 15 |
+----+-------+-------+
| 3 | 9 | 32 |
+----+-------+-------+
| 4 | 32 | 9 |
+----+-------+-------+
Why would you want to do just an absurd thing, doubling the size of the data? Typical queries on such data are about who is a friend of friend or to list all the friends of a given user. Such queries require traversing this data as a graph structure, and you need both links. Not only do such queries become much more complicated with a single row per friendship, but because subqueries (derived tables) are often involved, the query loses the ability to use indexes.
With this structure, you need to be careful when inserting to insert both directions of the relationship. Then a simple unique constraint on the two columns ensures that duplicates are not inserted.
You can create a trigger to automatically fix this, similar to Dmytro's answer:
CREATE TRIGGER trgr_uid_check BEFORE INSERT ON Relationships
FOR EACH ROW
BEGIN
IF NEW.user1 > NEW.user2 THEN
SET #user1 = NEW.user1;
SET NEW.user1 = NEW.user2;
SET NEW.user2 = #user1;
END IF;
END
You could do a simple query to check if there is already a friendship:
SELECT id
FROM your_table
WHERE (user1 = numToInsert1 AND user2 = numToInsert2)
OR (user1 = numToInsert2 AND user2 = numToInsert1)
If this statement returns anything it means that there is already a friendship between those two. If this statement does not return anything insert your new friendship.

MySQL auto increment for a row's value

I have rows like this for users table:
user_id | username | user_game_count | user_password | ...
2 | testUser | 19587 | testPW | ...
I'm increasing user_game_count with UPDATE method:
UPDATE users SET user_game_count = (#cur_value := user_game_count) + 1 WHERE user_id ..
Are there any different way? Because when I use this method in a real-time game, very fast, it's giving results like this:
1
2
3
3 (skipping)
3 (skipping too)
3
3
8
...
(I'm very new to MySQL)

mysql user row level access

Have a database with the following
id | userid | name
1 | 1 | John
2 | 1 | John
3 | 2 | Joe
4 | 2 | Joe
5 | 2 | Joe
6 | 3 | Sue
7 | 3 | Sue
I need to get a way that I can create a database, then create users. Each user that I create in mysql limit them to access of data for their userid. Every database table in the database has the userid value.
So whether they are reading ,updating, insert or delete. If it is going through a specific mysql user that I attached to that database, I want that user to only read, update, insert or delete where their userid is.
I have read some things on mysql triggers but have not found anything that will work for me.
We have a backend that has data in it and restricted by userid.
The website pulls data from that table based in userid so select * from articles where userid=1. Right now, that code is modifiable by the user. I would like a way to go select * from articles and mysql only results rows that have userid=1 for that mysql user. The goal would be for every user to have their own mysql user login to the mysql database that would restrict to that specific value of userid that is theirs.
Any thoughts? Thanks so much!
GoogleResult[0] has this:
http://www.sqlmaestro.com/resources/all/row_level_security_mysql/
Abstract
The article contains a step-by-step guide to implementation of row level security in MySQL 5.0 and higher using such MySQL features as views and triggers.
Well! i will suggest to make a table for that. For the whole application
user_rights
id | user_id | insert | update | delete | read
1 | 2 | 0 | 0 | 0 | 1
Note : 1 for allowed and 0 for disallowed.
Now before you do anything first check the rights then perform other actions.
Detailed method including parts of application :
screens
id | title
1 | articles
2 | blog
user_rights
id | user_id | insert | update | delete | read | screen_id
1 | 2 | 1 | 0 | 0 | 1 | 1
2 | 2 | 0 | 0 | 0 | 1 | 2
In this method you can allow screen level access. User id 2 can add and view articles and he can aslo view blog but.
I may be using inappropriate terms here
but i hope you get the idea.

Mysql - deleting duplicates

I have a table with a barcode column with a unique index. The data has been loaded with additional chars (-xx) at the end of each barcode to prevent duplicates, but there will be lots of duplicates once I strip off the suffix. Here is a sample of the data:
itemnumber barcode
17912 2-14
18082 2-1
21870 2-10
29219 2-8
Then I created two temporary tables, marty and manny, both with the itemnumber and the stripped down barcodes. So,both tables would contain
itemnumber barcode
17912 2
18082 2
21870 2
29219 2
etc
And the I tried to delete all but the first entry with barcode '2' in the marty table(and every other barcode). I hoped then to update the original table with the correct first entry and the users could fix up the duplicates themselves in time in the application.
So, this was my query to delete all but the first entry in the marty table for each barcode
DELETE FROM marty
WHERE itemnumber NOT IN
(SELECT MIN(itemnumber) FROM manny GROUP BY barcode)
There are 130,000 rows in marty and manny. The query took over 24 hours and then didn't finish properly. The connection to the server crashed and the query did not do all the updates.
Is there a better way to approach this that would not us the subquery, which i think is causing the delay? And the group by is probably slowing things down too with so many records.
Thanks
One more variant: this variant works without any temporary tables for deleting duplicates:
Delete m1
From Marty m1
join Marty m2
on m1.barcode = m2.barcode
and m1.itemnumber > m2.itemnumber
Here is a two-stage approach that avoids use of NOT IN. It also does not use the temporary table "manny". First, join "marty" to itself to pick out rows for which itemnumber != min(itemnumber). Use UPDATE to set barcode for these rows to NULL. A second pass with DELETE then removes all rows that were flagged in the first phase.
For this example, I split the barcode column of "marty" into two columns; it could be done with the table in its original format with some modification (need to split the column values on the fly).
select * from marty;
+------------+---------+---------+
| itemnumber | barcode | subcode |
+------------+---------+---------+
| 17912 | 2 | 14 |
| 18082 | 2 | 1 |
| 21870 | 2 | 10 |
| 29219 | 2 | 8 |
| 30133 | 3 | 5 |
| 30134 | 3 | 7 |
| 30139 | 3 | 9 |
| 30142 | 3 | 12 |
+------------+---------+---------+
8 rows in set (0.00 sec)
UPDATE
(marty m1
JOIN
(SELECT barcode,
MIN(itemnumber) AS itemnumber
FROM marty
GROUP BY barcode) m2
USING(barcode))
SET m1.barcode = NULL WHERE m1.itemnumber != m2.itemnumber;
mysql> select * from marty;
+------------+---------+---------+
| itemnumber | barcode | subcode |
+------------+---------+---------+
| 17912 | 2 | 14 |
| 18082 | NULL | 1 |
| 21870 | NULL | 10 |
| 29219 | NULL | 8 |
| 30133 | 3 | 5 |
| 30134 | NULL | 7 |
| 30139 | NULL | 9 |
| 30142 | NULL | 12 |
+------------+---------+---------+
8 rows in set (0.00 sec)
DELETE FROM marty WHERE barcode IS NULL;
MySQL is notoriously slow when using IN with very large sets. A scripted alternative:
Use a script to construct a long itemnumber = X OR itemnumber = y OR itemnumber = z clause (chunks size ~1000) and INSERT the matched rows (i.e. the ones that would not have been DELETEd in your previous query) into a new table, TRUNCATE the existing and load the contents of the new table back into the old with INSERT INTO marty SELECT * FROM marty_tmp.
You may want to lock the table or run in a transaction for the final TRUNCATE, INSERT.
edit:
Query SELECT MIN(itemnumber) FROM manny GROUP BY barcode from a script, store results in desiredItemNumbers array
Take batches of 1000 desiredItemNumbers and construct this query: INSERT INTO manny_tmp SELECT * FROM manny WHERE itemnumber = desiredItemNumbers[0] OR itemnumber = desiredItemNumbers[1] .... Rerun this query until you've exhausted the desiredItemNumbers array (n.b. the last query will probably have less than 1000 desiredItemNumbers).
You now have a table with the results that you would have been left with had you DELETEd the rest, so swap the contents of the marty and marty_tmp tables.
TRUNCATE marty
INSERT INTO marty SELECT * FROM marty_tmp
If you are creating temp tables anyway, how about building your table with an "INSERT INTO " or "CREATE TABLE .. AS ..." based on:
SELECT MIN(itemnumber) AS itemnumber, barcode
FROM marty
GROUP BY barcode