if condition inside a sql query - mysql

following is a part of my stored proceedure im using it to extract data from my db.
query
BEGIN
SET #sqlstring = CONCAT("SELECT b.ID, c.name, c.accountID,, b.total_logs, a.time_start, a.time_end ,COUNT(a.id) AS number_of_users
FROM ",logtable," a INNER JOIN users b on a.ID = b.ID INNER JOIN accounts c on b.accountID = c.accountID
GROUP BY ID;");
PREPARE stmt FROM #sqlstring;
EXECUTE stmt;
END
At times in the db, the logtable(table is passed in a variable like logtable_1, logtable_2 .... ) can be non existent, currently when the perticuler table is missing it crashes and throws an error because a.time_start, a.time_end cannot have values without the log table.
but what i want is just to assign NULL on values a.time_start, a.time_end without throwing an error,
So can any body tell is there a way i could modify this code like
BEGIN
if logtable exists
\\ the query
else
\\ the query
END

Find existence of the table by querying information_schema.tables. If it returns a count equals to 1 then you can proceed executing your query on the table. Otherwise go with your Else block.
Sample:
declare table_exists int default 0;
select count(1) into table_exists
from information_schema.tables
where table_schema='your_table_schema_name'
and table_name = 'your_table_name';
if table_exists then
-- do something
else
-- do something else
end if;

Related

AWS RDS MySQL - GROUP_CONCAT returns multiple rows instead of comma separated string

I have a Stored Procedure that takes three parameters, one of which is TEXT and it should contain comma separated values with ids, something like this -> '12345,54321,11111,22222', and this it inserts a row with data for each id in the list. Below is the Stored Procedure:
DELIMITER //
-- Create Stored Procedure
CREATE PROCEDURE MyProcedure(
IN ItemUUID VARCHAR(255),
IN ReceiverIds TEXT,
IN ItemCreated VARCHAR(255)
)
BEGIN
DECLARE strLen INT DEFAULT 0;
DECLARE SubStrLen INT DEFAULT 0;
IF ReceiverIds IS NULL THEN
SET ReceiverIds = '';
END IF;
do_this:
LOOP
SET strLen = LENGTH(ReceiverIds);
INSERT INTO item_receiver (item_uuid, receiver_id, item_created)
VALUES (ItemUUID ,SUBSTRING_INDEX(ReceiverIds, ',', 1),ItemCreated);
SET SubStrLen = LENGTH(SUBSTRING_INDEX(ReceiverIds, ',', 1)) + 2;
SET ReceiverIds = MID(ReceiverIds, SubStrLen, strLen);
IF ReceiverIds = '' THEN
LEAVE do_this;
END IF;
END LOOP do_this;
END//
DELIMITER ;
To get comma separated values with ids, something like this -> '12345,54321,11111,22222' I execute subquery, however, when I call this Stored Procedure I get this error -> Error Code: 1242. Subquery returns more than 1 row
SET group_concat_max_len = 2048;
call MyProcedure('random_test_uuid',(
SELECT CAST(GROUP_CONCAT(receiver_id SEPARATOR ',') AS CHAR) AS receiver_ids FROM receiver
WHERE user_id LIKE (SELECT user_id FROM user WHERE user_name LIKE 'myName')
GROUP BY receiver_id ),
'2017-09-24 23:44:32');
The problem is the subquery. Remove the group by:
SELECT CAST(GROUP_CONCAT(receiver_id SEPARATOR ',') AS CHAR) AS receiver_ids
FROM receiver
WHERE user_id LIKE (SELECT user_id FROM user WHERE user_name LIKE 'myName')
With the group by, you are getting a separate row for each receiver_id. The group_concat() is not doing anything.
Also, the CAST() is unnecessary. And this would typically be written as:
SELECT GROUP_CONCAT(r.receiver_id SEPARATOR ',') AS receiver_ids
FROM receiver r JOIN
user u
ON u.user_id = r.user_id
WHERE u.user_name LIKE 'myName';
If 'myName' is not using wildcards, then = is more appropriate than like.
If receiver_id is not unique in receiver, then you might want to add distinct to the group_concat().

