avoid duplicate in mysql insert - mysql

I have a friends table that has 'id', 'friend (INTEGER)' and 'user (INTEGER)' fields.
A friend relationship exists between user and friend.
i.e.
id user friend
6 22 45
7 45 22
is the same friend relationship and should be considered a duplicate record.
I want to input a whole lot of records at once, so something like:
INSERT INTO friends (user, friend) VALUES(22, 34), (22, 76), (22, 567)...;
In this situation, I can easily use IGNORE to avoid entering a duplicate (22, 34) entry(if (22, 34) already exists), but is there a way I can also avoid entering (22, 34) if (34, 22) already exists, as this is the same relationship.

Sort each pair; then do INSERT IGNORE to avoid error messages.
You can sort as you insert by doing
INSERT IGNORE INTO tbl (a,b) VALUES (LEAST($a, $b), GREATEST($a, $b));
However, in order to do a batch insert, you should probably sort in the client language.
Another issue: INSERT IGNORE will create an id before it checks for dup. Therefore, lots of AUTO_INCREMENT values will be 'burned'.
Rather than explaining how to avoid the burning, I will say that there seems to be no good reason for id. Instead have
PRIMARY KEY(user_id, friend_id)
Another issue with the sorting... SELECT may need to do a UNION:
( SELECT ... WHERE user_id = $x )
UNION ALL
( SELECT ... WHERE friend_id = $x )
That implies that you need this, too:
INDEX(friend_id, user_id)

Related

MySQL Stop Insert based on field value

I want to do an insert ignore if a column has a specific value.
Say I have a simple table that records changes to a PO over time. Each time the PO is updated, I need to insert a record on this table:
PONum | POLine | submittedBy | ... other columns
100 1 initial-value
100 1 TB
100 2 initial-value
On a PHP page I check for the existence of the PO number in this table. If there isn't one, I insert all the PO lines I find from another table. I set the submittedBy name to "initial-value".
Now later on, say PO Line 3 is added. I want to run through my check again and only insert the new record for line 3 if there isn't already one that has line 3 with a submittedBy of initial-value
INSERT IGNORE INTO PODetailCL (PONum, POLine, submittedBy, ... )
VALUES (100, 1, 'TB', ... ),
(100, 2, 'TB', ...),
(100, 3, 'initial-value', ...)
WHERE submittedBy <> 'initial-value' -- <- something like this but not sure of the syntax
Try using conditional insert multiple rows:
INSERT INTO PODetailCL(PONum, POLine, submittedBy, ... )
SELECT (100, 1, 'TB', ... ), (100, 2, 'TB', ...),(100, 3, 'initial-value', ...)
FROM PODetailCL
WHERE EXISTS (SELECT * FROM PODetailCL WHERE submittedBy <> 'initial-value')
I was able to use a virtual column and then add a unique constraint on that column. After that I can use INSERT IGNORE. (Virtual columns are only available in MySQL version 5.7 or later.)
ALTER TABLE porelcl
ADD COLUMN initialCheck BIT GENERATED ALWAYS
AS (CASE WHEN submittedBy = 'initial-value' THEN b'1' ELSE NULL END)
then after that:
ALTER TABLE porelcl ADD UNIQUE initial_check_index (PONum, POLine, PORelNum, initialCheck)
Now when my code runs through the check again, if tries to insert a duplicate record: SomePO, SomePOLine, SomePORelNum & 'initial-value' the insert won't take place, which is what I need.

SQL `update` if combination of keys exists in row - else create new row

First of, I've searched this topic here and elsewhere online, and found numorous articles and answers, but none of which did this...
I have a table with ratings, and you should be able to update your rating, but not create a new row.
My table contains: productId, rating, userId
If a row with productId and userId exists, then update rating. Else create new row.
How do I do this?
First add a UNIQUE constraint:
ALTER TABLE tableX
ADD CONSTRAINT productId_userId_UQ
UNIQUE (productId, userId) ;
Then you can use the INSERT ... ON DUPLICATE KEY UPDATE construction:
INSERT INTO tableX
(productId, userId, rating)
VALUES
(101, 42, 5),
(102, 42, 6),
(103, 42, 0)
ON DUPLICATE KEY UPDATE
rating = VALUES(rating) ;
See the SQL-Fiddle
You are missing something or need to provide more information. Your program has to perform a SQL query (a SELECT statement) to find out if the table contains a row with a given productId and userId, then perform a UPDATE statement to update the rating, otherwise perform a INSERT to insert the new row. These are separate steps unless you group them into a stored procedure.
Use a REPLACE INTO query.
REPLACE INTO table (productId, userId, rating) VALUES ('product id', 'user id', 'rating');
REPLACE INTO is like a normal insert, except if it finds a row already in the table with the same unique key it will delete that row before inserting.

SQL Insert into table only if record doesn't exist [duplicate]

