Cancel INSERT if value in referenced table is null or zero - mysql

I have a couple of tables that are related to each other. engine: myisam
1:
account
account_ID, account_username, account_pwd, ......
2:
items
items_account_ID, items_name, ......
As you can see, items have a column items_account_ID. Every item belongs to an
account. To insert an item I use a query like:
INSERT INTO items SET items_name = 'asd', items_account_ID = (SELECT account_ID FROM account WHERE account_ID = accountValue AND account_pwd='something');
This query is created in asp.net and accountValue is from SESSION or hidden field of something. the SELECT statement is for security reasons so that users can't create items for each other.
I would like to cancel the INSERT if the account_ID doesn't exist. Something like:
...." items_account_ID = IFNULL(theSelectStatement, "cancelQuery()");
But I can't find any function in mysql to cancel the current query.
Is this possible?

First of all, you ought to define items_account_ID as not null and make sure it references account(account_ID) as a foreign key. Doing so will prevent you from inserting a bad account_ID into items.
Then you might consider rewriting your insert as follows:
insert items
(
items_name
,items_account_ID
)
select
'asd'
,account_ID
from account
where account_ID = accountValue
and account_pwd = 'something'

Related

Is there a way to get the number of comments for each user and update it in the number_of_comments column automatically?

I have two tables in MySQL like this
Users -> user_id , user_name , number_of_comments
Comments -> comment_id , comment , user_id
Is there a way to get the number of comments for each user and update it in the number_of_comments column automatically?
Not recommended, but solves nevertheless. For learning purposes only.
CREATE TRIGGER tr_ai_update_n_of_comments
AFTER INSERT ON comments
FOR EACH ROW
UPDATE users
SET number_of_comments = ( SELECT COUNT(*)
FROM comments
WHERE comments.user_id = NEW.user_id )
WHERE user_id = NEW.user_id;
If the rows in comments may be updated (with user_id value changing) and/or deleted then create similar AFTER DELETE and AFTER UPDATE triggers.
PS. I strongly recommend you to remove users.number_of_comments column at all and calculate actual comments amount value by according query when needed.
If you agree that the value may be approximate (slightly different from the exact one), then you can use an incremental trigger.
CREATE TRIGGER tr_ai_update_n_of_comments
AFTER INSERT ON comments
FOR EACH ROW
UPDATE users
SET number_of_comments = number_of_comments + 1
WHERE user_id = NEW.user_id;
But just in case, provide for the creation of a service stored procedure (or event) that will periodically recalculate the accumulated value.

Delete Duplicates from large mysql Address DB

