alanmanderson
alanmanderson

Reputation: 8200

MySQL insert only if a condition is true

I have several insert statements that I only want to execute if they are true.

Here is my example:

START TRANSACTION;

INSERT INTO fields (field_name, control_type_id, needs_approval)
VALUES 
('Array Photos', 3, 0);

INSERT INTO field_to_job_type (field_id, job_type_id, sequence_number, parent_id)
VALUES
(last_insert_id(), (SELECT job_type_id FROM job_types WHERE job_type_name = 'Cash'), 1, (SELECT field_id FROM fields where field_name = 'Photo Pack'));

COMMIT;

Now, I only really want to run the second query if there is only 1 result for this query:

SELECT job_type_id FROM job_types WHERE job_type_name = 'Cash'.

If that query returns 2 results, I would want to abandon the transaction.

Is that possible? Something I tried:

START TRANSACTION;
INSERT ...;
INSERT ...;
IF(SELECT count(job_type_name) FROM job_types WHERE job_type_name = 'Cash') = 1, 'COMMIT', 'ROLLBACK');

Which, obviously didn't work.

Upvotes: 2

Views: 22887

Answers (3)

Robert Columbia
Robert Columbia

Reputation: 6418

You can use a WHERE clause for this. Convert your INSERT VALUES to an INSERT SELECT and add a WHERE clause.

For example,

INSERT INTO fields (field_name, control_type_id, needs_approval)
SELECT
'Array Photos', 3, 0
WHERE Condition;

If Condition is true, it will insert the row. If Condition is false, the SELECT will return zero rows and thus the INSERT will insert zero rows.

If the query above does not run due to a syntax error, you can, as @spencer7593 mentioned, add FROM DUAL.

INSERT INTO fields (field_name, control_type_id, needs_approval)
SELECT
'Array Photos', 3, 0
FROM DUAL
WHERE Condition;

The DUAL table is essentially a dummy table that has predictable content and can be relied upon to always have at least one row.

Upvotes: 6

spencer7593
spencer7593

Reputation: 108400

To answer the question you asked, about conditionally issuing a ROLLBACK statement:

That can not be done in the context of a single SQL statement.

We would need to run a separate query that returns a result, retrieve the result, and then use that result in a comparison in an if/else, and issue a separate SQL ROLLBACK statement.

We could demonstrate how to do those steps in a MySQL stored program; but the question doesn't specifically mention using a procedure.

Or, the same steps could be performed in a client program, issuing separate SQL statements for the SELECT query, and the ROLLBACK.


I would approach the design differently. If we can determine ahead of time, before we ever issue an INSERT statement, that we would want to ROLLBACK the transaction... then we could avoid performing an INSERT in the first place. And avoiding the unnecessary overhead, parsing the statement, obtaining locks, writing to the log, generating rollback, wasting an AUTO_INCREMENT, etc.

In the context of a MySQL stored program (for example, a PROCEDURE), you could execute a SELECT COUNT() INTO var and then use an IF THEN ELSE block to test the value of the variable. Or, you could setup a CONTINUE handler to handle a particular exception.

But the question doesn't specifically mention that this is in the context of a MySQL stored program.


Personally, I would approach the design a little differently.

I'm wondering why it's a problem that there are two or more rows with the value of 'Cash' for job_type_name.

I see why it would cause a problem for the INSERT statement shown in the question, the SELECT query returning more than one row is going to throw an error in the context it's in.

But there are fixes for that. We could take the lowest or highest value, use a MIN() or MAX() aggregate, or add an ORDER BY ... LIMIT 1, or we could add some additional criteria that would guarantee the return of a single value.

I'm wondering if job_type_name be UNIQUE in the job_type table. Or are we only concerned that the 'Cash' value only occurs once.

Looking at the question, I can't help but wonder if this need to conditionally rollback a transaction is a symptom of a more encompassing design issue.

Looking at the question being asked, I think there is more going on here. I would take a few steps back, and reconsider the proposed design.

Upvotes: 1

e4c5
e4c5

Reputation: 53734

Select the count into a variable and then use that in the comparison.

DECLARE total_row INT DEFAULT 0

START TRANSACTION;

INSERT INTO fields (field_name, control_type_id, needs_approval)
VALUES ('Array Photos', 3, 0);


SELECT count(job_type_name) FROM job_types WHERE job_type_name = 'Cash' INTO total_rows;

IF total_rows =2 THEN
    ROLLBACK;
ELSE

    INSERT INTO field_to_job_type (field_id, job_type_id, sequence_number, parent_id)
    VALUES (last_insert_id(), (SELECT job_type_id FROM job_types WHERE job_type_name = 'Cash'), 1, (SELECT field_id FROM fields where field_name = 'Photo Pack'));
   COMMIT;
END IF;

Upvotes: 3

Related Questions