How much statement can be made conditional in MySQL? - mysql

the MySQL manual says that a CASE statement (the WHEN part) can contain a statement_list.
How much statement is that to be exact? I need to perform a pretty big query which I would like to parametrize. However this also implies that I need to change tables that are being joined. I've read that this cannot be done using CASE statements, so I'm looking for a solution to not have to re-create the stored procedure for every parameter value that there is...
UPDATE
I tried to solve it using a CURSOR. I have defined a CURSOR for every object I have. I would like to use IF-ELSE-statements to choose which cursor to open, loop and close. However I cannot get the syntax right...
All cursors and variables (usb, obj, mm, stamp) are declared with different names, but I've shortened them for reading....
if OBJECTTYPE = 1 then
open CUR_1_BUILDING;
LOOP1: loop
fetch CUR_1_BUILDING into usb, obj, mm, stamp;
if no_more_rows then
close CUR_1_BUILDING;
leave loop1;
end if;
INSERT INTO ObjectCache (usb, obj, mm, date_checked) VALUES (usb, obj, mm, now());
end loop LOOP1;
CLOSE CUR_1_BUILDING;
else if OBJECTTYPE = 2 then
open CUR_2_CITY;
LOOP2: loop
fetch CUR_2_CITY into usb, obj, mm, stamp;
if no_more_rows then
close CUR_2_CITY;
leave loop2;
end if;
INSERT INTO ObjectCache (usb, obj, mm, date_checked) VALUES (usb, obj, mm, now());
end loop LOOP2;
close CUR_2_CITY;
end if;
Is this is any way possible to do using CASE statements?
BEGIN
CASE
when OBJECTTYPE = 1
INSERT INTO ObjectCache SELECT id FROM Building
when OBJECTTYPE = 2
INSERT INTO ObjectCache SELECT id FROM City
END CASE;
END

You could use IF ELSE or you could build and execute dynamic SQL.

Related

Record changes in a different table

I have successfully set up a history table according to this tutorial:
https://www.cybertec-postgresql.com/en/tracking-changes-in-postgresql/
My problem is that this function saves both the whole new record and the whole old record as jsons.
How can I alter this function so that only those column titles and values will be added to the json, which have really been changed?
In other words how can I replace the expression 'row_to_json(OLD)' with one which represents only the difference between row_to_json(NEW) row_to_json(OLD)?
CREATE FUNCTION change_trigger() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, new_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW));
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, new_val, old_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP,
row_to_json(NEW), row_to_json(OLD));
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, old_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(OLD));
RETURN OLD;
END IF;
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
Finally figured out that
I needed to convert the 'NEW' and 'OLD' rows to jsonb. (to_jsonb(NEW))
I needed another function which does the subtraction
https://coussej.github.io/2016/05/24/A-Minus-Operator-For-PostgreSQLs-JSONB/

Performing an insert query into a loop from an select results

