PostgreSQL: a valid variable assignation sample? - mysql

After reading this question, I'm trying to convert some SQL from MySQL to PostgreSQL. Thus I need variable assignation:
INSERT INTO main_categorie (description) VALUES ('Verbe normal');
SET #PRONOMINAL := SELECT LAST_INSERT_ID();
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW());
SET #verbe_149 = SELECT LAST_INSERT_ID();
INSERT INTO main_motcategorie (mot_id,categorie_id) VALUES (#verbe_149,#PRONOMINAL);
How would you do this with PostgreSQL? No useful sample in the documentation of v9 and v8 (almost the same).
NB: I dont want to use a stored procedure like here, I just want "raw sql" so I can inject it through CLI interface.

There are no variables in Postgres SQL (you can use variables only in procedural languages).
Use RETURNING in WITH query:
WITH insert_cat AS (
INSERT INTO main_categorie (description)
VALUES ('Verbe normal')
RETURNING id
),
insert_mot AS (
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW())
RETURNING id
)
INSERT INTO main_motcategorie (mot_id,categorie_id)
SELECT m.id, c.id
FROM insert_mot m, insert_cat c;
As an alternative, you can use custom configuration parameters in the way described in this post.
Create two functions:
create or replace function set_var (name text, value text)
returns void language plpgsql as $$
begin
execute format('set mysql.%s to %s', name, value);
end $$;
create or replace function get_var (name text)
returns text language plpgsql as $$
declare
rslt text;
begin
execute format('select current_setting(''mysql.%s'')', name) into rslt;
return rslt;
end $$;
With the functions you can simulate variables, like in the example:
INSERT INTO main_categorie (description)
VALUES ('Verbe normal');
SELECT set_var('PRONOMINAL', (SELECT currval('main_categorie_id_seq')::text));
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW());
SELECT set_var('verbe_149', (SELECT currval('main_mot_id_seq')::text));
INSERT INTO main_motcategorie (mot_id,categorie_id)
SELECT get_var('verbe_149')::int, get_var('PRONOMINAL')::int;
This is certainly not an example of good code.
Particularly the necessity of casting is troublesome.
However, the conversion can be done semi-automatically.

You can run PostgreSQL scripts outside of a function using the do construct. Here's an example with Donald Ducks' nephews. First the nephew will be added to the nephew table, and then we'll add a baseball cap using the newly inserted nephew's id.
First, create two tables for nephews and baseball caps:
drop table if exists nephew;
drop table if exists cap;
create table nephew (id serial primary key, name text);
create table cap (id serial, nephewid bigint, color text);
Now add the first nephew:
do $$declare
newid bigint;
begin
insert into nephew (name) values ('Huey') returning id into newid;
insert into cap (nephewid, color) values (newid, 'Red');
end$$;
The returning ... into ... does in Postgres what currval does in MySQL. Huey's new id is assigned to the newid variable, and then used to insert a new row into the cap table. You can run this script just like any other SQL statement. Continue with Dewey and Louie:
do $$declare
newid bigint;
begin
insert into nephew (name) values ('Dewey') returning id into newid;
insert into nephew (name) values ('Louie') returning id into newid;
insert into cap (nephewid, color) values (newid, 'Green');
end$$;
And you end up with:
# select * from nephew;
id | name
----+-------
1 | Huey
2 | Dewey
3 | Louie
(3 rows)
# select * from cap;
id | nephewid | color
----+----------+-------
1 | 1 | Red
2 | 3 | Green
(2 rows)
See it working at SQL Fiddle.

Related

MySQL PROCEDURE using IF Statement with #Parameter Not Working

