A general question, without a specific case in mind - is it usually preferred to use MySQL stored procedures over writing a PHP script that performs the same calculations an
I use stored procedures as much as possible for a number of reasons.
Reduce round trips to the database
If you need to alter multiple related tables at once, then you can use a single stored procedure so that only one call to the database is made.
Clearly define business logic
If certain things must be true about a query, then a store procedure lets someone who knows SQL (a fairly simple language) ensure that things are done right.
Create simple interfaces for other programmers
Your non-sql competent teammates can use much simpler interfaces to the database and you can be certain that they cannot put relationships in a bad state on accident.
Consider:
SELECT a.first_name, IFNULL( b.plan_id, 0 ) AS plan_id
FROM account AS a
LEFT JOIN subscription AS s ON s.account_id = a.id
WHERE a.id = 23
Compared to:
CALL account_get_current_plan_id( 23 );
Write them a nice little wrapper to take care of handling stored procedure calls and they are in business.
Update all usages in a system at once
If everyone uses stored procedures to query the database and you need to change how something works, you can update the stored procedure and it is updated everywhere as long as you don't change the interface.
Enforced security
If you can use just stored procedures to do everything within your system, then you can give seriously restricted permissions to the user account which accesses the data. No need to give them UPDATE, DELETE, or even SELECT permissions.
Easy error handling
Many people don't realize it, but you can create your stored procedures in such a manner that tracking down most problems becomes very easy.
You can even integrate your code base to properly handle the errors returned if you use a good structure.
Here is an example that does the following:
Here is the internals of a made up stored procedure that accepts an account id, the closing account id, and an ip address and then uses them to update as appropriately. The delimiter has already been set to $$:
BEGIN
# Helper variables
DECLARE r_code INT UNSIGNED;
DECLARE r_message VARCHAR(128);
DECLARE it_exists INT UNSIGNED;
DECLARE n_affected INT UNSIGNED;
# Exception handler - for when you have written bad code
# - or something really bad happens to the server
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 0 as `id`, 10001 as `code`, CONCAT(r_message, ' Failed with exception') as `message`;
END;
# Warning handler - to tell you exactly where problems are
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
SET r_code = 20001, r_message = CONCAT( r_message, 'WARNING' );
END;
SET r_code = 0, r_message = '', it_exists = 0, n_affected = 0;
# STEP 1 - Obvious basic sanity checking (no table scans needed)
IF ( 0 = i_account_id ) THEN
SET r_code = 40001, r_message = 'You must specify an account to close';
ELSEIF ( 0 = i_updated_by_id ) THEN
SET r_code = 40002, r_message = 'You must specify the account doing the closing';
END IF;
# STEP 2 - Any checks requiring table scans
# Given account must exist in system
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_account_id;
IF ( 0 = it_exists ) THEN
SET r_code = 40001, r_message = 'Account to close does not exist in the system';
END IF;
END IF;
# Given account must not already be closed
# - if already closed, we simply treat the call as a success
# - and don't bother with further processing
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_account_id AND status_id = 2;
IF ( 0 < it_exists ) THEN
SET r_code = 1, r_message = 'already closed';
END IF;
END IF;
# Given closer account must be valid
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_updated_by_id;
END IF;
# STEP 3 - The actual update and related updates
# r-message stages are used in case of warnings to tell exactly where a problem occurred
IF ( 0 = r_code ) THEN
SET r_message = CONCAT(r_message, 'a');
START TRANSACTION;
# Add the unmodified account record to our log
INSERT INTO account_log ( field_list )
SELECT field_list
FROM account
WHERE id = i_account_id;
IF ( 0 = r_code ) THEN
SET n_affected = ROW_COUNT();
IF ( 0 = n_affected ) THEN
SET r_code = 20002, r_message = 'Failed to create account log record';
END IF;
END IF;
# Update the account now that we have backed it up
IF ( 0 = r_code ) THEN
SET r_message = CONCAT( r_message, 'b' );
UPDATE account
SET
status_id = 2,
updated_by_id = i_updated_by_id,
updated_by_ip = i_updated_by_ip
WHERE id = i_account_id;
IF ( 0 = r_code ) THEN
SET n_affected = ROW_COUNT();
IF ( 0 = n_affected ) THEN
SET r_code = 20003, r_message = 'Failed to update account status';
END IF;
END IF;
END IF;
# Delete some related data
IF ( 0 = r_code ) THEN
SET r_message = CONCAT( r_message, 'c' );
DELETE FROM something
WHERE account_id = i_account_id;
END IF;
# Commit or roll back our transaction based on our current code
IF ( 0 = r_code ) THEN
SET r_code = 1, r_message = 'success';
COMMIT;
ELSE
ROLLBACK;
END IF;
END IF;
SELECT
r_code as `code`,
r_message as `message`,
n_affected as `affected`;
END$$
Status code meanings:
Rather than trust programmers who are not familiar with databases (or simply not familiar with the scheme), it is much simpler to provide them interfaces.
Instead of doing all of the above checks, they can simply use:
CALL account_close_by_id( 23 );
And then check the result code and take appropriate action.
Personally, I believe that if you have access to stored procedures and you are not using them, then you really should look into using them.