How to loop through all the tables on a database to update columns

I'm trying to update a column (in this case, a date) that is present on most of the tables on my database. Sadly, my database has more than 100 tables already created and full of information. Is there any way to loop through them and just use:
UPDATE SET date = '2016-04-20' WHERE name = 'Example'
on the loop?
One painless option would be to create a query which generates the UPDATE statements you want to run on all the tables:
SELECT CONCAT('UPDATE ', a.table_name, ' SET date = "2016-04-20" WHERE name = "Example";')
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
You can copy the output from this query, paste it in the query editor, and run it.
Update:
As #PaulSpiegel pointed out, the above solution might be inconvenient if one be using an editor such as HeidiSQL, because it would require manually copying each record in the result set. Employing a trick using GROUP_CONCAT() would give a single string containing every desired UPDATE query in it:
SELECT GROUP_CONCAT(t.query SEPARATOR '; ')
FROM
(
SELECT CONCAT('UPDATE ', a.table_name,
' SET date = "2016-04-20" WHERE name = "Example";') AS query,
'1' AS id
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
) t
GROUP BY t.id
You can use SHOW TABLES command to list all tables in database. Next you can check if column presented in table with SHOW COLUMNS command. It can be used this way:
SHOW COLUMNS FROM `table_name` LIKE `column_name`
If this query returns result, then column exists and you can perform UPDATE query on it.
Update
You can check this procedure on sqlfiddle.
CREATE PROCEDURE UpdateTables (IN WhereColumn VARCHAR(10),
IN WhereValue VARCHAR(10),
IN UpdateColumn VARCHAR(10),
IN UpdateValue VARCHAR(10))
BEGIN
DECLARE Finished BOOL DEFAULT FALSE;
DECLARE TableName VARCHAR(10);
DECLARE TablesCursor CURSOR FOR
SELECT c1.TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS c1
JOIN INFORMATION_SCHEMA.COLUMNS c2 ON (c1.TABLE_SCHEMA = c2.TABLE_SCHEMA AND c1.TABLE_NAME = c2.TABLE_NAME)
WHERE c1.TABLE_SCHEMA = DATABASE()
AND c1.COLUMN_NAME = WhereColumn
AND c2.COLUMN_NAME = UpdateColumn;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET Finished = TRUE;
OPEN TablesCursor;
MainLoop: LOOP
FETCH TablesCursor INTO TableName;
IF Finished THEN
LEAVE MainLoop;
END IF;
SET #queryText = CONCAT('UPDATE ', TableName, ' SET ', UpdateColumn, '=', QUOTE(UpdateValue), ' WHERE ', WhereColumn, '=', QUOTE(WhereValue));
PREPARE updateQuery FROM #queryText;
EXECUTE updateQuery;
DEALLOCATE PREPARE updateQuery;
END LOOP;
CLOSE TablesCursor;
END
This is just an example how to iterate through all tables in database and perform some action with them. Procedure can be changed according to your needs.
Assuming you are using MySQL, You can use Stored Procedure.
This post is a very helpful.
Mysql-loop-through-tables

How to use query string inside IN statement in MySQL stored procedure

