MySQL Conditional Insert. Works in phpMyAdmin but not PHP Script - mysql

I am trying to create a conditional INSERT into my MySQL databate from a PHP script. The following SQL syntax works in phpMyAdmin, but not in my PHP Script:
INSERT INTO profiles (id, firstname)
SELECT "22","John" from profiles
WHERE NOT EXISTS (
SELECT * FROM li_profiles
WHERE li_p_firstname = "John"
)
(Note that "id" is the primary key, "firstname" is not a key or unique)
Something weird that might be part of the issue is that when I run that SQL in phpMyAdmin, while it does "work" (meaning that a new record is added with the id "22" and the firstname "John") I get the following warning: "#1062 - Duplicate entry '22' for key 1"
But the table didn't have a previous entry with id of 22. ??!!
What's going on?

Change SELECT to VALUES
INSERT INTO profiles (id, firstname) VALUES("22","John") FROM profiles WHERE NOT EXISTS ( SELECT * FROM li_profiles WHERE li_p_firstname = "John" )
Also, if you are using auto-increment values, you should specify the next value. Also, if its an integer, give an integer (22) not a string ("22")

You'll get a duplicate entry for the iD because you are inserting a new row for each row in the profiles table; for every row in the profiles table there is no John in the li_profiles table. You might try
INSERT INTO profiles (id, firstname)
SELECT "22","John" from profiles
WHERE NOT EXISTS (SELECT * FROM li_profiles
WHERE li_p_firstname = "John")
LIMIT 1;
which would eliminate the duplicate problem (if it works, sorry but I haven't checked this myself).

I figured it out in a different way. (I'm told that the HAVING statement is slow, so I'm not sure that it's the best way... but it the only method I've gotten to work.)
INSERT INTO profiles (id,firstname)
SELECT 22,'John'
FROM li_profiles
WHERE firstname = 'John'
HAVING COUNT(*) = 0;

Related

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.

Combine two queries to check for duplicates in MySQL?

I have a table that looks like this:
Number | Name
--------+--------
123 | Robert
This is what I want to do:
If the Number is already in the database, don't insert a new record.
If the Number is not in the databse, but the name is, create a new name and insert it. So for example, if I have a record that contains 123 for Number and Bob for Name, I don't want to insert it, but if I get a record that contains 456 for Number and Robert for name, I would insert 456 and Robert1. I was going to check for duplicates individually like:
SELECT * FROM Person where Number = 123;
//If number is not found
SELECT * FROM Person where Name = 'Robert';
//If name is found, add a number to it.
Is there a way I can combine the two statements?
There are actually two problems in your question. The first problem is to make Number column unique and the second one is to increment the column Name by appending a number if it already exists.
FIRST PART
Since the number is UNIQUE, enforce a UNIQUE constraint on the column. It could be a PRIMARY KEY or a UNIQUE KEY.
If the column has no KEY and you want to make it PRIMARY, here is the ALTER statement:
ALTER TABLE TableName ADD CONSTRAINT tb_pk PRIMARY KEY (Number)
SQLFiddle Demo
but if you only want it to be UNIQUE and not a primary key,
ALTER TABLE TableName ADD CONSTRAINT tb_uq UNIQUE (Number)
SQLFiddle Demo
SECOND PART
You can actually do it without using join.
INSERT INTO TableName(Number, Name)
SELECT 124 AS Number,
CONCAT('Robert', COALESCE(MAX(CAST(REPLACE(Name, 'Robert', '0') AS UNSIGNED)) + 1,'')) AS Name
FROM TableName
WHERE Name LIKE 'Robert%'
SQLFiddle Demo
SQLFiddle Demo (added more example)
SQLFiddle Demo (throws exception due to uniqueness)
Some details:
when the value supplied on column Number already exists, it will throw an error since the column is unique. I have read a comment from a deleted posts saying: "..Number is not unique, but if it does exist, I don't want to enter a record." -- it does not make any sense if you don't want to add uniqueness on the column. How will you know if the number already exists or not? Doing a little check for the existence of Number feels like a little overhead for me. So my best recommendation is to enforce uniqueness.
SELECT * FROM Person WHERE Number = 123 OR Name = 'Robert'
I haven't worked with SQL for some time, so this may be wrong ;)
Edit:
$number = 123;
$name = 'Robert';
$query = mysql_query("SELECT * FROM Person WHERE Number = $number OR Name = '$name' ");
if (mysql_num_rows($query) == 0 ) {
//-> Add your record, it's unused
} else if (mysql_result($query, 0, 'number') == $number && mysql_result($query, 0, 'name' == $name)) {
//combination of number and name already exists -> modify name and add record
} else {
echo "Number is used by another name";
}
Use this query, for insert the row [123, 'Robert']. if you want insert other values, change 123 & Robert values in below query:
insert into Person (Number,Name)
select 123, IF(mn.MaxNumber is NULL,'Robert',concat('Robert',mn.MaxNumber+1))
from (SELECT 'foo') foo
left JOIN (select max(CONVERT(SUBSTR(Name,LENGTH('Robert')+1),UNSIGNED)) `MaxNumber`
from person where name rlike '^Robert[0-9]*$') mn on 1=1
where Not Exists (select * from Person where Number=123)
NOTE: if Robert exists in the table, above query inserts Robert1. if Robert1 exists, it inserts Robert2, and so on .
make both number and name unique.
ALTER TABLE `person` ADD UNIQUE (`number` ,`name`);
You can now do a insert with ON DUPLICATE
INSERT INTO `person` (`number`, `name`, `id`) VALUES ('322', 'robert', 'NULL') ON DUPLICATE KEY UPDATE `id`='NULL';
For appending a number after name i would suggest using autoincrement column instead.
insert into Person (Number,Name)
select 123, IF(mn.MaxNumber is NULL,'Robert',concat('Robert',mn.MaxNumber+1))
from (SELECT 'foo') foo
left JOIN (select max(CONVERT(SUBSTR(Name,LENGTH('Robert')+1),UNSIGNED)) `MaxNumber`
from person where name rlike '^Robert[0-9]*$') mn on true
where Not Exists (select * from Person where Number=123)

Single query for "INSERT INTO table if column value not found"

How can I use a single query for inserting table when a column value is not found.
eg/ i want to insert new user only when this username not found
what i doing now is issue 1 query to check for existing,
then another query if no existing found. total 2 query
INSERT INTO friends (memberID) SELECT 1 WHERE NOT EXISTS (SELECT memberID FROM friends WHERE memberID = 1)
You just need to add FROM DUAL
INSERT INTO friends
(memberid)
SELECT 1
FROM dual
WHERE NOT EXISTS (SELECT memberid
FROM friends
WHERE memberid = 1)
sql fiddle
How about this:
INSERT INTO YourTable (UserName)
SELECT x
FROM (SELECT 'New User Name' AS x) a
WHERE x NOT IN(SELECT UserName
FROM YourTable)
Since you only want one row with a given value you should enforce that with a UNIQUE constraint on the table, for example:
ALTER TABLE friends ADD UNIQUE (memberID);
After you do that, you can simply add the IGNORE keyword to your insert statements and it won't report an error and it won't insert a duplicate row if it already exists.
INSERT IGNORE INTO friends(memberID) VALUES(1);

how to uniquely concatenate a value in mysql for a non key field

I'm using a third party mysql table (ie I can't change any of its properties) and I have a row that has id (key), name and value.
I want to store unique cache keys into a row with the name cacheKeys.. and this is my sql statement
$query = "INSERT INTO ".$tableName." (name, value) VALUES ('CacheKeys', '".$key."') ON
DUPLICATE KEY UPDATE value = CONCAT_WS (',', $tableName.value, '$key')";
I've already implemented my caching algorithm, so that every time someone adds a cache key, I check to see if it already exists (from the CacheKeys row above), if it does I fetch it from cache.. otherwise I store it.
Problem is it seems that the sql write operation takes time, and it often stores duplicate cacheKeys
ie: currencies,defaultCurrencyId,user19,currency1,currency1,currency1,currency1,currency1
So I need to check to see that I'm not adding a duplicate key into the cacheKeys field.. and I need to do that using SQL (using php, ie regex etc would just be waaaay to expensive).
Try this::
INSERT INTO tb (firstname, lastname) VALUES ('Jack', 'Doe') IF NOT
EXISTS ( SELECT * FROM tb WHERE firstname='Jack' AND lastname='Doe' );

