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');
Related
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');
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;
I have this MySQL procedure where I used a cursor to:
CREATE DEFINER=`root`#`localhost` PROCEDURE `tax_to_salary`()
BEGIN
DECLARE basic_salary INTEGER;
DECLARE new_salary INTEGER;
DECLARE done INTEGER;
declare count INTEGER;
DECLARE counter INTEGER default 0;
DECLARE cur1 CURSOR FOR SELECT salary FROM employee;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SELECT count(id) INTO count FROM employee;
SET #counter:=0;
OPEN cur1;
l1:LOOP
FETCH cur1 INTO basic_salary;
SET #counter:=#counter+1;
IF #counter>count THEN
leave l1;
end if;
IF basic_salary>2500 THEN
SET #new_salary := 500;
SET #basic_salary := #basic_salary - #new_salary;
else
SET #new_salary := 200;
SET #basic_salary := #basic_salary - #new_salary;
END IF;
SELECT emp_name, salary, basic_salary AS 'Salary after taxes' FROM employee;
END LOOP;
END
And I got this result:
But my procedure should remove 500 from all salaries over 2500 and remove 200 from salaries less than 2500.
I tried to put the final SELECT query inside the loop but I get 5 tabs and every tab contain the same of image below.
Schema
create table employee
( id int auto_increment primary key,
emp_name varchar(100) not null,
salary int not null
);
insert employee (emp_name,salary) values
('John',4400),
('Sarah',2700),
('Peter',2150),
('Ali',2650),
('Ashley',2650);
Note your language was greater than 2500 and also you said less than 2500. yet it has no condition for salary equaling 2500 exactly. So the below is one fix to that concept (otherwise there is no reduction).
Case when
best for many conditions, not that yours has it
select emp_name,salary,
CASE when salary>=2500 then salary-500
ELSE
salary-200
END as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
If
for simple conditions like yours
select emp_name,salary,
if(salary>=2500,salary-500,salary-200) as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
There is no reason to be using a row-by-row cursor the way you are. That is what people sometimes do just starting out with sql. Not only are they slow, often unbearably slow, but they keep you from harnessing the power of relations that make sql shine.
Said another way, you are trying to write procedural code and getting in the middle of it all by helping the sql engine figure it out with that mindset. It doesn't want it that way for optimization. You can, but you will slow it down horribly.
The reason you are getting multiple tabs as you say is that with your strategy, even if it worked well number-wise, each select statement returns a result set. And by going the dangerous cursor route, you returned five of them.
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;
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