I'm trying to perform an insert query into a loop from a select results.
The problem is when I want to retrieve only one result from a select query that works correctly
but when the select query retrieve more than one result that doesn't work.
Is there an issue with the GROUP_CONCAT function ? How can I do in order fix the issue ?
I let you the code snippet here :
BEGIN
SELECT idJour INTO #idJour FROM `jour` WHERE JourSemaine = DayWeek;
SELECT GROUP_CONCAT(idTrancheH) AS result FROM tranchehoraire WHERE TrancheH BETWEEN heureDebut AND heureFin;
loop_label : loop
INSERT INTO `horaire` (`idEmployé`, `idJour`, `idTrancheH`) VALUES
(idEmployee,#idJour, #idTrancheH);
SET heureDebut = ADDTIME(heureDebut ,'0:30:00.00');
IF heureDebut <= TIME(heureFin) THEN
ITERATE loop_label;
ELSE
LEAVE loop_label;
END IF;
END LOOP;
RETURN heureDebut;
END
Thanks a lot

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;

MySQL Stored Procedures with detail table and transactions

I have a relatively large (for me) stored procedure that I'm working on that is responsible for maintaining inventory values whenever a "movement" occurs.
I originally had all the checks and inserts happening in PHP, but I felt that the number of things that need to happen in the database made it more controllable by putting it in a stored procedure and denying INSERT/UPDATE permissions to these tables.
So history aside, I'm curious if there is a way to run a prepared statement for the stored procedure in which I can pass multiple values that will be insert into a detail table.
For example, let's say I want to insert the following data into a parent and children records.
"ACME", "TNT", 100
"ACME", "Anvil", 5
The parent record would contain ACME, and the children records would have the details of TNT, 100 and Anvil, 5.
Is it possible to somehow pass this data into a single stored procedure?
The reason I'm hoping to do this in one pass, is let's say we don't have enough TNT available, I don't want the original parent record to be inserted unless the entire transaction can take place.
Would concatenating the data sent to the stored procedure work? I don't imagine sending more than 1000 detail lines at a time to the database.
do_movement('ACME','2:TNT,100||Anvil,5'); The 2 in front denotes the number of details to expect, might help with error catching. Just a thought.
This way the code would be responsible for formatting, but I could output SQLEXCEPTIONS and have everything happen in one true transaction.
Drawing heavily from this post: MySQL Split Comma Separated String Into Temp Table
Using the SPLIT_STR function from (also referenced in previous post): http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
CREATE FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '');
And then my functionality to parse N:s1|s2|..|sN and check.
I could easily call SPLIT_STR again if I had something like N:s1,a1|s2,a2|..|sN,aN
BEGIN
DECLARE defined_quantity INT;
IF serial_numbers REGEXP '^[^0]{0,}[[:digit:]]+:.*$' = TRUE THEN -- check that we are indeed at least starting properly formatted.
SET defined_quantity = CONVERT(LEFT(serial_numbers,LOCATE(':',serial_numbers)-1), UNSIGNED);
IF defined_quantity <= 0 THEN
SIGNAL SQLSTATE '45006'
SET MESSAGE_TEXT = 'The quantity defined with the serial number list is <= 0.';
END IF;
SET serial_numbers = RIGHT(serial_numbers,LENGTH(serial_numbers) - LOCATE(':',#serial_numbers));
BEGIN
DECLARE a INT Default 0 ;
DECLARE str VARCHAR(255);
DECLARE q INT;
simple_loop: LOOP
SET a=a+1;
SET str=TRIM(SPLIT_STR(serial_numbers,"|",a));
IF str='' THEN
SET a=a-1; -- we ignore the last increment
LEAVE simple_loop;
END IF;
#Do Inserts into temp table here with str going into the row
INSERT INTO transaction_detail (transaction_id, serial_number,created_at,updated_at) VALUES(transaction_id, str,NOW(),NOW());
END LOOP simple_loop;
SELECT a, defined_quantity, quantity;
IF a <> defined_quantity OR a <> quantity OR defined_quantity <> quantity THEN
SIGNAL SQLSTATE '45007'
SET MESSAGE_TEXT = 'The quantities do not match for the serial numbers provided.';
END IF;
END;
ELSE
SIGNAL SQLSTATE '45005'
SET MESSAGE_TEXT = 'The serial number formatted list is not properly formatted. Please provide in "n:s1|s2|s3|...|sn" format.';
END IF;
END;

Using 2 cursors, one to get a parameter for the other

I am doing an exercies that requires me to use 2 explicit cursors. One cursor is to get a parameter for the second. The end goal is to find the most recent 'rental date' for each car based on registration. For example, registration 345JKL was rented on the 01/06/2010, 07/09/2011 and 08/09/2013. I want this to only return the most recent date which is 08/09/2013 and I want it to provide a most recent date for every registration in the table.
I am aware that there are better ways to do this such as MAX, subqueries etc (both of which I am not allowed to use), but as a 'lateral thinking exercise' I am required to do it without inbuilt functions, subqueries and the rest of the things that make life easy.
I am a little stuck with this one.
This is what I have so far which is getting me nowhere:
declare
v_maxdate DATE;
v_reg VARCHAR2(20);
cursor reg_cur IS
SELECT * FROM i_car;
v_car reg_cur%ROWTYPE;
cursor c_reg (reg i_booking.registration%TYPE) IS
SELECT date_reserved from i_booking
WHERE registration = reg;
v_date c_reg%ROWTYPE;
begin
FOR v_date IN c_reg (v_car.registration) LOOP
v_maxdate := '01/JAN/90';
If v_date > v_maxdate THEN
v_maxdate := v_date;
end if;
end loop;
end;
It is throwing me this error:
If v_date > v_maxdate THEN
*
ERROR at line 17:
ORA-06550: line 17, column 11:
PLS-00306: wrong number or types of arguments in call to '>'
ORA-06550: line 17, column 1:
PL/SQL: Statement ignored
I figured rather than continuing to bang my head on the desk I would ask for guidance.
Your help is appreciated.
The v_date variable is a record, so you have to use the dot to actually access some of its fields - date_reserved in your case:
begin
v_maxdate := '01/JAN/90';
FOR v_date IN c_reg (v_car.registration) LOOP
If v_date.date_reserved > v_maxdate THEN
v_maxdate := v_date;
end if;
end loop;
end;
I have also moved initialization of v_maxdate outside of the loop.