I know, deleting duplicates from mysql is often discussed here. But none of the solution work fine within my case.
So, I have a DB with Address Data nearly like this:
ID; Anrede; Vorname; Nachname; Strasse; Hausnummer; PLZ; Ort; Nummer_Art; Vorwahl; Rufnummer
ID is primary Key and unique.
And i have entrys for example like this:
1;Herr;Michael;Müller;Testweg;1;55555;Testhausen;Mobile;012345;67890
2;Herr;Michael;Müller;Testweg;1;55555;Testhausen;Fixed;045678;877656
The different PhoneNumber are not the problem, because they are not relevant for me. So i just want to delete the duplicates in Lastname, Street and Zipcode. In that case ID 1 or ID 2. Which one of both doesn't matter.
I tried it actually like this with delete:
DELETE db
FROM Import_Daten db,
Import_Daten dbl
WHERE db.id > dbl.id AND
db.Lastname = dbl.Lastname AND
db.Strasse = dbl.Strasse AND
db.PLZ = dbl.PLZ;
And insert into a copy table:
INSERT INTO Import_Daten_1
SELECT MIN(db.id),
db.Anrede,
db.Firstname,
db.Lastname,
db.Branche,
db.Strasse,
db.Hausnummer,
db.Ortsteil,
db.Land,
db.PLZ,
db.Ort,
db.Kontaktart,
db.Vorwahl,
db.Durchwahl
FROM Import_Daten db,
Import_Daten dbl
WHERE db.lastname = dbl.lastname AND
db.Strasse = dbl.Strasse And
db.PLZ = dbl.PLZ;
The complete table contains over 10Mio rows. The size is actually my problem. The mysql runs on a MAMP Server on a Macbook with 1,5GHZ and 4GB RAM. So not really fast. SQL Statements run in a phpmyadmin. Actually i have no other system possibilities.
You can write a stored procedure that will each time select a different chunk of data (for example by rownumber between two values) and delete only from that range. This way you will slowly bit by bit delete your duplicates
A more effective two table solution can look like following.
We can store only the data we really need to delete and only the fields that contain duplicate information.
Let's assume we are looking for duplicate data in Lastname , Branche, Haushummer fields.
Create table to hold the duplicate data
DROP TABLE data_to_delete;
Populate the table with data we need to delete ( I assume all fields have VARCHAR(255) type )
CREATE TABLE data_to_delete (
id BIGINT COMMENT 'this field will contain ID of row that we will not delete',
cnt INT,
Lastname VARCHAR(255),
Branche VARCHAR(255),
Hausnummer VARCHAR(255)
) AS SELECT
min(t1.id) AS id,
count(*) AS cnt,
t1.Lastname,
t1.Branche,
t1.Hausnummer
FROM Import_Daten AS t1
GROUP BY t1.Lastname, t1.Branche, t1.Hausnummer
HAVING count(*)>1 ;
Now let's delete duplicate data and leave only one record of all duplicate sets
DELETE Import_Daten
FROM Import_Daten LEFT JOIN data_to_delete
ON Import_Daten.Lastname=data_to_delete.Lastname
AND Import_Daten.Branche=data_to_delete.Branche
AND Import_Daten.Hausnummer = data_to_delete.Hausnummer
WHERE Import_Daten.id != data_to_delete.id;
DROP TABLE data_to_delete;
You can add a new column e.g. uq and make it UNIQUE.
ALTER TABLE Import_Daten
ADD COLUMN `uq` BINARY(16) NULL,
ADD UNIQUE INDEX `uq_UNIQUE` (`uq` ASC);
When this is done you can execute an UPDATE query like this
UPDATE IGNORE Import_Daten
SET
uq = UNHEX(
MD5(
CONCAT(
Import_Daten.Lastname,
Import_Daten.Street,
Import_Daten.Zipcode
)
)
)
WHERE
uq IS NULL;
Once all entries are updated and the query is executed again, all duplicates will have the uq field with a value=NULL and can be removed.
The result then is:
0 row(s) affected, 1 warning(s): 1062 Duplicate entry...
For newly added rows always create the uq hash and and consider using this as the primary key once all entries are unique.

How can I update an existing record to have a new auto_increment id in MySQL?

