I'm trying to create a keyword system for my site but I have hit a bump in the road.
The database table has five rows DATE | USERID | AMOUNT | KEYWORD | ID
When inserting a row into the database it needs to check if the keyword already exists for that userid, if so the amount increments, if not it's inserted with a default value of 1.
So far I have:
$stmt= $conn->prepare("INSERT INTO `keywords` (keyword, userId, id) VALUES(?,?,?) ON DUPLICATE KEY UPDATE `amount` = `amount` + 1");
How do I check if there is a duplicate of the userid value and increment or insert the keyword based on the result?
Your query is:
INSERT INTO `keywords`(keyword, userId, id)
VALUES(?,?,?)
ON DUPLICATE KEY UPDATE `amount` = `amount` + 1;
That looks like what you want to do, with possibly one small change:
INSERT INTO `keywords`(keyword, userId, id)
VALUES(?,?,?)
ON DUPLICATE KEY UPDATE `amount` = coalesce(`amount`, 0) + 1;
(This is not needed if amount has a default value other than NULL.)
The other thing you need is a constraint that says that keyword is unique. So, add a unique index:
create unique index idx_keywords_keyword on keywords(keyword)
Related
I have a SQL query as follows-
"INSERT INTO users(id, rank) SELECT v.user, v.vote FROM votes v WHERE
v.assertion = '$ID' ON DUPLICATE KEY UPDATE
rank = ( CASE WHEN v.vote = '1' THEN rank+50 WHEN v.vote = '-1'
THEN rank-200 WHEN v.vote = '3' THEN rank+100 ELSE rank END)"
applied on a database with a table users with and id and rank field, and a votes table with a user and vote field. I have to update the rank of the users in the users table based on their vote.
I really like this kind of query, but I've noticed a problem: every time I execute this from my PHP script the query adds a row to the users table completely empty (with only an ID, which is A_I, and a rank of 1, when usually there would be other field as well). I can't really wrap my head around why this happens.
Any help/idea?
Your table does not have a primary key first provide a primary key to id
run this sql query
alter table user add primary key (id)
and than try it will work
There are two possible reasons :
The id column is not the primary key, and probably you table doesn't have a primary key at all.
Create a primary key like this :
alter table user add primary key (id)
If you insert an value of 0 in an auto increment column, a new id is generated. An auto incremented column must not contain the value 0.
There is also a more general problem with your approach : in fact you only insert the user id and the rank, other compulsory fields in the table (username) are missing. The insert part does not seem to be valid for this reason. If you use an insert on duplicate key update, you must make sure that the result is correct which ever of insert and update is executed.
I have already asked this but did not get what I am looking for, I have many tables with column id set to auto-increment and primary key.
Table1
id, title, condition...
Table2
id, title, condition...
And I have a search box where I search for products and my query is like
$stmt = $mysqli->prepare("select * from table1 where title Like ? or id = ? ");
$stmt->bind_param('ss', $searchterm, $searchterm");
$stmt->execute();
$stmt = $mysqli->prepare("select * from table2 where title Like ? or id = ? ");
$stmt->bind_param('ss', $searchterm, $searchterm");
$stmt->execute();
So if I search for "1" then I will get all the products with id or title 1 but what I want is to have a unique identfier for each row. How can I accomplish this? As I am working with products, there should be a unique id for each product.
Or could it be possible to have a alphabet in front of the id, something like T1, T2, T3?
If you were using the Oracle table server, you'd use a database entity called a SEQUENCE to get a data-base-wide unique id value for your tables.
It's easy enough to emulate SEQUENCE entities in MySQL, though, even if it's a little clunky and not quite as space-efficient.
First, create yourself a table like this.
CREATE TABLE sequence (
sequence_id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`sequence_id`)
)
Then, create your other tables, the ones which will use these unique sequence_id values. Here's an example:
CREATE TABLE gadget (
sequence_id BIGINT NOT NULL,
gname VARCHAR(20) DEFAULT NULL,
gvalue VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (sequence_id)
)
Here's another:
CREATE TABLE widget (
sequence_id BIGINT NOT NULL,
wname VARCHAR(20) DEFAULT NULL,
wvalue VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (sequence_id)
)
Then, whenever you insert a row into your other tables, do it like this:
INSERT INTO sequence () VALUES() ;
INSERT INTO widget (sequence_id, wname, wvalue)
VALUES (LAST_INSERT_ID(), 'whatever', 'you_want');
or, for the other example table,
INSERT INTO sequence () VALUES() ;
INSERT INTO gadget (sequence_id, gname, gvalue)
VALUES (LAST_INSERT_ID(), 'another', 'gadget');
The trick is this: when you insert each (seemingly empty) row into the sequence table it updates the auto-increment sequence_id field. Then, when you use the LAST_INSERT_ID() function it retrieves the most-recently inserted value of that sequence_id.
Be very careful to keep your two INSERT INTO statements consecutive, or this will stop working properly.
This works even if many different clients of the database server are doing the inserts, for two reasons;
auto-increment is thread-safe. No two sequence numbers can be the same.
LAST_INSERT_ID() values are maintained one for each distinct connection to the MySQL database. Different programs (or different threads in a multi-threaded web application) each have their own connections, so they each have their own values of LAST_INSERT_ID().
By the way, I used bigint data for the sequence, but int will work just as well.
I have a count table, and i would like to be able to sum the counts when renaming one of the compound key fields. This is difficult to explain, so let me show you some SQL:
Example table:
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`type` int(11) NOT NULL,
`count` int(11) NOT NULL,
PRIMARY KEY (`id`,`type`)
);
Insert some data:
INSERT INTO test VALUES(1, 10, 1);
INSERT INTO test VALUES(2, 20, 3);
INSERT INTO test VALUES(2, 10, 3);
INSERT INTO test VALUES(2, 30, 3);
Query the data:
mysql> SELECT SUM(count) as count FROM test WHERE id = 2;
+-------+
| count |
+-------+
| 9 |
+-------+
mysql> SELECT SUM(count) as count FROM test WHERE type = 10;
+-------+
| count |
+-------+
| 4 |
+-------+
Works very well, is fast and flexible.
Now, I'd like to remap type 10 to type 20
UPDATE test SET type = 20 WHERE type = 10;
Duplicate entry '2-20' for key 'PRIMARY'
Using ON DUPLICATE KEY UPDATE here is invalid.
I figure it should be possible with a creative insert, but i'm not sure. Can anyone think of an approach ?
One approach is to "loosen" the PRIMARY KEY, meaning, change it from a PRIMARY (unique) KEY to a non-unique KEY. This will allow for duplicate values, and will allow your UPDATE statement to succeed, such that you will have two (or more) rows with matching (id,type).
(Note that this makes updating a single row problematic, so you would likely want to add a new column which can be unique. An AUTO_INCREMENT column would work nicely for that.)
If you don't want to do that, then the other approach would be to "combine" the counts for the type 10 and type 20 rows together (for each id) into a type 20 row, set the count for the type 10 rows to zero, and (optionally) remove the redundant type 10 rows in a separate statement.
The statement below "combines" the type 10 and type 20 counts by "mapping" the 10 value to a 20 in the first select.
-- combine count for types 10 and 20 as new count for type 20
-- and set count to zero for type 10
INSERT INTO test (id, `type`, `count`)
SELECT t.id
, IF(t.`type`=10,20,t.`type`) AS new_type
, SUM(t.`count`) AS `count`
FROM test t
WHERE t.`type` IN (10,20)
GROUP BY id, new_type
UNION ALL
SELECT u.id
, u.`type`
, 0 AS `count`
FROM test u
WHERE u.`type` = 10
ON DUPLICATE KEY UPDATE `count` = VALUES(`count`);
-- remove unnecessary type 10 rows.
DELETE FROM test WHERE `type` = 10 AND `count` = 0;
Note: The IF() expression in the first SELECT is equivalent to:
CASE WHEN t.type = 10 THEN 20 ELSE t.type END
The second select is getting us all the type 10 rows that will need to be updated. We concatenate those two result sets using the UNION ALL operator.
Then we take the combined (concatenated) result set, and run them into an insert. Any place we hit a "duplicate" key, we perform the update action via the ON DUPLICATE KEY clause.
You will likely want to do this in a transaction (if you are using InnoDB) and verify that the first statement completes successfully before executing a DELETE. If the first statement throws an exception (maybe there is a wonky trigger), then you would want to ROLLBACK the transaction. You'd also want to ROLLBACK if the DELETE fails for some reason (perhaps there is a foreign key constraint that would be violated.)
Alternatively, you do not necessarily need to perform the DELETE, you could just leave the type 10 rows with counts of zero.
IMPORTANT:
Do NOT implement BOTH of these solutions! Only ONE of them.
The first approach is a schema change, which doesn't require any data changes. That change will allow your UPDATE to succeed.
The second approach requires that the schema remain the same, and depends on the existence (and enforcement) of the UNIQUE KEY on (id,type).
When you update your query, you're creating multiple rows with id = 2 and type = 20, which is not allowed due to your primary key constraint.
You should instead use a single column as your primary key and have it auto-increment when you insert a new row. This way it's guaranteed to be unique.
You've set primary key on two columns, and after update the third row primary key will be the same as the second, so that is not allowed.
In the end you'll have 1..20; 2..20, 2..20, etc.
I am using a table to store votes of different users on different polls.
Table has following structure.
id | poll_id | opt | ip_address
id : auto increment
poll_id : (STRING UNIQUE) unique for a particular poll
opt : (STRING) option selected by user
ip_address: (STRING UNIQUE) ip address of user
This is my query
INSERT OR REPLACE INTO tbl_poll(id,poll_id,opt,ip_addr) VALUES (null,'$poll_id','$opt','$ip_addr')
(Here $poll_id, $opt and $ip_addr are php variables which holds the respective values)
Now, the scenario is like this,
User 'A' votes for option 2 of poll_id 'mypoll'. Query works perfectly. (Does insert)
User 'A' changes mind and votes for option 5 of poll_id 'mypoll'. Query works perfectly. (Does replace)
But if User 'A' votes for option 4 of poll_id 'yourpoll'. Query fails (It does a replace) but it should insert a new record with poll_id 'yourpoll'
I think, it considers the unqiue constraint of ip_address only but not the poll_id
From your description on the desired functionality, it would seem that you want poll_id and ip_address to be a unique pair or compound unique.
CREATE TABLE tbl_poll (
id INTEGER PRIMARY KEY AUTOINCREMENT,
poll_id STRING NOT NULL,
ip_address STRING NOT NULL,
opt STRING NULL,
CONSTRAINT 'unique_vote_per_poll_per_ip_address' UNIQUE ( poll_id, ip_address ) ON CONFLICT REPLACE
);
No, this is correct behaviour.
You have a UNIQUE constraint on ip_address, therefore you may not have two records with the same ip_address.
Put your UNIQUEconstraint on the pair (poll_id, ip_address) instead.
I have a table in a mySQL database we'll call 'tbl' where the fields are:
id, userID, favorite, emailID
The id is auto incremental. The userID stores an integer. The favorite is either yes or no. The emailID stores an integer.
I am programing in PHP and would like to have one query used to query the database that checks if the userID and emailID combination exists. If it does then update the favorite field with a yes or no value that comes from a form that is passed into the query dynamically. If it does not exist then insert the combination into the database.
Therefore if I had:
<?php
$userID = 34;
$emailID = 395;
$favorite = "yes"; // could be yes or no.
I don't believe the query below is correct but gets the idea of what I am trying to do:
IF NOT EXISTS
(SELECT userID, favorite, emailID
FROM tbl
WHERE ((userID = '$userID')and(emailID = '$emailID'))
INSERT INTO tbl (userID, favorite, emailID) VALUES ('$userID', '$favorite', '$emailID')
ELSE UPDATE tbl
SET favorite = '$favorite'
WHERE ((userID = '$userID')and(emailID = '$emailID'))
(please know that I know that entering values into a query like this is a security risk, this is only to help explain my question.)
What is the best way to write the query? Can it be written with one query?
It can't be written as a single query, because the primary key isn't the two columns you're looking for - it's the id column only.
If userid and emailid are unique pairs, they should be the primary key for the table -- not the id value. ORMs typically prefer that a primary key be single column for making query construction easier but the approach suffers on performance. Anything requiring more than one columns is generally referred to as a "composite" - primary key, unique constraint/index, foreign key, etc.
The ANSI means of doing this would be a MERGE statement, but MySQL doesn't support the syntax because it already has the ON DUPLICATE KEY UPDATE and REPLACE INTO to provide the same functionality.
I would drop the redundant id column and define the primary key as the combination of (userID, emailID), which means you have exactly one record for each different combination of the two fields, which seems to be what you want. Then you can use the REPLACE INTO command, which is equivalent to an INSERT but deletes any existing record if the primary key matches.
you can do it really simply in a single call - but sprocs are bad aren't they ??
drop procedure if exists insert_update_tbl;
delimiter #
create procedure insert_update_tbl
(
in p_userID int unsigned,
in p_emailID int unsigned,
out p_id int unsigned
)
proc_main:begin
set p_id = 0;
if exists (select 1 from tbl where userID = p_userID and emailID = p_emailID) then
update tbl set favorite = 1 where userID = p_userID and emailID = p_emailID;
leave proc_main;
end if;
insert into tbl (userID, emailID, favorite) values (p_userID, p_emailID, 0);
set p_id = last_insert_id();
end proc_main #
delimiter ;