friends I have a stored procedure. which taking an input. But the input is a Query string. When I'm executing that string in IN statement I'm not getting anything.
My Stored Procedure is:
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255)
IN location VARCHAR(255),
IN classification VARCHAR(255))
BEGIN
SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(SELECT DISTINCT serviceprovider_cl AS serviceProviderId FROM db.serviceprovider_classification t1
INNER JOIN
db.locationid t2 ON t1.serviceprovider_cl=t2.serviceprovider_locationId
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_location
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t1.serviceproviderclassification_classification IN (classification)
AND
t2.location_serviceLocation IN (location)
AND
t3.category_serviceProviderCategory IN (category)
);
END
In category, classification and location. I'm getting another query in String. So to execute that string or How to convert it into query or how to use string as Query?
Thanks
for this you can use something called Prepared Statements, you can find more about that here...
So here is an SQL Fiddle where you can see how prepared statement works...
As you can see in this simple stored procedure it is not complicated that much. Basically there is three step to do this.
First create string which will be used in prepared statement. You do this to connect your query and query you will get as a string (IN category VARCHAR(255)) into one statement.
In my Fiddle:
SET #myString =
CONCAT('SELECT * FROM t2 WHERE t1_id IN (', category, ')');
That is the hardest part. Than you should perapare statement from that string
PREPARE statement FROM #myString;
End than execute the statement
EXECUTE statement;
When you call your procedure you pass your string which will be part of statement:
CALL SimpleProcedure('SELECT id FROM t1 WHERE val1 = "myVal2"');
And that's the logic you should apply on your problem.
That should look like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255))
BEGIN
SET #myString =
CONCAT('SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(', category,
' INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t3.category_serviceProviderCategory IN (', category, '))');
PREPARE statement FROM #myString;
EXECUTE statement;
END
EDIT: note that between ' and INNER JOIN there is one blank space because CONCAT, without that, would connect last word from 'category' query and inner join and that will cause you problem and your query wont work!
GL!
P.S. Also i notice that you mix both syntax when JOIN table (old comma separated JOIN and the new way) which is not look nice, it would be good to correct that and use new INNER JOIN syntax like you do in your sub query...
New EDIT (based on question edit)
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255)
IN location VARCHAR(255),
IN classification VARCHAR(255))
BEGIN
SET #myString =
CONCAT('SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(SELECT DISTINCT serviceprovider_cl AS serviceProviderId FROM db.serviceprovider_classification t1
INNER JOIN
db.locationid t2 ON t1.serviceprovider_cl=t2.serviceprovider_locationId
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_location
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t1.serviceproviderclassification_classification IN (', classification, ')
AND
t2.location_serviceLocation IN (', location, ')
AND
t3.category_serviceProviderCategory IN (', category, '))');
PREPARE statement FROM #myString;
EXECUTE statement;
END
Subquery in IN statement is equal to JOIN with subquery's table. Why you need "exotic" syntax instead of simple join.

Creating a self-reflexive cross join