This question already has answers here:
Check if a row exists, otherwise insert
(12 answers)
MySQL Conditional Insert
(13 answers)
Closed 9 years ago.
I want to run a set of queries to insert some data into an SQL table but only if the record satisfying certain criteria are met. The table has 4 fields: id (primary), fund_id, date and price
I have 3 fields in the query: fund_id, date and price.
So my query would go something like this:
INSERT INTO funds (fund_id, date, price)
VALUES (23, '2013-02-12', 22.43)
WHERE NOT EXISTS (
SELECT *
FROM funds
WHERE fund_id = 23
AND date = '2013-02-12'
);
So I only want to insert the data if a record matching the fund_id and date does not already exist. If the above is correct it strikes me as quite an inefficient way of achieving this as an additional select statement must be run each time.
Is there a better way of achieving the above?
Edit: For clarification neither fund_id nor date are unique fields; records sharing the same fund_id or date will exist but no record should have both the same fund_id and date as another.
This might be a simple solution to achieve this:
INSERT INTO funds (ID, date, price)
SELECT 23, DATE('2013-02-12'), 22.5
FROM dual
WHERE NOT EXISTS (SELECT 1
FROM funds
WHERE ID = 23
AND date = DATE('2013-02-12'));
p.s. alternatively (if ID a primary key):
INSERT INTO funds (ID, date, price)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE ID = 23; -- or whatever you need
see this Fiddle.
Although the answer I originally marked as chosen is correct and achieves what I asked there is a better way of doing this (which others acknowledged but didn't go into). A composite unique index should be created on the table consisting of fund_id and date.
ALTER TABLE funds ADD UNIQUE KEY `fund_date` (`fund_id`, `date`);
Then when inserting a record add the condition when a conflict is encountered:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = `price`; --this keeps the price what it was (no change to the table) or:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = 22.5; --this updates the price to the new value
This will provide much better performance to a sub-query and the structure of the table is superior. It comes with the caveat that you can't have NULL values in your unique key columns as they are still treated as values by MySQL.
Assuming you cannot modify DDL (to create a unique constraint) or are limited to only being able to write DML then check for a null on filtered result of your values against the whole table
FIDDLE
insert into funds (ID, date, price)
select
T.*
from
(select 23 ID, '2013-02-12' date, 22.43 price) T
left join
funds on funds.ID = T.ID and funds.date = T.date
where
funds.ID is null

MySQL string search between commas

My db is built as follows:
value1,value2,value3 | 1
value4,value5,val"u6 | 2
value 5, value 6, value 8 |3
(Two columns, one with a key separated by commas and the other just a normal var-char)
I'm looking for the most reliable way to find a query within the quotes and I'm getting kinda lost here.
I'm using the word boundaries for that:
SELECT * FROM ABC WHERE content REGEXP '[[:<:]]value 5[[:>:]]'
The problem is when I'm doing this query:
SELECT * FROM ABC WHERE content REGEXP '[[:<:]]5[[:>:]]'
It will also return the value, which is not what I'm looking for. Another problem is that the word boundaries refer to quotes as a word boundary
How can I solve this and create a simple query that will only fetch the exact full query between the quotes?
BTW
I don't have an option to change the DB structure...
As #MarcB commented, you really should try to normalise your schema:
CREATE TABLE ABC_values (
id INT,
content VARCHAR(10),
FOREIGN KEY (id) REFERENCES ABC (id)
);
INSERT INTO ABC_values
(id, content)
VALUES
(1, 'value1'), (1, 'value2'), (1, 'value3'),
(2, 'value4'), (2, 'value5'), (2, 'val"u6'),
(3, 'value 5'), (3, 'value 6'), (3, 'value 8')
;
ALTER TABLE ABC DROP content;
Then, as required, you can perform a SQL join between your tables and group the results:
SELECT id, GROUP_CONCAT(ABC_values.content) AS content
FROM ABC LEFT JOIN ABC_values USING (id) NATURAL JOIN (
SELECT id FROM ABC_values WHERE content = 'value 5'
) t
GROUP BY id
If it is completely impossible to change the schema, you can try FIND_IN_SET():
SELECT * FROM ABC WHERE FIND_IN_SET('value 5', content)
Another workaround is to use LIKE with the delimiters of the items in your list:
WHERE content LIKE ',5,'
But the item you're looking for may be at the start or end of the list. So you have to modify the list on the fly to include the delimiters at the start and end.
WHERE CONCAT(',', content, ',') LIKE '%,5,%' -> this works for me on mysql
This works, and in some sense it's no worse than any other search that you do for an item in a comma-separated list. That's because such a search is bound to do a table-scan and therefore it's very inefficient. As the data in your table grows, you'll find it can't perform well enough to be useful.
See also my answer to Is storing a delimited list in a database column really that bad?

Copy multiple records using foreign keys

Suppose I have two tabels, A and B, each with three columns (A.id, A.title, A.text and B.id, B.a_id, B.text). B.a_id is a foreign key to relates to A.id. Now, suppose there is one record in A (1, 'foo', 'bar') and 2 records in B (1, 1, 'test') and (2, 1, 'test1').
My question is, is there a standard method of copying the record in A, and, at the same time copying all the records from B that relate to A. So suppose I create a new record in A (2, 'foo', 'bar') that's based on (1, 'foo', 'bar'), is there some sort of method that creates two new records in B (3, 2, 'test') and (4, 2, 'test1)?
I've never used triggers before, is this the correct moment to start doing that? Or is this a very stupid question?
this is not a stupid question. However, I believe that this is not possible with pure SQL, or only with some exotic syntax that I am not aware of. Copying rows is not the problem (assuming that id is auto_increment):
insert into A (title, text) select title, text from A where id = XY
However, then you need to find the last insert ID to duplicate the records in B. Let's see:
insert into B (a_id, text) select LAST_INSERT_ID(), text from B where a_id = XY
Hm... maybe this works, but I am a bit sceptical about the LAST_INSERT_ID(). Anyway, I don't think it can be done with just one statement.
Let me know how it goes
Tom