Insert into a table, if there is no such rows - mysql

I have a sessions table with the 'userid' and 'expired' columns. I want to check, whether any row with the given user ID and not expired exists and insert a new row, if no rows found.
I've read about INSERT IGNORE, but (userid + expired) cannot be a key, since it's possible to have multiple rows with the same users (all expired), I cannot only have more than 1 not expired user.
I tried this, but to no avail ('You have an error...'):
IF (SELECT 1 FROM sessions WHERE user = :user AND expired = 0) <> 1
INSERT INTO sessions (user) VALUES(:user)
('expired' is 0 by default). mySQL version is 5.
UPDATE.
I've tried this as well:
IF NOT EXISTS(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0)
INSERT INTO sessions (user) VALUES(0)
using HeidySQL 7. It doesn't work neither. mySQL version is 5.5.
UPDATE2. Logical errors in my statement fixed.
Regards,

use If exists clause (in this case, not exists)
IF NOT EXISTS(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0)
INSERT INTO sessions (`user`) VALUES(:user)
edit
INSERT INTO sessions (`user`) select user from
(select :user as user from sessions where not exists(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0)) T1
then put 0 or the value you want harcoded in :user
INSERT INTO sessions (`user`) select user from
(select 0 as user from sessions where not exists(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0)) T1

Elvieejo's solution seems good, except looks like you'd have to use not exists going by the problem stated.
IF NOT EXISTS(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0)
INSERT INTO sessions (user) VALUES(:user)

INSERT IGNORE INTO sessions (user) VALUES(:user)

The problem with my statement is that IF EXISTS is supposed to be in a stored procedure. Nevertheless, the function IF() works:
SELECT IF(EXISTS(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0), 0, 1)
Now I'm looking for how to equal plain value like 0 and INSERT INTO statement. This doesn't work:
SELECT IF(EXISTS(SELECT 1 FROM sessions WHERE user = 0 AND expired = 0), 0,
INSERT INTO sessions (user) VALUES (0) SELECT)
If it was C++, I'd say 'type mismatch between ?: operands' and use comma operator. In SQL it doesn't work.
UPDATE
Well, I've just created a stored routine. Just because I cannot use IF otherwise. What a stupid RDBMS this mySQL is!

Related

Is it possible SET 'status' at other row false when NEW.status = true?

I have an AFTER UPDATE TRIGGER, that i want only one row that has status is TRUE. Here is my trigger,
CREATE TRIGGER check_true AFTER UPDATE ON table1 FOR EACH ROW
BEGIN
IF (NEW.status = 1) THEN
UPDATE table1 SET status = 0 WHERE id <> NEW.id;
END IF;
END
I try change status from 1 to 0 there is no error. But when I try change from 0 to 1 there is error like this,
Can't update table 'table1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger
Thanks in advance, sorry for my bad language.
A trigger cannot action the table it was fired upon, so what you want to do cannot be achieved with a trigger.
Another option would be to re-formulate your update queries so it updates all needed records at once, like:
update mytable
set status = case when id = :myid then :status else 0 end
where id = :myid or (status = 1 and :status = 1)
Here, :id and :status represent the query parameters.
The where clause selects records that match the :id parameter - if the new status is 1, it also selects existing records whose status is 1 (there should be only one).
Then, the set clause uses a case expression to update the status of the new record and reset the previous one (if any)
First, in MySQL 8+, you can have MySQL enforce the uniqueness.
alter table table1 add column status_true varchar(255) generated always as
(case when status then status end);
create unique index unq_table1_status_true on (status_true);
Now, the database ensures uniqueness.
You can update the value using a single update:
update table1
set status = not status
where status or id = 2
The "4" is the id you want to change to be the active one.
Here is a db<>fiddle.

MySQL limit maximum rows for same "user" value

How can I limit in MySQL maximum rows for the same "user" value?
For example, I have table with columns
id | user | data
and I would like to limit maximum rows count to 5 for each user (the same "user" value).
My idea is to use transactions (InnoDB):
start transaction
insert row with user = "XXX"
count rows, where user = "XXX"
if count < 5 commit else rollback
Is my idea good or exist also another (better) solution?
I think that it's ok, You may want to make a
select count(1) from table where user='XXX'
and check the count before performing the insert.
This could be done by using special "dual" table:
INSERT INTO table (id, user, data)
SELECT 1, 'XXX', 'somedata'
FROM dual
WHERE NOT EXISTS (
SELECT user
FROM t
WHERE user = 'XXX'
GROUP BY user
HAVING COUNT(*) >= 5
)

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

MySQL INSERT if SELECT COUNT(*) is greater than 1

I'm trying a MySQL statement, where I need to check of the user credentials are correct; and only if they are correct -- I insert the data
IF (SELECT COUNT(*) FROM users WHERE uid = 1 AND password = "67^8ax%!") > 0
THEN
INSERT INTO messages (uid, text, time)
VALUES (1, "Hello", CURRENT_TIMESTAMP)
END IF
I have to do both the operations in a single MySQL query due the limitations of my platform.
Any way to get this working?
Try this query
Working in MYSQL, thought shouldn't work in MYSQL :P
INSERT INTO messages (uid, text, time)
(SELECT 1, "Hello", CURRENT_TIMESTAMP FROM users WHERE uid = 1 AND password = "67^8ax%!")
Will work if there is only 1 distinct user with the given username and password..
FIDDLE

Voting - Stored procedures

I'm trying to come up with a nice elegant solution for a voting system like SO's. If there's a way to do it with elegance using triggers I couldn't figure it out so I'm trying with stored procedures. This is what I've come up with, It's not pretty so I'm asking for ideas. I'll probably even have one query rather than the query+stored procedure. But I'd really like to know a clean way to update a user's points and insert/update votes. Points are in a separate table to be updated by procedure.
Upvote
INSERT INTO votes
ON DUPLICATE KEY
UPDATE votes
SET v.weight = v.weight + 1
WHERE v.weight = 0 OR v.weight = -1
AND v.userid = {$uid}
AND v.itemid = {$itemid}
//call procedure to +1 user points
Downvote
INSERT INTO votes
ON DUPLICATE KEY
UPDATE votes
SET v.weight = v.weight - 1
WHERE v.weight = 1 OR v.weight = 0
AND v.userid = {$uid}
AND v.itemid = {$itemid}
//call procedure to -1
Flipdown (when user changes vote from up to down)
INSERT INTO votes
ON DUPLICATE KEY
UPDATE votes
SET v.weight = -1
WHERE v.weight = 1
AND v.userid = {$uid}
AND v.itemid = {$itemid}
//call procedure to -2
Flipup
INSERT INTO votes
ON DUPLICATE KEY
UPDATE votes
SET v.weight = 0
WHERE v.weight = -1
AND v.userid = {$uid}
AND v.itemid = {$itemid}
//call procedure to +2
I assume that votes table has 3 columns (post_id, user_id, weight). You can use the following query:
insert into votes(post_id, user_id, weight)
values(post_id_in, user_id_in, weight_in)
on duplicate key update
set
weight = weight_in;
Use should have unique index on(post_id, user_id).
If you denormalize data and table posts has column for total votes that you need to recalculate it.
I personally do not see a need for stored procedure in your case. If you are tracking user votes this means that the user id is available to you. I would suggest opening a mysql transaction perform your insert in the votes table and then perform an update to keep track of the user's score. Then if both calls are successful commit the transaction this will ensure data integrity.
Maybe you could share the specific reasoning why you want to use procedures?