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;
Related
I just have a quick question about how to do something, but also about whether or not this is even possible in the first place. I've tried to find an answer online, but got no success.
I'll create a very simplified scenario that's not necessarily what I want to do precisely, but it's a pretty good example. Let's say that I have this table
Users
----------------------------------------------
| ID | FirstName | LastName | Username | ... |
|----|-----------|----------|----------|-----|
| 1| John | Doe | jdoe | ... |
| 2| James | Smith | jsmith | ... |
| 3| Jane | Fisher | jfisher | ... |
| x| ... | ... | ... | ... |
----------------------------------------------
Let's say that I want to create a query to show only the FirstName and LastName, but as a full name.
I could do:
SELECT Users.FirstName, Users.LastName
FROM Users
WHERE ID > 2
What I want however is something that would give me FirstName and LastName as one column that I'd present like this:
(Users.FirstName, Users.LastName) AS 'Full Name'
Therefore, I'd get something like:
Users
---------------------------------------
| ID | Full Name | Username | ... |
|----|---------------|----------|-----|
| 1| John Doe | jdoe | ... |
| 2| James Smith | jsmith | ... |
| 3| Jane Fisher | jfisher | ... |
| x| ... | ... | ... |
---------------------------------------
Of course, in my real query, I'd be joining a bit more than just 2 columns, but I think you get the point. So is this possible? If so, how can I do it.
SELECT first_name + ' ' + last_name as full_name from users
in Oracle
SELECT first_name || ' ' || last_name full_name from users
edit: maybe if you are having data type issues - you could force it to think it's a string - something like
SELECT '' + first_name + ' ' + last_name from users
You are using MySQL. While the standard string concatenation operator is ||, it doesn't work with MySQL. Use the function CONCAT_WS:
select concat_ws(' ', firstname, lastname)
from mytable;
(You could also use the function CONCAT, but I prefer CONCAT_WS in this situation.)
Here is the docs: https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_concat-ws
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');
Suppose I have a MySQL table that defines a collection of things, each of which is associated with either 1 or 2 owners. For example:
CREATE TABLE thing (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT
, name CHAR(10)
, first_owner INT UNSIGNED NOT NULL
, second_owner INT UNSIGNED DEFAULT NULL
);
+----+------------+-------------+--------------+
| id | name | first_owner | second_owner |
+----+------------+-------------+--------------+
| 1 | skateboard | Joe | NULL |
| 2 | flashlight | Joe | NULL |
| 3 | drill | Joe | Erica |
| 4 | computer | Erica | NULL |
| 5 | textbook | Diane | NULL |
| 6 | cell phone | Amy | Diane |
| 7 | piano | Paul | Amy |
+----+------------+-------------+--------------+
Each distinct owner is a node of a graph, and two owners in the same row constitute an edge between their nodes. A graph drawn from the above example rows looks like this:
In this example, there are two components: Joe and Erica are one; Diane, Paul and Amy are the other.
I want to identify these components in my table, so I add another column:
ALTER TABLE thing ADD COLUMN `group` INT UNSIGNED;
How could I write an UPDATE statement that would populate this new column by uniquely identifying the connected component to which the row belongs? Here's an example of an acceptable result for the above example rows:
+----+------------+-------------+--------------+-------+
| id | name | first_owner | second_owner | group |
+----+------------+-------------+--------------+-------+
| 1 | skateboard | Joe | NULL | 1 |
| 2 | flashlight | Joe | NULL | 1 |
| 3 | drill | Joe | Erica | 1 |
| 4 | computer | Erica | NULL | 1 |
| 5 | textbook | Diane | NULL | 2 |
| 6 | cell phone | Amy | Diane | 2 |
| 7 | piano | Paul | Amy | 2 |
+----+------------+-------------+--------------+-------+
I could do this with a stored procedure, but my actual scenario involves more tables and millions of rows, so I'm hoping there's a clever way to do this without looping through cursors for a week.
This is a simplified example for the purpose of illustrating the problem. Each component is supposed to represent a "household" and most will have only 1 or 2 nodes, but those with more nodes are especially important. There isn't necessarily any strict upper limit to the size of a household.
You can consider this method of creating hierarchical queries in mysql
CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _next INT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET #id = NULL;
SET _parent = #id;
SET _id = -1;
IF #id IS NULL THEN
RETURN NULL;
END IF;
LOOP
SELECT MIN(id)
INTO #id
FROM t_hierarchy
WHERE parent = _parent
AND id > _id;
IF #id IS NOT NULL OR _parent = #start_with THEN
SET #level = #level + 1;
RETURN #id;
END IF;
SET #level := #level - 1;
SELECT id, parent
INTO _id, _parent
FROM t_hierarchy
WHERE id = _parent;
END LOOP;
END
Also, a very good article on this topic Adjacency list vs. nested sets: MySQL
A very good answer to a related question
"What is the most efficient/elegant way to parse a flat table into a
tree?"
There are several ways to store tree-structured data in a relational
database. What you show in your example uses two methods:
Adjacency List (the "parent" column) and
Path Enumeration (the dotted-numbers in your name column).
Another solution is called Nested Sets, and it can be stored in the
same table too. Read "Trees and Hierarchies in SQL for Smarties" by
Joe Celko for a lot more information on these designs.
I usually prefer a design called Closure Table (aka "Adjacency
Relation") for storing tree-structured data. It requires another
table, but then querying trees is pretty easy.
Please have a look at original question for reference.
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 a table like:
+------+---------+-
| id | parent |
+------+---------+
| 2043 | NULL |
| 2044 | 2043 |
| 2045 | 2043 |
| 2049 | 2043 |
| 2047 | NULL |
| 2048 | 2047 |
| 2049 | 2047 |
+------+---------+
which shows a simple, 2-level "parent-child"-corelation. How can I ORDER BY an SELECT-statement to get the order like in the list above, which means: 1st parent, childs of 1st parent, 2nd parent, childs of 2nd parent and so on (if I have that, I can add the ORDER BYs for the children... I hope). Is it possible withoug adding a sort-field?
Including sorting children by id:
ORDER BY COALESCE(parent, id), parent IS NOT NULL, id
SQL Fiddle example
Explanation:
COALESCE(parent, id): First sort by (effectively grouping together) the parent's id.
parent IS NOT NULL: Put the parent row on top of the group
id: Finally sort all the children (same parent, and parent is not null)
If your table uses 0 instead of null to indicate an entry with no parent:
id | parent
-------------
1233 | 0
1234 | 1233
1235 | 0
1236 | 1233
1237 | 1235
Use greatest instead of coalesce and check the value does not equal 0:
ORDER BY GREATEST(parent, id), parent != 0, id
The solution above didn't work for me, my table used 0 instead of NULL.
I found this other solution: you create a column with the concatened parent id and child id in your query and you can sort the result by it .
SELECT CONCAT(IF(parent = 0,'',CONCAT('/',parent)),'/',id) AS gen_order
FROM table
ORDER BY gen_order
This question still shows as one of the first search results. So I would like to share a my solution and hope it will help more people out. This will also work when you have a table with many levels of parent and child relations. Although it is quite a slow solution. The top level has NULL as parent.
+---------+---------+
| id | parent |
+---------+---------+
| 1 | NULL |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
+---------+---------+
In my approach I will use a procedure that will recursively call itself and keep prepending the path with the parent of the requested id until it reaches the NULL parent.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `PATH`(IN `input` INT, OUT `output` VARCHAR(128))
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _path VARCHAR(128);
SET `max_sp_recursion_depth` = 50;
SELECT `id`, `parent`
INTO _id, _parent
FROM `database`.`table`
WHERE `table`.`id` = `input`;
IF _parent IS NULL THEN
SET _path = _id;
ELSE
CALL `PATH`(_parent, _path);
SELECT CONCAT(_path, '-', _id) INTO _path;
END IF;
SELECT _path INTO `output`;
END $$
DELIMITER ;
To use the results in an ORDER BY clause you will need a FUNCTION too that wraps the results of the PROCEDURE.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `GETPATH`(`input` INT) RETURNS VARCHAR(128)
BEGIN
CALL `PATH`(`input`, #path);
RETURN #path;
END $$
DELIMITER ;
Now we can use the recursive path to sort the order of the table. On a table with 10000 rows it takes just over a second on my workstation.
SELECT `id`, `parent`, GETPATH(`id`) `path` FROM `database`.`table` ORDER BY `GETPATH`(`id`);
Example output:
+---------+---------+---------------+
| id | parent | path |
+---------+---------+---------------+
| 1 | NULL | 1 |
| 10 | 1 | 1-10 |
| 300 | 10 | 1-10-300 |
| 301 | 300 | 1-10-300-301 |
| 302 | 300 | 1-10-300-302 |
+---------+---------+---------------+
5 rows in set (1,39 sec)