updating column when not present in other table [large data set] - mysql

I have two tables entries and users that has following columns
create table entries
(
id int(11) unsigned auto_increment primary key,
user_id int unsigned null,
status enum ('active', 'inactive', 'blocked')
)
create index user_id on entries (user_id);
------------------------
create table users
(
id int(11) unsigned auto_increment primary key,
email varchar(255) not null,
name varchar(255) not null,
phone varchar(255) not null
)
There are 5 million records in users table and around 20 million records in entries table and a lot of them have dangling user_id values, meaning user_id is pointing to a non-existant value in users table.
I'd like to update those values in entries as efficiently as possible without locking the entire table for an update for many minutes.
I've tried using batch updates by providing different status each time i.e.
UPDATE entries
SET user_id = null
WHERE user_id IS NOT NULL
AND status = 'active'
AND NOT EXISTS(SELECT id
FROM users
WHERE id = entries.user_id);
but had to kill query after a couple of minutes. any suggestions?

You might find that adding an index to the entries table speeds up the update:
CREATE INDEX idx ON entries (user_id, status);

Related

Generate unique key for users with same email-address

We have a table with orders of customers like:
CREATE TABLE `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`number` varchar(20) NOT NULL,
`ordered` datetime DEFAULT NULL,
`email` varchar(255) NOT NULL,
...
);
The table is already filled with data. I need to add a field:
`user` int(10) unsigned NOT NULL,
which contains a unique number for each customer. A customer is defined by the same email-address, so all orders with the email 'test#example.com' should get a 1, with 'something_else#example.com' should get a 2 and so on.
For this 'user'-number it doesn't matter if it starts with 1 or is somehow incrementing, it just should be different for every email-address.
Is there a way to do this in one SQL-statement? I know how to do it with some php-code for example, but we where curious if it's possible just with SQL. We know it would be a better design if there was a table "customer", but it's not our design, we just trying to fix the worst things ;)
It's not possible to do what you describe in one SQL statement.
Even if you didn't care to make the user id unique per email, your ALTER TABLE wouldn't work. You show adding a column that is NOT NULL but has no DEFAULT. So what value is it supposed to add to the table, given that the table has rows in it? It can't use NULL, and it has no DEFAULT value. You can't add the column as an AUTO_INCREMENT because you already have an id column that is AUTO_INCREMENT, and MySQL doesn't allow a table to have two such columns.
Here's the way I'd do it:
CREATE TABLE customers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
INSERT INTO customers (email)
SELECT DISTINCT email FROM orders;
ALTER TABLE orders ADD COLUMN user_id INT UNSIGNED; -- this allows NULLs until we fill it
UPDATE orders JOIN customers USING (email)
SET orders.user_id = customers.id;
Before the next step, make sure that it has populated orders.user_id the way you think is correct. Once you drop the orders.email column, there's no undo. It would be a good idea to make a backup first.
ALTER TABLE orders DROP COLUMN email,
MODIFY COLUMN user_ID INT UNSIGNED NOT NULL; -- disallow NULLs from now on

USING STORED PROCEDURE to SUM, GROUP BY and insert to another table with

I have created 3 tables: item, shop and stock. Plus a stored procedure called inserting
which inserts to the shop table with a given item from the item table
CREATE TABLE item(
i_id int(11) auto_increment,
i_name varchar(255) not null,
primary key(i_id));
CREATE TABLE shop(
s_id int(11) auto_increment,
s_name varchar(255) not null,
s_item int(11) not null,
s_qty int(11) not null,
primary key(s_id),
foreign key(s_item) references item(i_id)
);
CREATE TABLE stock(
item int(11) not null,
total int(11) not null
);
CREATE PROCEDURE inserting (
IN shop_name varchar(225),
IN shop_item int(11),
IN shop_qty int(11)
)
BEGIN
INSERT INTO shop(s_name, s_item, s_qty)
VALUES
(shop_name, shop_item, shop_qty);
INSERT INTO STOCK(item, total)
SELECT s_item, SUM(s_qty) FROM shop GROUP BY s_item
ON DUPLICATE KEY UPDATE
item = VALUES(item),
total = VALUES(total);
The first insert works, but on the second insert when it populates the stock table it gives me extra columns, which i'm not expecting.
I have tried using REPLACE INTO and ON DUPLICATE KEY UPDATE to get single results, still the results comes as the following:
SELECT * FROM `stock`;
+------+-------+
| ITEM | TOTAL |
+------+-------+
| 1 | 5 |
| 1 | 9 |
+------+-------+
what I am trying to achieve is, group the ITEM column, and sum up the TOTAL to a single row.
what am I doing wrong here, or missing from the query?
thanks.
For the on duplicate key syntax to work as expected, you need a unique or primary key constraint on the target table, so the database can identify the "duplicate" rows. Same goes for the REPLACE syntax.
But your stock table does not have a primary key. Consider the following DDL instead:
CREATE TABLE stock(
item int(11) primary key,
total int(11) not null
);
Side note: there is no need to reassign column item in the on duplicate key clause, since it's what is used to identify the conflict in the first place. This is good enough:
INSERT INTO STOCK(item, total)
SELECT s_item, SUM(s_qty) FROM shop GROUP BY s_item
ON DUPLICATE KEY UPDATE total = VALUES(total);
If you run this one time, it should work as you expected. But subsequent runs may bring duplicate ITEM because of what #gmb said. The table must have a UNIQUE index or PRIMARY KEY. See more details here
https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

modeling issue with field with 4 values (1 or more)

lets say I have an account object in my application, which currently represented as:
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (accountId)
);
Now, Account object need to also have Solution field...and Status have 4 different possible values:
Solution1, Solution2, Solution3, Solution4
What would be the right way to represent it in the database?
Account can have few statuses, and status can have few accounts...
So at first I thought create in the db table of Solutions and than have another table to hold the relationship, but its seems too complicated for a field that have only 4 possible values...
Create a junction table to represent the relationships between accounts and solutions:
CREATE TABLE account_solution (
accountId int NOT NULL,
solutionId int NOT NULL
PRIMARY KEY (accountId, solutionId)
)
For your solution table, since there are only 4 values, you might be able to take advantage of MySQL's enum type, e.g.
CREATE TABLE solution
solutionId int NOT NULL PRIMARY KEY,
status ENUM('Solution1', 'Solution2', 'Solution3', 'Solution4')
);
You can use set Mysql SET type
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
status set('Solution1','Solution2','Solution3','Solution4') NOT NULL,
PRIMARY KEY (accountId)
);
And if you want to select a specific status
SELECT *
FROM `Account`
WHERE FIND_IN_SET( 'Solution2', `status` ) >0

How do I get a row of the same type from one table or another table along with the information about from which table it was

Let's say I have tables:
create table people (
human_id bigint auto_increment primary key,
birthday datetime );
create table students (
id bigint auto_increment primary key,
human_id bigint unique key not null,
group_id bigint not null );
create table teachers (
id bigint auto_increment primary key,
human_id bigint unique key not null,
academic_degree varchar(20) );
create table library_access (
access_id bigint auto_increment primary key,
human_id bigint not null,
accessed_on datetime );
Now I want to display information about a library access, along with the information whether it was a student or a teacher (and then the id corresponding to the table) (let's say I want something like SELECT access_id,id,true_if_student_false_if_teacher FROM library_access), in an idiomatic way.
How do I form the query (in case such database was already deployed) and what are better and more idiomatic ways to solve that problem (in case it wasn't deployed so far).
MariaDB 5.5, database accessed by Go and nothing else.
Thanks in advance.
You said you need to know which table the data comes from. You can use union all for this:
select la.access_id, s.id, 'Students' as source_table
from library_access la
join students s on la.human_id = s.human_id
union all
select la.access_id, t.id, 'Teachers' as source_table
from library_access la
join teachers t on la.human_id = t.human_id
Without looking at your tables or any idea as to what you want returned in the select statement:
SELECT *
FROM people a,
students b,
teachers c,
library_access d
WHERE a.human_id = b.human_id
AND a.human_id = c.human_id
AND a.human_id = d.human_id

MySQL table linking 1:n

So first up I'm not sure if this is a double post or not because I don't know how the exact approach or feature is called and if it even exist.
I know that MySQL has a feature called joins
My plan is to link two MySQL tables in relation 1:n one is t_user the other one t_event.
t_user:
CREATE TABLE t_user (
uId INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(25) NOT NULL,
...
)
t_event:
CREATE TABLE t_event (
eId INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40) NOT NULL,
date DATETIME NOT NULL,
members ???,
...
)
I want the users to "subscribe" to the events and get stored in the members column as a list (?). This would be no problem if only one user would subscribe to one event. But I have no idea how to setup the t_event table to store more than one user and how to query for all the events a user has "subscribed" for.
This is usually done via third table:
CREATE TABLE t_eventsusers (
eId INT(6),
uId INT(6)
)