MySQL Insert into multiple tables? (Database normalization?) [duplicate]

This question already has answers here:
Insert into multiple tables in one query
(6 answers)
Closed 1 year ago.
I tried searching a way to insert information in multiple tables in the same query, but found out it's impossible?
So I want to insert it by simply using multiple queries i.e;
INSERT INTO users (username, password) VALUES('test', 'test')
INSERT INTO profiles (userid, bio, homepage) VALUES('[id of the user here?]','Hello world!', 'http://www.stackoverflow.com')
But how can I give the auto-increment id from the users to the "manual" userid for the profile table?
No, you can't insert into multiple tables in one MySQL command. You can however use transactions.
BEGIN;
INSERT INTO users (username, password)
VALUES('test', 'test');
INSERT INTO profiles (userid, bio, homepage)
VALUES(LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com');
COMMIT;
Have a look at LAST_INSERT_ID() to reuse autoincrement values.
You said "After all this time trying to figure it out, it still doesn't work. Can't I simply put the just generated ID in a $var and put that $var in all the MySQL commands?"
Let me elaborate: there are 3 possible ways here:
In the code you see above. This
does it all in MySQL, and the
LAST_INSERT_ID() in the second
statement will automatically be the
value of the autoincrement-column
that was inserted in the first
statement.
Unfortunately, when the second statement itself inserts rows in a table with an auto-increment column, the LAST_INSERT_ID() will be updated to that of table 2, and not table 1. If you still need that of table 1 afterwards, we will have to store it in a variable. This leads us to ways 2 and 3:
Will stock the LAST_INSERT_ID() in
a MySQL variable:
INSERT ...
SELECT LAST_INSERT_ID() INTO #mysql_variable_here;
INSERT INTO table2 (#mysql_variable_here, ...);
INSERT INTO table3 (#mysql_variable_here, ...);
Will stock the LAST_INSERT_ID() in a
php variable (or any language that
can connect to a database, of your
choice):
INSERT ...
Use your language to retrieve the LAST_INSERT_ID(), either by executing that literal statement in MySQL, or using for example php's mysql_insert_id() which does that for you
INSERT [use your php variable here]
WARNING
Whatever way of solving this you choose, you must decide what should happen should the execution be interrupted between queries (for example, your database-server crashes). If you can live with "some have finished, others not", don't read on.
If however, you decide "either all queries finish, or none finish - I do not want rows in some tables but no matching rows in others, I always want my database tables to be consistent", you need to wrap all statements in a transaction. That's why I used the BEGIN and COMMIT here.
fairly simple if you use stored procedures:
call insert_user_and_profile('f00','http://www.f00.com');
full script:
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
username varchar(32) unique not null
)
engine=innodb;
drop table if exists user_profile;
create table user_profile
(
profile_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
homepage varchar(255) not null,
key (user_id)
)
engine=innodb;
drop procedure if exists insert_user_and_profile;
delimiter #
create procedure insert_user_and_profile
(
in p_username varchar(32),
in p_homepage varchar(255)
)
begin
declare v_user_id int unsigned default 0;
insert into users (username) values (p_username);
set v_user_id = last_insert_id(); -- save the newly created user_id
insert into user_profile (user_id, homepage) values (v_user_id, p_homepage);
end#
delimiter ;
call insert_user_and_profile('f00','http://www.f00.com');
select * from users;
select * from user_profile;
What would happen, if you want to create many such records ones (to register 10 users, not just one)?
I find the following solution (just 5 queryes):
Step I: Create temporary table to store new data.
CREATE TEMPORARY TABLE tmp (id bigint(20) NOT NULL, ...)...;
Next, fill this table with values.
INSERT INTO tmp (username, password, bio, homepage) VALUES $ALL_VAL
Here, instead of $ALL_VAL you place list of values: ('test1','test1','bio1','home1'),...,('testn','testn','bion','homen')
Step II: Send data to 'user' table.
INSERT IGNORE INTO users (username, password)
SELECT username, password FROM tmp;
Here, "IGNORE" can be used, if you allow some users already to be inside. Optionaly you can use UPDATE similar to step III, before this step, to find whom users are already inside (and mark them in tmp table). Here we suppouse, that username is declared as PRIMARY in users table.
Step III: Apply update to read all users id from users to tmp table. THIS IS ESSENTIAL STEP.
UPDATE tmp JOIN users ON tmp.username=users.username SET tmp.id=users.id
Step IV: Create another table, useing read id for users
INSERT INTO profiles (userid, bio, homepage)
SELECT id, bio, homepage FROM tmp
have a look at mysql_insert_id()
here the documentation: http://in.php.net/manual/en/function.mysql-insert-id.php
try this
$sql= " INSERT INTO users (username, password) VALUES('test', 'test') ";
mysql_query($sql);
$user_id= mysql_insert_id();
if(!empty($user_id) {
$sql=INSERT INTO profiles (userid, bio, homepage) VALUES($user_id,'Hello world!', 'http://www.stackoverflow.com');
/* or
$sql=INSERT INTO profiles (userid, bio, homepage) VALUES(LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com'); */
mysql_query($sql);
};
References
PHP
MYSQL
Just a remark about your saying
Hi, I tried searching a way to insert information in multiple tables in the same query
Do you eat all your lunch dishes mixed with drinks in the same bowl?
I suppose - no.
Same here.
There are things we do separately.
2 insert queries are 2 insert queries. It's all right. Nothing wrong with it. No need to mash it in one.
Same for select. A query must be sensible and do its job. That's the only reasons. Number of queries is not.
There is no point in looking for a way to stuff different queries in one call. Different calls is how the database API is meant to work.
For PDO You may do this
$dbh->beginTransaction();
$stmt1 = "INSERT INTO users (username, password) VALUES('test', 'test')";
$stmt2 = "INSERT INTO profiles (userid, bio, homepage) VALUES('LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com')";
$sth1 = $dbh->prepare($stmt1);
$sth2 = $dbh->prepare($stmt2);
$sth1->execute (array ('test','test'));
$sth2->execute (array ('Hello world!','http://www.stackoverflow.com'));
$dbh->commit();