Hi I'm looking to see if there is alternative syntax for the following self-reflexive cross-join. The objective is a sort of row-filler for a table - dates should have entries for every cdn. I am using MySQL
select
d.labelDate,
n.cdn,
networks.sites
from (
select
distinct labelDate
from
cdn_trend
) as d
cross join (
select
distinct cdn
from cdn_trend
) as n
left join cdn_trend as networks
on networks.labelDate = d.labelDate
and networks.cdn = n.cdn
order by
labelDate,
cdn
I've tried recasting the cross-join using simple aliases but that gives me column errors in the join. Is it possible to do so or should I consider using views instead?
As a cross join should simply return the Cartesian product of two tables it should be the same as simply selecting both without a join. However, the following raises an "unknown column d.labelDate in on clause" exception
select distinct d.labelDate, n.cdn, networks.sites
from
cdn_trend as d,
cdn_trend as n
left join cdn_trend as networks ON
(n.labelDate = networks.labelDate
and d.cdn = networks.cdn)
order by labelDate, cdn
Error Code: 1054. Unknown column 'd.cdn' in 'on clause'
Because the length of dand n are relatively small the size of the query is fast enough.
I think you were close in your original intent... For every date, you want the results of every network node status. If you list multiple tables in the WHERE clause without a join condition, it by default will create a Cartesian... From that, join to your detail table...
select
d.labelDate,
n.cdn,
networks.sites
from
( select d.LabelDate, n.cdn
from
( select distinct labelDate
from cdn_trend ) as d,
( select distinct cdn
from cdn_trend ) as n ) as CrossResults
LEFT JOIN cdn_trend as networks
on CrossResults.labelDate = networks.labelDate
and CrossResults.cdn = networks.cdn
order by
networks.labelDate,
networks.cdn
Reading the comments and the extra info you need a pivot with y - lableDate and x - cdn and values - sites assuming the values for cdn are (a,b,c) and that sites is a number you can try this:
SELECT
labelDate,
SUM(IF(cdn = 'a',sites,0)) as cdn_a,
SUM(IF(cdn = 'b',sites,0)) as cdn_b,
SUM(IF(cdn = 'c',sites,0)) as cdn_c
FROM
cdn_trend
GROUP BY
labelDate
And the output should be something like this (i used the sample data from you) :
labelDate cdn_a cdn_b cdn_c
2013-04 NULL 5 4
2013-05 6 NULL NULL
....
After some playing around this is the best I could come up with. It seems that parametrising the table name would be possible but would involve yet another layer of statement generation that I fortunately don't need for this project.
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER="root"#"localhost" PROCEDURE "cdn_pivot"(
IN slice varchar(64),
IN start date,
IN stop date)
BEGIN
SET ##group_concat_max_len = 32000;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' sum(IF(cdn = ''',
cdn,
''', sites,NULL)) "'
,cdn, '"'
)
) INTO #sql
FROM cdns ORDER BY sites;
SET #stmt = CONCAT('SELECT labelDate, ',
#sql,
' from cdns
WHERE slice = ''',
slice,
''' AND ( labelDate between''',
start,
''' AND ''',
stop,
'''
)
GROUP BY labelDate');
prepare stmt from #stmt;
execute stmt;
deallocate prepare stmt;
SET ##group_concat_max_len = 1024;
END
This can then simply be called e.,g.
call cdn_pivot('Top100', '2013-01-01', 2013-02-01')
Given the problems associated with testing this code and keeping it with any client-side code it's very tempting to generate the dynamic part of the head on the client and, at least for this kind of use case, the performance penalty of the additional query shouldn't be too high. The key thing is obviously understanding how to generate the columns dynamically.

How create a Stored procedure that will accept a parameter

I have a "set" of SQL statements
DROP TABLE IF EXISTS data.s;
CREATE TABLE data.s LIKE data._style;
INSERT INTO data.s Values (?,?,?,?,?,?,?,?,?,?,?,?,?);
UPDATE data.s n JOIN data.s_ o ON n.ID = o.ID SET n.TC = o.TC;
UPDATE data.s n JOIN data.s_ o ON n.ID = o.ID SET n.VR = o.VR;
UPDATE data.s n JOIN data.o_ o ON n.ID = o.ID SET n.OC = o.OC;
DELETE FROM data.s WHERE TC <= 0;
DELETE FROM data.s WHERE TC < 100;
DELETE FROM data.s WHERE OC < 100 ;
Using "s" table as example, How would I create a SP where "s" is a variable, which could be replace with t, u v, z...... whatever? I would like to change this variable with a SQL call statement.
MySQL does not handle real dynamic SQL, so you have to use prepared statement.
Look at the accepted answer : How To have Dynamic SQL in MySQL Stored Procedure and especially the link he gives (Dynamic SQL part).
Something like :
CREATE PROCEDURE `execute`(IN sqlQuery varchar(255))
BEGIN
set #sqlQuery := sqlQuery;
prepare stmp from #sqlQuery;
execute stmp;
deallocate prepare stmp;
END
CREATE PROCEDURE `yourProcName`(IN tableName varchar(50))
BEGIN
call execute(concat('DROP TABLE IF EXISTS ', tableName));
call execute(concat('CREATE TABLE ', tableName, ' LIKE data._style'));
...
END