Bruno
Bruno

Reputation: 53

How to aggregate 2 procuderes into one in MySQL?

I have 2 procedures that look almost the same. One receives a location and a price and do stuff and the other one receives the experience and the price.

First one:

-- Returns: service providers in given location and price
DELIMITER &&  
CREATE PROCEDURE get_service_providers_location_price (IN id_location INT,IN price DOUBLE,IN limite INT, IN inicio INT)  
BEGIN  
    SELECT user.idUser, user.name,user.lastActivity,user.active,serviceprovider.description,location.name AS location, location.cordsX, location.cordsY, file.image FROM user
    INNER JOIN location ON user.idLocation = location.idLocation
    INNER JOIN file ON user.idUser = file.idUser
    INNER JOIN serviceprovider ON user.idUser = serviceprovider.idSP 
    INNER JOIN category_has_serviceprovider ON serviceprovider.idSP = category_has_serviceprovider.idServiceProvider 
        WHERE user.type = 3 AND user.idLocation = id_location  
            AND (category_has_serviceprovider.price <= price OR category_has_serviceprovider.price IS NULL) and serviceprovider.idSubscription != 1 
    ORDER BY CASE WHEN serviceprovider.idSubscription in (5,6,7) then 1 else 2 end, serviceprovider.endSub ASC LIMIT limite OFFSET inicio;
END &&  
DELIMITER ;

Second one:

-- Returns: service providers in given experience and price
DELIMITER &&  
CREATE PROCEDURE get_service_providers_experience_price (IN experience INT, IN price DOUBLE,IN limite INT, IN inicio INT)  
BEGIN  
    SELECT user.idUser, user.name,user.lastActivity,user.active,serviceprovider.description,location.name AS location, location.cordsX, location.cordsY, file.image FROM user
    INNER JOIN location ON user.idLocation = location.idLocation
    INNER JOIN file ON user.idUser = file.idUser
    INNER JOIN serviceprovider ON user.idUser = serviceprovider.idSP 
    INNER JOIN category_has_serviceprovider ON serviceprovider.idSP = category_has_serviceprovider.idServiceProvider 
        WHERE user.type = 3
            AND (category_has_serviceprovider.price <= price OR category_has_serviceprovider.price IS NULL) AND category_has_serviceprovider.experience >= experience and serviceprovider.idSubscription != 1 
    ORDER BY CASE WHEN serviceprovider.idSubscription in (5,6,7) then 1 else 2 end, serviceprovider.endSub ASC LIMIT limite OFFSET inicio;
END &&  
DELIMITER ;

As you can see, only the WHERE clause changes. Is it possible, in MySQL, to aggregate this 2 procedures into one? Because I have like 5 procedures that look the same but it only changes the WHERE clause and I find it annoying doing a separate procedure for each case.

Upvotes: 0

Views: 55

Answers (3)

Deep
Deep

Reputation: 152

You can solve this problem bu using Dynamic SQL formation.

DELIMITER &&  
CREATE PROCEDURE get_service_providers_price (IN id_location INT,IN EXPERIENCE INT, IN price DOUBLE,IN limite INT, IN inicio INT)  
BEGIN  

SET @DynSql='SELECT USER.IDUSER,USER.NAME,USER.LASTACTIVITY,USER.ACTIVE,';
SET @DynSql=CONCAT(@DynSql,'SERVICEPROVIDER.DESCRIPTION,LOCATION.NAME AS LOCATION,');
SET @DynSql=CONCAT(@DynSql,'LOCATION.CORDSX,LOCATION.CORDSY,FILE.IMAGE ');
SET @DynSql=CONCAT(@DynSql,'FROM USER ');
SET @DynSql=CONCAT(@DynSql,'INNER JOIN LOCATION ON USER.IDLOCATION = LOCATION.IDLOCATION ');
SET @DynSql=CONCAT(@DynSql,'INNER JOIN FILE ON USER.IDUSER = FILE.IDUSER ');
SET @DynSql=CONCAT(@DynSql,'INNER JOIN SERVICEPROVIDER ON USER.IDUSER = SERVICEPROVIDER.IDSP ');
SET @DynSql=CONCAT(@DynSql,'INNER JOIN CATEGORY_HAS_SERVICEPROVIDER ON SERVICEPROVIDER.IDSP = CATEGORY_HAS_SERVICEPROVIDER.IDSERVICEPROVIDER ');
SET @DynSql=CONCAT(@DynSql,'WHERE USER.TYPE = 3 ');
IF ID_LOCATION>0 THEN
    SET @DynSql=CONCAT(@DynSql,' AND USER.IDLOCATION = ID_LOCATION');
END IF;
IF EXPERIENCE>0 THEN
    SET @DynSql=CONCAT(@DynSql,' AND CATEGORY_HAS_SERVICEPROVIDER.EXPERIENCE >= EXPERIENCE');
