Change schema of multiple PostgreSQL functions in one operation? - function

Recently I needed to move objects from PostgreSQL's default schema "public" to another schema. I found this post which shows how to move tables which was great, but I also need to move the functions.

You could refine the loop some more (demonstrating only the second query):
DO
$do$
DECLARE
r record;
sql text = '';
BEGIN
FOR r IN
SELECT p.proname, pg_get_function_identity_arguments(p.oid) AS params
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE nspname = 'public'
-- and other conditions, if needed
LOOP
sql := sql
|| format(E'\nALTER FUNCTION public.%I(%s) SET SCHEMA new_schema;'
,r.proname, r.params);
END LOOP;
RAISE NOTICE '%', sql; -- for viewing the sql before executing it
-- EXECUTE sql; -- for executing the sql
END
$do$;
Major points
Assignment operator in plpgsql is :=. = works, but is undocumented.
Remove unneeded tables from FROM.
concat() may be overkill, but format() simplifies the syntax.
Better set-based alternative
Re-casting the problem as set-based operation is more effective. One SELECT with string_agg() does the job:
DO
$do$
DECLARE
sql text;
BEGIN
SELECT INTO sql
string_agg(format('ALTER FUNCTION public.%I(%s) SET SCHEMA new_schema;'
,p.proname, pg_get_function_identity_arguments(p.oid)), E'\n')
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE nspname = 'public';
-- and other conditions, if needed
RAISE NOTICE '%', sql; -- for viewing the sql before executing it
-- EXECUTE sql; -- for executing the sql
END
$do$;

DO$$
DECLARE
row record;
BEGIN
FOR row IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' -- and other conditions, if needed
LOOP
EXECUTE 'ALTER TABLE public.' || quote_ident(row.tablename) || ' SET SCHEMA [new_schema];';
END LOOP;
END;
$$;
DO$$
DECLARE
row record;
sql text = E'\n';
BEGIN
FOR row IN
SELECT
proname::text as proname,
pg_get_function_identity_arguments(p.oid) AS params
FROM pg_proc p
JOIN pg_namespace n on n.oid = p.pronamespace
WHERE nspname = 'public'
-- and other conditions, if needed
LOOP
sql = CONCAT(sql, E'\n',
'ALTER FUNCTION public.', row.proname,
'(', row.params, ') SET SCHEMA [new_schema];');
END LOOP;
RAISE NOTICE '%', sql; -- for viewing the sql before executing it
-- EXECUTE sql; -- for executing the sql
END;$$;

Related

MySQL Prepared Statements Appeding Where Condition Not Working

