POSTGRES: problems on functions - function

So, Im trying to work on a simple book loans system and Im having problems on creating and using a function.
I have a Loans 'Table', Copies 'Table' and Available 'View'.
"Available View" looks like this:
book_id | available_copies
---------+------------------
BI6 | 1
wherein 'available_copies' column is
COUNT(copy_id) AS available_copies
FROM copies
WHERE copy_id NOT IN (SELECT copy_id FROM loans)
This is my "Copies Table"
copy_id | book_id | copy_no | copy_code
---------+---------+---------+-----------
CI8 | BI6 | 8 | CI
CI9 | BI6 | 9 | CI
CI7 | BI7 | 7 | CI
CI10 | BI7 | 10 | CI
and this is my "Loans Table"
loan_id | copy_id | user_id | borrow_date | due_date | loan_no | loan_code
---------+---------+---------+-------------+------------+---------+-----------
LI10 | CI10 | UI4 | 2013-05-21 | 2013-05-26 | 10 | LI
LI11 | CI8 | UI4 | 2013-05-21 | 2013-05-26 | 11 | LI
LI12 | CI7 | UI4 | 2013-05-22 | 2013-05-27 | 12 | LI
What i really wanted to do is.. if the available_copies is 0 (like in the "available view" above, BI7 is not in the Available View anymore because all copies where already borrowed) postgres will prompt something that you cannot borrow books in Loans anymore since the book is already out of copies.
Im kinda new to plpgsql. Please help. :(

I don't know what Pg version you has, but probably some older. I see lot of bugs in your example - so I don't believe it was accepted by postgres
CREATE OR REPLACE FUNCTION try(copyID TEXT)
RETURNS BOOLEAN AS $$
BEGIN
SELECT available_copies FROM available -- missing INTO ???
-- Undeclared variable "available_copies" and probably
-- collision with column named "available_copies"
IF available_copies > 0 THEN
INSERT INTO loans(copy_id) VALUES(copyID);
RETURN BOOLEAN; --- RETURN true or false, but BOOLEAN??
ELSE
RETURN BOOLEAN;
END IF;
END;
$$ LANGUAGE plpgsql;

Example of PL/SQL function:
CREATE OR REPLACE FUNCTION try(copyID TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN NOT EXISTS (SELECT l.copy_id FROM loans l WHERE l.copy_id = copyID);
END;
$$ LANGUAGE plpgsql;
It will RETURN TRUE if the record with copyID NOT EXISTS in loans;
Same function as SQL function:
CREATE OR REPLACE FUNCTION try(copyID TEXT)
RETURNS BOOLEAN AS $$
SELECT NOT EXISTS (SELECT l.copy_id FROM loans l WHERE l.copy_id = copyID)
$$ LANGUAGE SQL;

Related

How to make a Stored Procedure select everything if one of the Arguments that are passed is null?

I'm working on this project that uses databases and I've created a table in MySQL tba_instruments. It has both the ID and the Name of each instrument. It's something like this:
| INSTRUMENT_ID | INSTRUMENT_NM |
| -------- | -------- |
| 1 | Violin |
| 2 | Cello |
| 3 | Flute |
| 4 | Trumpet |
In order to get the instruments by passing a certain filter, I wrote this Stored Procedure
DELIMITER //
CREATE PROCEDURE GetInstruments_ByFilter(
IN instrument_id INT,
IN instrument_name VARCHAR(255)
)
BEGIN
SELECT * FROM tba_instrument
WHERE ID_INSTRUMENT = instrument_id
AND INSTRUMENT_NM = instrument_name;
END //
DELIMITER ;
CALL GetInstruments_ByFilter(1, 'Violin');
RESULT:
| INSTRUMENT_ID | INSTRUMENT_NM |
| 1 | Violin |
It works just fine if the Paremeters passed match what's in the table. But I want it to behave differently if one the Paremeters is null, e.g:
CALL GetInstruments_ByFilter('Violin');
It prints an error message because one Argument is missing. I'd like that it would then just show the entire table instead of an error message. Is there a way to do that in MySQL?
What I thought so far was just to create a different Stored Procedure for this particular case, but it doesn't look like it's the best solution.
You can take advantage of the IFNULL-function.
DELIMITER //
CREATE PROCEDURE GetInstruments_ByFilter(
instrument_id INT,
instrument_name VARCHAR(255)
)
BEGIN
SELECT *
FROM tba_instrument
WHERE ID_INSTRUMENT = IFNULL(instrument_id, ID_INSTRUMENT)
AND INSTRUMENT_NM = IFNULL(instrument_name, INSTRUMENT_NM);
END //
DELIMITER ;
CALL GetInstruments_ByFilter(1, 'Violin');
CALL GetInstruments_ByFilter(1, NULL);
CALL GetInstruments_ByFilter(NULL, 'Violin');

User defined function

For some reason, I'm having a lot of troubles with this.
Here is the question:
Write a user-defined sql function named LastNameFirst that concatenates the employee’s LastName and FirstName into a single value named FullName, and displays, in order, the LastName, a comma, a space, and the FirstName (hint: Smith and Steve would be combined to read Smith, Steve). There are many ways to do this without writing a user defined function, but the purpose of this exercise is to write a solution that uses an sql user-defined function.
Here is what I have:
CREATE FUNCTION LastNameFirst
(LastName varchar(50), FirstName varchar(50))
returns varchar(110)
begin
declare Fullname varchar(110);
select CONCAT(LastName, ', ', FirstName)
from prob5
return Fullname;
I'm getting errors all over this thing.. and I don't understand why. Any help would be greatly appreciated.
Irrespective of whether you want a function or a procedure the normal syntax rules for mysql apply - always terminate a statement with ; every begin must have an end etc. A function would look like this
drop function if exists lastnamefirst;
delimiter $$
CREATE FUNCTION LastNameFirst (LastName varchar(50), FirstName varchar(50)) returns varchar(110)
begin
declare Fullname varchar(110);
set fullname = CONCAT(LastName, ', ', FirstName) ;
return Fullname;
end $$
delimiter ;
Note if you are doing this purely in mysql your need to set the delimiter before you create and reset it after.
So given
+----+----------+-----------+
| id | username | photo |
+----+----------+-----------+
| 1 | John | john.png |
| 2 | Jane | jane.png |
| 3 | Ali | |
| 6 | Bruce | bruce.png |
| 7 | Martha | |
+----+----------+-----------+
5 rows in set (0.00 sec)
The function would be invoked like this
select id,username, lastnamefirst(username,ifnull(photo,''))
from users limit 5;
and the result looks like this
+----+----------+------------------------------------------+
| id | username | lastnamefirst(username,ifnull(photo,'')) |
+----+----------+------------------------------------------+
| 1 | John | John, john.png |
| 2 | Jane | Jane, jane.png |
| 3 | Ali | Ali, |
| 6 | Bruce | Bruce, bruce.png |
| 7 | Martha | Martha, |
+----+----------+------------------------------------------+
5 rows in set (0.03 sec)
Beware of nulls in your data and code for them.
Change your function to this -
CREATE FUNCTION LastNameFirst (LastName varchar(50), FirstName varchar(50)) returns varchar(110)
return concat(LastName, ", ", FirstName);
And then you can use it like this -
select LastNameFirst(LastName, FirstName) from prob5;

MySQL trigger to update a column by reordering its values

Sorry if the title is miss-leading, I couldn't come up with a better one that is related to my issue.
I've been trying to solve this for a while now, and I couldn't find the solution.
I have a table categories:
+----+--------+----------+
| ID | Name | Position |
+----+--------+----------+
| 1 | Dogs | 4 |
| 2 | Cats | 3 |
| 3 | Birds | 10 |
| 4 | Others | 2 |
+----+--------+----------+
I need to keep the Position column in order, in a way not to miss an values as well, so the final table should look like:
+----+--------+----------+
| ID | Name | Position |
+----+--------+----------+
| 1 | Dogs | 3 |
| 2 | Cats | 2 |
| 3 | Birds | 4 |
| 4 | Others | 1 |
+----+--------+----------+
What I tried doing, is creating a trigger on UPDATE and on INSERT that would try to prevent this. The trigger I created ( same one before INSERT) :
DELIMITER //
CREATE TRIGGER sortPostions BEFORE UPDATE ON categories
FOR EACH ROW BEGIN
SET #max_pos = 0;
SET #min_pos = 0;
SET #max_ID = 0;
SET #min_ID = 0;
SELECT position, id INTO #max_pos,#max_ID FROM categories WHERE position = ( SELECT MAX(position) FROM categories);
SELECT position, id INTO #min_pos,#min_ID FROM categories WHERE position = ( SELECT MIN(position) FROM categories);
IF NEW.position >= #max_pos AND NEW.id != #max_ID THEN
SET NEW.position = #max_pos + 1;
END IF;
IF NEW.position <= #min_pos AND NEW.id != #min_ID THEN
SET NEW.position = #min_pos - 1;
END IF;
IF NEW.position < 0 THEN
SET NEW.position = 0;
END IF;
END//
DELIMITER ;
But unfortunately it's not working as intended. It's not fixing missing values and I think this is not a perfect solution.
I went ahead and created a procedure:
BEGIN
SET #n = 0;
UPDATE categories
SET position = #n:=#n+1
ORDER BY position ASC;
END
But I wasn't able to call this procedure from a trigger, as it seems that MySQL doesn't allow that. I get the following error:
#1442 - Can't update table 'categories' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
mysql -V output:
mysql Ver 14.14 Distrib 5.5.57, for debian-linux-gnu (x86_64) using readline 6.3
What's the perfect solution to solve this problem ?
Thanks a lot!
You can't do this in a trigger. MySQL does not allow you to do an INSERT/UPDATE/DELETE in a trigger (or in a procedure called by the trigger) against the same table for which the trigger was spawned.
The reason is that it can result in infinite loops (your update spawns the trigger, which updates the table, which spawns the trigger again, which updates the table again...). Also because it can create lock conflicts if more than one of these requests happens concurrently.
You should do this in application code if you must renumber the position of the rows. Do it with a separate statement after your initial query has completed.
Another option is don't worry about making the positions consecutive. Just make sure they are in the right order. Then when you query the table, generate row numbers on demand.
SELECT (#n:=#n+1) AS row_num, c.*
FROM (SELECT #n:=0 AS n) AS _init
CROSS JOIN categories AS c
ORDER BY c.position ASC;
+---------+----+--------+----------+
| row_num | id | name | position |
+---------+----+--------+----------+
| 1 | 4 | Others | 2 |
| 2 | 2 | Cats | 3 |
| 3 | 1 | Dogs | 4 |
| 4 | 3 | Birds | 10 |
+---------+----+--------+----------+
In MySQL 8.0, you'll be able to do this with more standard syntax using ROW_NUMBER().
SELECT ROW_NUMBER() OVER w AS row_num, c.*
FROM categories AS c
WINDOW w AS (ORDER BY c.position ASC);
Gives the same output as the query using the user-variable.

SQL query to create function counting total cells with selector

I am using phpMyAdmin and am trying to create a SQL query that creates a function.
DELIMITER $$
CREATE FUNCTION get_totalLanguages(_CountryCode CHAR(3)) RETURNS INT
BEGIN
DECLARE totalLang INT;
SET totalLang = SELECT count(DISTINCT Language) FROM countrylanguage WHERE (CountryCode == _CountryCode);
RETURN totalLang;
END$$
DELIMITER
The table looks like this:
+=============+==========+
| countrylanguage |
+=============+==========+
| CountryCode | Language |
+=============+==========+
| USA | English|
+-------------+----------+
| USA | Spanish|
+-------------+----------+
| USA | French|
+-------------+----------+
| MEX | Spanish|
+-------------+----------+
| MEX | English|
+-------------+----------+
| GER | German|
+-------------+----------+
| GER | English|
+=============+==========+
I want to be able to call the function with a countrycode argument. The function would then count how many separate languages exist with that country code, and then return the count as an integer. Currently, this query doesn't give me any errors, but doesn't do anything.
get_totalLanguages('MEX') should return 2.
get_totalLanguages('USA') should return 3.
etc.
Thanks!
Try selecting the function call's return value
DELIMITER $$
CREATE FUNCTION get_totalLanguages(_CountryCode CHAR(3))
RETURNS INT
BEGIN
DECLARE totalLang INT;
SELECT count(DISTINCT Language) into totalLang FROM countrylanguage WHERE CountryCode = _CountryCode);
RETURN totalLang;
END$$
DELIMITER
SELECT get_totalLanguages('MEX');

MySQL: Split comma separated list into multiple rows

I have an unnormalized table with a column containing a comma separated list that is a foreign key to another table:
+----------+-------------+ +--------------+-------+
| part_id | material | | material_id | name |
+----------+-------------+ +--------------+-------+
| 339 | 1.2mm;1.6mm | | 1 | 1.2mm |
| 970 | 1.6mm | | 2 | 1.6mm |
+----------+-------------+ +--------------+-------+
I want to read this data into a search engine that offers no procedural language.
So is there a way to either make a join on this column or run a query on this data that inserts appropriate entries into a new table?
The resulting data should look like this:
+---------+-------------+
| part_id | material_id |
+---------+-------------+
| 339 | 1 |
| 339 | 2 |
| 970 | 2 |
+---------+-------------+
I could think of a solution if the DBMS supported functions returning a table but MySQL apparently doesn't.
In MySQL this can be achieved as below
SELECT id, length FROM vehicles WHERE id IN ( 117, 148, 126)
+---------------+
| id | length |
+---------------+
| 117 | 25 |
| 126 | 8 |
| 148 | 10 |
+---------------+
SELECT id,vehicle_ids FROM load_plan_configs WHERE load_plan_configs.id =42
+---------------------+
| id | vehicle_ids |
+---------------------+
| 42 | 117, 148, 126 |
+---------------------+
Now to get the length of comma separated vehicle_ids use below query
Output
SELECT length
FROM vehicles, load_plan_configs
WHERE load_plan_configs.id = 42 AND FIND_IN_SET(
vehicles.id, load_plan_configs.vehicle_ids
)
+---------+
| length |
+---------+
| 25 |
| 8 |
| 10 |
+---------+
For more info visit http://amitbrothers.blogspot.in/2014/03/mysql-split-comma-separated-list-into.html
I've answered two similar questions in as many days but not had any responses so I guess people are put off by the use of the cursor but as it should be a one off process I personally dont think that matters.
As you stated MySQL doesnt support table return types yet so you have little option other than to loop the table and parse the material csv string and generate the appropriate rows for part and material.
The following posts may prove of interest:
split keywords for post php mysql
MySQL procedure to load data from staging table to other tables. Need to split up multivalue field in the process
Rgds
MySQL does not have temporary table reuse and functions do not return rows.
I can't find anything in Stack Overflow to convert string of csv integers into rows so I wrote my own in MySQL.
DELIMITER $$
DROP PROCEDURE IF EXISTS str_split $$
CREATE PROCEDURE str_split(IN str VARCHAR(4000),IN delim varchar(1))
begin
DECLARE delimIdx int default 0;
DECLARE charIdx int default 1;
DECLARE rest_str varchar(4000) default '';
DECLARE store_str varchar(4000) default '';
create TEMPORARY table IF NOT EXISTS ids as (select parent_item_id from list_field where 1=0);
truncate table ids;
set #rest_str = str;
set #delimIdx = LOCATE(delim,#rest_str);
set #charIdx = 1;
set #store_str = SUBSTRING(#rest_str,#charIdx,#delimIdx-1);
set #rest_str = SUBSTRING(#rest_str from #delimIdx+1);
if length(trim(#store_str)) = 0 then
set #store_str = #rest_str;
end if;
INSERT INTO ids
SELECT (#store_str + 0);
WHILE #delimIdx <> 0 DO
set #delimIdx = LOCATE(delim,#rest_str);
set #charIdx = 1;
set #store_str = SUBSTRING(#rest_str,#charIdx,#delimIdx-1);
set #rest_str = SUBSTRING(#rest_str from #delimIdx+1);
select #store_str;
if length(trim(#store_str)) = 0 then
set #store_str = #rest_str;
end if;
INSERT INTO ids(parent_item_id)
SELECT (#store_str + 0);
END WHILE;
select parent_item_id from ids;
end$$
DELIMITER ;
call str_split('1,2,10,13,14',',')
You will also need to cast to different types if you are not using ints.
You can also use REGEXP
SET #materialids=SELECT material FROM parttable where part_id=1;
SELECT * FROM material_id WHERE REGEXP CONCAT('^',#materialids,'$');
This will help if you want to get just one part. Not the whole table of course