I have a table with primary key (its name is "id") defined as auto_increment. I use NULL in INSERT statements to "fill" the id value. It works, of course. However now I need to "move" an existing record to a new primary key value (the next available, the value is not so much important, but it must be a new one, and the last one if ordered by id). How can I do it in an "elegant" way? Since the "use NULL at INSERT" does not work too much with UPDATE:
update idtest set id=NULL where id=1;
This simply makes the id of the record zero. I would expect to do the same thing as with INSERT, but it seems my idea was incorrect.
Of course I can use "INSERT ... SELECT" statement, then a DELETE on the old one, or I can use something like MAX(id) + 1 to UPDATE the id of the old record in one step, etc, but I am curious if there is a finer solution.
Also, the MAX(id) solution doesn't seem to work either by the way:
mysql> update idtest set id=max(id)+1 where id=3;
ERROR 1111 (HY000): Invalid use of group function
mysql> update idtest set id=(select max(id)+1 from idtest) where id=3;
ERROR 1093 (HY000): You can't specify target table 'idtest' for update in FROM clause
This is the way I believe:
UPDATE users SET id = (SELECT `AUTO_INCREMENT`
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test'
AND TABLE_NAME = 'users') WHERE id = 2;
select * from users;
I used by own tables substitute yours.
test is database name, users is table name and id is AUTO_INCREMENT in my case.
EDIT: My Query above works perfect but its side effects are somewhat 'dangerous', upon next insert as AUTO_INCREMENT value will collide with this recently updated record so just next single insert will fail. To avoid that case I've modified above query to a transaction:
START transaction;
UPDATE users SET id = (SELECT `AUTO_INCREMENT`
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test'
AND TABLE_NAME = 'users') WHERE id = 2;
#renew auto increment to avoid duplicate warning on next insert
INSERT IGNORE INTO users(username) values ('');
COMMIT
Hope this will help someone if not OP.
The way you are trying to update same table is wrong but you can use join on same table
update idtest t
join (select id +1 as id
from idtest order by id desc
limit 1) t1
set t.id=t1.id
where t.id=3;
or
update idtest t
join (select max(id) +1 as id
from idtest ) t1
set t.id=t1.id
where t.id=3;
You can use the REPLACE INTO clause to do the trick.
From the manual:
REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted. See Section 13.2.5, "INSERT Syntax".
EDIT
My mistake (in the comments) that you have to have two unique constraint to achieve this:
When you use the auto_increment value to REPLACE the record, the record will be replaced with the give ID and will not change (however the AI value will increment).
You have to exclude the AI column from the query. You can do that if you have one more UQ constraint.
Check this SQLFiddle demo: http://sqlfiddle.com/#!2/1a702e
The first query will replace all the records (but the id's value will not change).
The second one will replace it too, and the new AI value will be used. (Please note, that the second query does not contain the id column, and there is a UQ constraint on the some column).
You can notice, that the second query uses higher AI values than it is excepted: this is because the first replace incremented the AI value.
If you do not have two unique keys (one for the AI and one for another columns), the REPLACE statement will work as a normal INSERT statement!
(Ofcourse you can change one of the UNIQUE KEYs with a PRIMARY KEY)

InnoDB and count: Are helper tables the way to go?

Assume I've got an users table with 1M users on MySQL/InnoDB:
users
userId (Primary Key, Int)
status (Int)
more data
If I would want to have an exact count of the amount of users with status = 1 (denoting an activate account), what would be the way to go for big tables, I was thinking along the lines of:
usercounts
status
count
And then run an TRIGGER AFTER INSERT on users that updates the appropiate columns in usercounts
Would this be the best way to go?
ps. An extra small question: Since you also need an TRIGGER AFTER UPDATE on users for when status changes, is there a syntax available that:
Covers both the TRIGGER AFTER INSERT and TRIGGER AFTER UPDATE on status?
Increments the count by one if a count already is present, else inserts a new (status, count = 0) pair?
Would this be the best way to go?
Best (opinion-based) or not but it's definitely a possible way to go.
is there a syntax available that: covers both the TRIGGER AFTER INSERT and TRIGGER AFTER UPDATE on status?
No. There isn't a compound trigger syntax in MySQL. You'll have to create separate triggers.
is there a syntax available that: increments the count by one if a count already is present, else inserts a new (status, count = 0) pair?
Yes. You can use ON DUPLICATE KEY clause in INSERT statement. Make sure that status is a PK in usercounts table.
Now if users can be deleted even if only for maintenance purposes you also need to cover it with AFTER DELETE trigger.
That being said your triggers might look something like
CREATE TRIGGER tg_ai_users
AFTER INSERT ON users
FOR EACH ROW
INSERT INTO usercounts (status, cnt)
VALUES (NEW.status, 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
CREATE TRIGGER tg_ad_users
AFTER DELETE ON users
FOR EACH ROW
UPDATE usercounts
SET cnt = cnt - 1
WHERE status = OLD.status;
DELIMITER $$
CREATE TRIGGER tg_au_users
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
IF NOT NEW.status <=> OLD.status THEN -- proceed ONLY if status has been changed
UPDATE usercounts
SET cnt = cnt - 1
WHERE status = OLD.status;
INSERT INTO usercounts (status, cnt) VALUES (NEW.status, 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
END IF;
END$$
DELIMITER ;
To initially populate usercounts table use
INSERT INTO usercounts (status, cnt)
SELECT status, COUNT(*)
FROM users
GROUP BY status
Here is SQLFiddle demo
I think there are simpler options available to you.
Just add an index to the field you'd like to count on.
ALTER TABLE users ADD KEY (status);
Now a select should be very fast.
SELECT COUNT(*) FROM users WHERE status = 1

trouble copying data from one table to another with auto increment field

I have the following SQL statement which was working perfectly until I moved it to another server. The middle query (encapsulated in ** ) does not seem to work. I am getting an error saying that 'AUTO' is an incorrect integer type. If I remove it altogether, it says that I have an incorrect number of fields. I am trying to copy data from one table to another and allow the destination table to auto increment its ID number.
SET sql_safe_updates=0;
START TRANSACTION;
DELETE FROM shares
WHERE asset_id = '$asset_ID';
/*************************************************************/
INSERT INTO shares
SELECT 'AUTO', asset_ID, member_ID, percent_owner, is_approved
FROM pending_share_changes
WHERE asset_ID = '$asset_ID';
/*************************************************************/
DELETE FROM pending_share_changes
WHERE asset_ID = '$asset_ID';
DELETE FROM shares
WHERE asset_ID = '$asset_ID' AND percent_owner = '0';
COMMIT;";
Based on this page of the mysql docs, you have to do:
INSERT INTO shares
(column_name1, column_name2, column_name3, column_name4) -- changed!
SELECT asset_ID, member_ID, percent_owner, is_approved
FROM pending_share_changes
WHERE asset_ID = '$asset_ID';
The difference is that the column names of the "receiving" table are explicitly listed after the name of the receiving table.
The docs say
AUTO_INCREMENT columns work as usual.