END IF;
SET @DynSql=CONCAT(@DynSql,' AND (CATEGORY_HAS_SERVICEPROVIDER.PRICE <= PRICE OR CATEGORY_HAS_SERVICEPROVIDER.PRICE IS NULL)');
SET @DynSql=CONCAT(@DynSql,' AND SERVICEPROVIDER.IDSUBSCRIPTION != 1');
SET @DynSql=CONCAT(@DynSql,' ORDER BY CASE WHEN SERVICEPROVIDER.IDSUBSCRIPTION IN (5,6,7) THEN 1 ELSE 2 END, SERVICEPROVIDER.ENDSUB ASC LIMIT LIMITE OFFSET INICIO;');
#   SELECT @DynSql;
EXECUTE S_Sql;
DEALLOCATE PREPARE S_Sql;

END &&  
DELIMITER ;

Upvotes: 1

Akina
Akina

Reputation: 42739

For example, you may use this:

CREATE PROCEDURE get_service_providers_price (IN experience INT,IN id_location INT,IN price DOUBLE,IN limite INT, IN inicio INT)  
BEGIN  
SELECT user.idUser, user.name,user.lastActivity,user.active,serviceprovider.description,location.name AS location, location.cordsX, location.cordsY, file.image FROM user
INNER JOIN location ON user.idLocation = location.idLocation
INNER JOIN file ON user.idUser = file.idUser
INNER JOIN serviceprovider ON user.idUser = serviceprovider.idSP 
INNER JOIN category_has_serviceprovider ON serviceprovider.idSP = category_has_serviceprovider.idServiceProvider 
WHERE user.type = 3 
  AND (category_has_serviceprovider.price <= price OR category_has_serviceprovider.price IS NULL) 
  and serviceprovider.idSubscription != 1 
  AND CASE WHEN experience IS NOT NULL
           THEN category_has_serviceprovider.experience >= experience
           ELSE user.idLocation = id_location  
           END
ORDER BY CASE WHEN serviceprovider.idSubscription in (5,6,7) then 1 else 2 end, serviceprovider.endSub ASC LIMIT limite OFFSET inicio;

If provided IN experience INT is set to some value then the condition by it is applied. If you provide NULL for this parameter then the condition by IN id_location INT is applied.

Pay attention - now your SP have 5 parameters, not 4.

PS. Your SP contains one SQL statement - hence BEGIN-END and DELIMITER not needed.

PPS. Using similar method you may create SP which may apply both one of the conditions and both of them at the same time or none. For example, it can be:

AND CASE WHEN experience IS NOT NULL AND id_location IS NOT NULL  -- apply both parameters filtering
         THEN category_has_serviceprovider.experience >= experience AND user.idLocation = id_location
         WHEN experience IS NOT NULL   -- apply filtering by experience  only
         THEN category_has_serviceprovider.experience >= experience
         WHEN id_location IS NOT NULL   -- apply filtering by location only
         THEN user.idLocation = id_location
         ELSE 1    -- not filter, return all rows
         END

PPPS. If you want to have 2 separate functions but one copy of the code (for example, these functions names are already used in a bunch of code) then you may do this:

CREATE PROCEDURE get_service_providers_location_price (IN id_location INT,IN price DOUBLE,IN limite INT, IN inicio INT)
CALL get_service_providers_price (NULL, id_location, price, limite, inicio);

CREATE PROCEDURE get_service_providers_experience_price (IN experience INT, IN price DOUBLE,IN limite INT, IN inicio INT)
CALL get_service_providers_price (experience, NULL, price, limite, inicio);

Upvotes: 1

slaakso
slaakso

Reputation: 9080

You could use IFNULL. Either pass the experience or the id_location value and use NULL for another.

It's also a good practice to have naming scheme (here in_-prefix) for the parameters so that the parameters are disticnt from the column names.

DELIMITER &&  
CREATE PROCEDURE get_service_providers_experience_price (
in_experience INT, 
in_id_location INT, 
in_price DOUBLE,
in_limite INT, 
in_inicio INT
)  
BEGIN  
SELECT 
  user.idUser, 
  user.name,
  user.lastActivity,
  user.active,
  serviceprovider.description,
  location.name AS location, 
  location.cordsX, 
  location.cordsY, 
  file.image 
FROM user
  INNER JOIN location ON user.idLocation = location.idLocation
  INNER JOIN file ON user.idUser = file.idUser
  INNER JOIN serviceprovider ON user.idUser = serviceprovider.idSP 
  INNER JOIN category_has_serviceprovider ON serviceprovider.idSP = category_has_serviceprovider.idServiceProvider 
WHERE user.type = 3
  AND (category_has_serviceprovider.price <= in_price OR category_has_serviceprovider.price IS NULL) 
  AND category_has_serviceprovider.experience >= IFNULL(in_experience, category_has_serviceprovider.experience)
  AND user.idLocation = IFNULL(id_location, user.idLocation)
  AND serviceprovider.idSubscription != 1 
ORDER BY CASE WHEN serviceprovider.idSubscription in (5,6,7) then 1 else 2 end, serviceprovider.endSub ASC 
LIMIT in_limite OFFSET in_inicio;

END 
&&  
DELIMITER ;

Upvotes: 1

Related Questions