Why is the data not being inserted on the table when I execute the procedure, what seems to be lacking with the code?
I'm testing the procedure on phpMyAdmin > myDatabase > Procedures "Routines Tab" and clicking "Execute", prompts with a modal and ask for the values of "#idproc and #nameproc.
I tried with just the INSERT code it works, but when I add the IF condition it doesn't work.
Using XAMPP 8.0.3,
10.4.18-MariaDB
DELIMITER $$
CREATE DEFINER=`root`#`localhost:3307` PROCEDURE `testproc`(IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
IF #idproc = 0 THEN
INSERT INTO testproc(
id,
name)
VALUES(
#idproc,
#nameproc
);
ELSE
UPDATE testproc
SET
id = #idproc,
name = #nameproc
WHERE id = #idproc;
END IF;
SELECT * FROM testproc;
END$$
DELIMITER ;
You mix local variables (their names have not leading #) and user-defined variables (with single leading #). This is two different variable types, with different scopes and datatype rules. Procedure parameters are local variables too.
So when you use UDV which was not used previously you receive NULL as its value - and your code works incorrectly. Use LV everywhere:
CREATE DEFINER=`root`#`localhost:3307`
PROCEDURE `testproc` (IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
IF idproc = 0 THEN
INSERT INTO testproc (name) VALUES (nameproc);
ELSE
UPDATE testproc SET name = nameproc WHERE id = idproc;
END IF;
SELECT * FROM testproc;
END
You do not check does specified idproc value exists in the table. If it is specified (not zero) but not exists then your UPDATE won't update anything. Assuming that id is autoincremented primary key of the table I recommend to use
CREATE DEFINER=`root`#`localhost:3307`
PROCEDURE `testproc` (IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
INSERT INTO testproc (id, name)
VALUES (idproc, nameproc)
ON DUPLICATE KEY
UPDATE name = VALUES(name);
SELECT * FROM testproc;
END
If specified idproc value exists in id column the row will be updated, if not then the new row will be inserted.
Additionally - I recommend you to provide NULL value instead of zero when you want to insert new row with specified nameproc value. NULL always cause autoincremented primary key generation whereas zero needs in specific server option setting.

user defined function in insert command

I created a function:
DELIMITER $$
DROP FUNCTION IF EXISTS `heena`.`customer_id`$$
CREATE DEFINER=`root`#`localhost` FUNCTION `heena`.`customer_id`(
a varchar(20),
b varchar(20)
) RETURNS varchar(50) CHARSET latin1
DETERMINISTIC
BEGIN
RETURN CONCAT(
(select ((id), 0) + 1
from heenaj),
substring(a,1,2),
substring(b,1,2));
END;$$
DELIMITER ;
The code executed fine, but when I'm inserting a value using:
insert into heenaj
(c_id,name,number)
values
(customer_id121("abcd",9868275817),"abcd",9868275817);
It shows an error:
Column 'c_id' cannot be null
There's something wrong with your RETURN.
Maybe you are meaning to do this, although I am only guessing:
RETURN CONCAT(
(select ifnull(max(id), 0) + 1
from heenaj),
substring(a,1,2),
substring(b,1,2));
sqlfiddle
Then, you're calling customerid121() not customer_id(). Could this be typo?
Also, in looking at what you're trying to do: do you want your id as auto_increment and just want to have c_id as the id, concatenated with first 2 characters of name and concatenated with first 2 characters of number?
I suggest another solution. It might be nicer to drop your function and create a TRIGGER for before INSERT, like this:
CREATE TRIGGER set_customer_id BEFORE INSERT on heenaj
FOR EACH ROW
BEGIN
SET NEW.c_id = CONCAT((SELECT IFNULL(MAX(id),0)+1 FROM heenaj),SUBSTRING(NEW.name,1,2),SUBSTRING(NEW.number,1,2));
END/
This way, when you insert you can just ignore c_id and insert like this:
insert into heenaj(name,number)
values ("abcd",9868);
The trigger will handle the setting of c_id for you.
sqlfiddle for TRIGGER
P.S. To create the trigger (in the sqlfiddle), I selected / as my delimiter. You might change that / to $$, since you're setting delimiter as $$.

Postgres JSON Array data processing

Have a table "json_test" and inserted the following record:
create table json_test ( v json);
insert into json_test values ('{"facilityId": ["20","30","40","50","51"]}')
SELECT trim(json_array_elements_text(v->'facilityId') ) from json_test
The above select lists the facility ID as individual rows.
I need the same rows in a Postgres function to insert the record into another table. I wrote the following code to return i. The output of the v_status when checked is (20,,,,,,,,,,,,). I need to get just 20, but I am unable to get that.
for i in SELECT json_array_elements_text(v->'facilityId') from json_test
loop
v_status:= i;
end loop;
You have not specified entire function definition in your question.
Assuming you have DDL:
CREATE TABLE json_test(
id SERIAL PRIMARY KEY,
v JSON
);
INSERT INTO json_test(v) VALUES
('{"facilityId": ["20","30","40","50","51"]}'::JSON);
You can check full PL/pgSQL guide as a reference, but your function may be defined as the following:
CREATE OR REPLACE FUNCTION get_facility_ids(rid INTEGER)
RETURNS SETOF INTEGER AS $$
DECLARE
t TEXT;
BEGIN
FOR t IN SELECT json_array_elements_text(v->'facilityId')
FROM json_test WHERE id = rid
LOOP
RETURN NEXT t;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT get_facility_ids(1) AS facultyId;
Just for your information, you can INSERT records from SELECT statements. Check the documentation.

mysql how to update a column of all rows and for each row a different column value

Here is a sample table:
id name code
----------------
1 n1
2 n2
3 n3
I want to update the code column of every row with different values, so for row of id 1 i want to add this value for code 'zb6DXBfJ', and for row id 2 'NV6Nx4St', and for row id 3 this value for code column 'q23ZMACc'. So my final table should look like this:
id name code
----------------
1 n1 zb6DXBfJ
2 n2 NV6Nx4St
3 n3 q23ZMACc
UPDATE TableName
SET Code = CASE
WHEN id = 1 THEN 'zb6DXBfJ'
WHEN id = 2 THEN 'NV6Nx4St'
WHEN id = 3 THEN 'q23ZMACc'
END;
Try this
UPDATE Table_Name WHERE id = desired_id SET code = desired_code;
Of course, you'll need to substitute Table_Name, desired_id, and desired_code as required.
Depending on where your codes come from, you can try one of the folowing:
If your codes came from another table, you can create a procedure that will "match" each line of the two table in order to update the codes. Here's an example :
First create the tables (the one you already have, and the one with the codes)
CREATE TABLE table1 (
id INT(6) UNSIGNED PRIMARY KEY,
name VARCHAR(300) NOT NULL,
code VARCHAR(300));
CREATE TABLE table2 (
id INT(6) UNSIGNED PRIMARY KEY,
code VARCHAR(300) NOT NULL);
INSERT INTO table1 (id, name) VALUES
(1, 'n1'),
(2, 'n2'),
(3, 'n3');
INSERT INTO table2 (id, code) VALUES
(1, 'zb6DXBfJ'),
(2, 'NV6Nx4St'),
(3, 'q23ZMACc');
Then create the actual procedure
delimiter //
CREATE PROCEDURE assign_strings()
BEGIN
DECLARE _id INT DEFAULT 0;
DECLARE str VARCHAR(300);
DECLARE cur CURSOR FOR SELECT id FROM table1;
open cur;
myloop:LOOP
fetch cur into _id;
SELECT code INTO str FROM table2 WHERE id = _id;
UPDATE table1 SET code = str WHERE id = _id;
end loop myloop;
close cur;
END //
delimiter ;
You can now call the procedure
CALL assign_strings();
Note that I don't know your logic to retrieve these code. Here I just assume table2.id has the code for table1.id. Its a little dumb but your logic may be more complicated.
If your codes are just random strings (non-unique) you can just use a function instead of a procedure like this :
DELIMITER //
CREATE FUNCTION get_random_string()
RETURNS VARCHAR(300)
BEGIN
RETURN 'Your_random_string';
END//
DELIMITER ;
Note that you'll need to implement your own random string strategy. You can use something like a MD5 function with a random number and substrings... whatever you need.
You can now call this function directly in an update statement like so :
UPDATE table1 set code = get_random_string();
Hope it gets you started.

How to return a table, rows or record from a function in PostgreSQL 9?

I have a table called person which has id,name,status and I want to return rows as a result of a function with 1 parameter (name).
Can anyone help me? Please make it easy, because im very noob in PostgreSQL.
This is my code from a normal function
create or replace function fn_list(vname varchar) returns void as $$
begin
SELECT id,name,status from usuario WHERE name= vname;
end;
$$ language plpgsql;
I know I'm returning a void function but how can I do if I want a list of rows?
I know that pipelined returns in Oracle does this, so I used that to find 'RETURN NEXT' from plpgsql:
http://www.postgresql.org/message-id/007b01c6dc31$ae395920$0a00a8c0#trivadis.com
Also on grokbase:
http://grokbase.com/t/postgresql/pgsql-performance/069kcttrfr/pipelined-functions-in-postgres
(Edit to add official documentation): http://www.postgresql.org/docs/9.2/static/plpgsql-control-structures.html
Killer, I will have to make use of this myself.
Editing one more time to add in some demo code (directly from postgresql.org documentation):
CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');
CREATE OR REPLACE FUNCTION getAllFoo() RETURNS SETOF foo AS
$BODY$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN SELECT * FROM foo
WHERE fooid > 0
LOOP
-- can do some processing here
RETURN NEXT r; -- return current row of SELECT
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;
SELECT * FROM getallfoo();
Using a loop to return the result of a query is slow and inefficient. The overhead of PL/pgSQL is not even required for this.
The best solution is:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
SELECT id,name,status
from usuario
WHERE name= vname;
$$ language sql;
If PL/pgSQL is needed because some other procedural code needs to run before the query, then return query should be used instead of a loop:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
begin
-- do some work....
return query
SELECT id,name,status
from usuario
WHERE name= vname;
end;
$$ language plpgsql;
Then call it using:
select *
from fn_list('Arthur');
Many answers here omit important parts of using functions. Here's an updated way of using functions in postgres (including declaration, variables, args, return values, and running). Below is an over-baked example of updating the tweet on the bottom right "blurb" with "hello world".
id (serial)
pub_id (text)
tweet (text)
1
abc
hello world
2
def
blurb
-- Optional drop if replace fails below.
drop function if exists sync_tweets(text, text);
create or replace function sync_tweets(
src_pub_id text, -- function arguments
dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
src_id int; -- temp function variables (not args)
dest_id int;
src_tweet text;
begin
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
return query -- i.e. rows, return 0 with return int above works too
select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13
-- Run it!
select * from sync_tweets('abc', 'def');
/*
Outputs
__________________________________________________
| id (serial) | pub_id (text) | tweet (text) |
|---------------|-----------------|----------------|
| 1 | abc | hello world |
| 2 | def | blurb |
--------------------------------------------------
*/