Here is the actual query
select taken_date, DATE_FORMAT(taken_date, '%Y') taken_date_year, count(id) num_of_orders, sum(total_order_days) total_work_days from
(
select id, taken_date, getNumOfWorkDaysForOrder(order.order_num) total_order_days from order
where order.is_active = 1 and order.deleted_at is null and order.vendor_id = vendor_input and
order.company_id = company_input and order.contact_id = contact_input and order.candidate_id = candidate_input
order by taken_date
) as order_years group by YEAR(taken_date) order by taken_date desc;
I want to add where condition based on the input if it is not null, tried prepared statements and concatenation to add the where condition to the query but no luck.
DELIMITER $$
CREATE PROCEDURE
getAllActiveOrdersGroupByTakenDate(vendor_input INT, company_input INT, contact_input INT, candidate_input INT)
BEGIN
SET #prepareQuery = "select id, taken_date, getNumOfWorkDaysForOrder(order.order_num) total_order_days from order
where order.vendor_id = "+ vendor_input +" and order.is_active = 1 and order.deleted_at is null";
IF company_input IS NOT NULL THEN
SET #prepareQuery = CONCAT(#prepareQuery, ' ', "and order.company_id = "+company_input);
END IF;
IF contact_input IS NOT NULL THEN
SET #prepareQuery = CONCAT(#prepareQuery, ' ', "and order.contact_id = "+contact_input);
END IF;
IF candidate_input IS NOT NULL THEN
SET #prepareQuery = CONCAT(#prepareQuery, ' ', "and order.candidate_id = "+candidate_input);
END IF;
SET #finalQueryPart1 = CONCAT("select taken_date, DATE_FORMAT(taken_date, '%Y') taken_date_year, count(id) num_of_orders, sum(total_order_days) total_work_days from
(", #prepareQuery);
SET #finalQuery = CONCAT(#finalQueryPart1, ") as order_years group by YEAR(taken_date) order by taken_date desc");
PREPARE stmt FROM #finalQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END $$
DELIMITER ;
Can someone help me to achieve this?
Update: you had an issue with your CONCAT() syntax before you edited your question.
When you want to append content, you must assign it back to the original string. CONCAT() is a function that returns the concatenated string. It does not have any side-effect of modifying the variable you use as an argument.
WRONG:
CONCAT(#prepareQuery, ' ', "and order.company_id=company_input");
RIGHT:
SET #prepareQuery = CONCAT(#prepareQuery, ' ', "and order.company_id=company_input");
Also, I'm not sure if you can reference the procedure input parameters in these expressions.
Frankly, I hardly ever use stored procedures. MySQL's implementation of stored procedures sucks. It's inefficient, doesn't save compiled procedures, there's no debugger, there are no packages, and so on.
Mostly I just execute dynamic SQL from my applications. There you have debugging, code reuse, familiar string manipulation in a familiar language.
I understand that stored procedures are the tradition in Oracle and Microsoft SQL Server communities, but it's really better to avoid stored procedures in MySQL.

cursor loop in PostgreSQL

This below code is a cursor in PostgreSQL 9.0. I want to fetch my records by joining more than one table and i am getting JSON data from that join.
So I want to loop those records and parse that json using query something like
SELECT "Dump"->'activities-steps'->0->'value' as "steps"
FROM "ActivitySessionDump" where "Id"=42594321345021288
then i have to get data from this query and insert to some other table like
insert to table name (key,value);
So i prepared one readonly cursor to achieve this task
begin work;
DECLARE
sessionids INSENSITIVE no scroll CURSOR FOR
SELECT asn."Id",asn."UserId",asn."ActivityId",ad."Dump"
FROM "ActivitySession" as asn inner join "ActivitySessionDump" as ad
on asn."Id"=ad."ActivitySessionId"
where asn."CreatedAt" between now() - interval '5 hours' and now() and asn."ActivityId"=1
for read only;
---- i want her loop should start and i will parse a json Dump by executing query--------
--------insert record to another table---------------
---end loop-----------
FETCH next FROM sessionids;
CLOSE sessionids;
COMMIT WORK;
Any help really appreciated.Thanks
Since you cannot loop in SQL, you'll have to use PL/pgSQL, for example with a DO statement.
In your case, that could look like this:
DO
$$DECLARE
asn_id ...;
asn_userid ...;
...
c refcursor;
BEGIN
/* assign the SQL cursor to the refcursor variable */
c := 'sessionids';
LOOP
FETCH c INTO asn_id, asn_userid, ...;
IF NOT FOUND THEN EXIT; END IF;
/* process the result row */
END LOOP;
END;$$;
Of course it is a bit awkward to declare a cursor in SQL and use it in PL/pgSQL.
It might be better to put the statement in a FOR loop like this:
FOR asn_id, asn_userid, ... IN
SELECT ...
LOOP
/* process the result row */
END LOOP;
Maybe you could even squeeze the whole thing into a single INSERT statement, that would be most efficient:
INSERT INTO ...
(SELECT ...);
As far as I can tell, the loop or function is unnecessary. It can be replaced with a simple query using string aggregation:
SELECT string_agg("Dump"->'activities-steps'->0->'value', ',') as steps
FROM "ActivitySessionDump" d
WHERE d."ActivitySessionId" IN (SELECT asn."Id"
FROM "ActivitySession" as asn
join "PersonDataSource" as pd on pd."UserId" = asn."UserId"
where asn."CreatedAt" between now() - interval '5 days' and now()
and asn."ActivityId" = 1
and pd."DataSourceId" = 1);
Unrelated, but: you should really avoid those dreaded quoted identifiers
here is the code for my question and i am unable to
EXECUTE 'SELECT rec."Dump"::json#>''{activities-steps,0}''->>''value'' as steps ' INTO jsonrec; line;
SELECT '{"activities-steps":[{"dateTime":"2016-10-17","value":"4023"}]}'::json#>'{activities-steps,0}'->>'value' as steps;
where as i can execute this code in console.
but inside function i cant.
CREATE OR REPLACE FUNCTION ThirdPartyDataParse()
RETURNS text AS $$
DECLARE
sessionid NO SCROLL CURSOR FOR SELECT asn."Id",asn."UserId",asn."ActivityId",pd."DataSourceId",ad."Dump"::TEXT
FROM "Development"."ActivitySession" as asn inner join "Development"."PersonDataSource" as pd on pd."UserId" = asn."UserId" inner join "Development"."ActivitySessionDump" as ad
on asn."Id"=ad."ActivitySessionId" where asn."CreatedAt" between now() - interval '5 days' and now() and asn."ActivityId"=1 and pd."DataSourceId"=1 for read only;
titles TEXT DEFAULT '';
rec record;
jsonrec record;
BEGIN
OPEN sessionid;
loop
FETCH sessionid INTO rec;
--raise notice '%d',rec."UserId";
if not found then
exit ;
end if;
EXECUTE 'SELECT rec."Dump"::json#>''{activities-steps,0}''->>''value'' as steps ' INTO jsonrec;
titles := titles || ',' || jsonrec."steps";
end loop;
return titles;
END;
$$ LANGUAGE plpgsql;

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

MySQL generates many new result-set

I've got MySQL procedure which I execute in MySQL Workbench. The problem is that it generates many new result-set, and GUI client show it (...many times) -
I found solution, which I can use instead just select clause to avoid generating result-sets. It looks like:
SELECT EXISTS(
SELECT ...
)
INTO #resultNm
Unfortunately it won't work with LIMIT ?,1 where ? is my variable 'i'. I also cant use just 'i' instead % because I am working on MySQL 5.1 (and limit clauses can't be done in other way). So my question - are other possibilities to hide result-sets?
CREATE PROCEDURE LOOPDOSSIERS(starter integer, finish integer)
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE row_oid binary(16);
DECLARE row_str VARCHAR(256);
DECLARE row_old VARCHAR(256);
SET i=starter;
WHILE i<finish DO
-- SET row_str = ();
SET #row_str = 'select CAST(concat(d.prefixe_numero,d.numero) as CHAR) from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid limit ?,1';
PREPARE stmt1 FROM #row_str;
EXECUTE stmt1 USING #i;
SET #row_oid = 'select x.courrier_oid
from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid LIMIT ?,1';
PREPARE stmt2 FROM #row_oid;
EXECUTE stmt2 USING #i;
select dossiers_str from courrier_envoye where oid = row_oid into row_old;
update courrier_envoye set dossiers_str = CAST(CONCAT(row_str, " ", row_old) AS CHAR) where oid = row_oid;
SET i = i + 1;
END WHILE;
End;
;;
LIMIT without an ORDER BY clause doesn't have a well defined behavior. For your parameters to work in a sensible way, you'll need to order by something. The starter and finish variables aren't very meaningful at the moment, but it's currently not clear what they're intended to be.
I think you can probably accomplish this whole procedure in a single query using the syntax in this answer (also probably much faster). That probably won't work with LIMIT, but I'd highly recommend using a range of some kind in the where clause rather than a limit anyway.

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