have a question for you guys,
trying to make a procedure for my mysql table but I need some assistance ...
IM completely block ...
I need to create a procedure that will show the parents name in my table but the table show parents id
ex.
(DELIMITER //
CREATE PROCEDURE fetch_animal_parents (IN animal_id INT, OUT animal_name VARCHAR(10))
BEGIN
DECLARE animal_mom INT DEFAULT 0 ;
DECLARE animal_dad INT DEFAULT 0 ;
DECLARE animal_name_mom VARCHAR(10) ;
DECLARE animal_name_dad VARCHAR(10) ;
SELECT name INTO animal_name, (SELECT name FROM animal WHERE id = child.mother_id) INTO animal_name_mom,
(SELECT name FROM animal WHERE id = child.father_id) INTO animal_name_dad
FROM animal AS child ;
END //)
What im doing wrong ....
................................................................................................
Any input ...
1) why do you select mom/dad's name when you are not using them anywhere?
2) I imagine your procedure should take an child animal id as input and give mom & dad's name as output(that's what you procedure name suggest). In that case you need to either have 2 output value or you need to concatenate those names into 1 variable and return them.
3) #VMai suggested a 2 join format which I would agree. The query will be something like..
SELECT mom.name,dad.name INTO animal_name_mom, animal_name_dad
FROM (select mother_id,father_id from animal where id = <precedure_input>) AS `child`
INNER JOIN (select id,name from animal) AS `mom` ON (mom.id=child.mother_id)
INNER JOIN (select id,name from animal) AS `dad` ON (dad.id=child.father_id)
I see that you have as least tried something on your own (thou very confused). I'd suggest you to start with learning some basic syntax/keyword/functions of mysql before trying procedures. Learn to use GROUP BY, variations of JOIN and you could handle a lot of basic querys.
Related
I am working in MySQL Workbench and using the following SQL sample data. https://www.databasestar.com/sample-database-superheroes/
My diagram is attached at the bottom.
How do I create a stored procedure where you input the name of a superhero (e.g. Firebird) and get which specific fire-themed superpower they have?
So far, my code looks like this:
DROP PROCEDURE IF EXISTS Elemental_Heroes;
DELIMITER $$
CREATE PROCEDURE Elemental_Heroes(
IN HeroName VARCHAR(250),
OUT ElementalCat VARCHAR(250))
BEGIN
DECLARE credit DECIMAL DEFAULT 0;
SELECT
s.id,
s.superhero_name,
sp.power_name
FROM superhero s
LEFT JOIN hero_power hp ON s.id = hp.hero_id
LEFT JOIN superpower sp ON hp.power_id = sp.id
WHERE sp.id IN (56,79,90,104,140) AND s.superhero_name = HeroName
GROUP BY s.id, s.superhero_name, sp.power_name;
IF
ELSE
END$$
DELIMITER ;
CALL Elemental_Heroes(Firebird);
Where the IF ELSE part is just a placeholder for the conditional statements I know must eventually be there. The entire elect part (minus the s.superhero_name = HeroName) runs as desired.
I'm also wondering if there's any way for the output to be multiple categories. Because Firebird has Fire Control, Fire Resistance, and Heat Resistance and I would like all three categories to be the output when I call Elemental_Heroes(Firebird).
A sample.
Create source data
CREATE TABLE main
SELECT 1 id, 'Name 1' name UNION SELECT 2, 'Name 2' UNION SELECT 3, 'Name 3';
CREATE TABLE slave
SELECT 1 id, 1 val UNION SELECT 1, 2 UNION SELECT 2, 3 UNION
SELECT 2, 1 UNION SELECT 3, 2 UNION SELECT 3, 3;
Create stored procedure which retrieves names list by provided val value
CREATE PROCEDURE get_id_by_val (IN in_val INT, OUT out_names_list TEXT)
BEGIN
SELECT JSON_ARRAYAGG(name) INTO out_names_list
FROM main
JOIN slave USING (id)
WHERE slave.val = in_val;
END
SP queries the names by the value provided in in_val input parameter and saves found values into out_names_list output parameter
Using SP
CALL get_id_by_val(3, #output);
-- retrieve the list as solid JSON array
SELECT #output;
-- retrieve the same in separate rows
SELECT name FROM JSON_TABLE(#output, '$[*]' COLUMNS (name VARCHAR(255) PATH '$')) jsontable
In CALL we provide criteria value for in_val parameter and use user-defined variable #output as a place where the output will be placed into.
Solid JSON array output
#output
["Name 2", "Name 3"]
Output parsed to separate values
name
Name 2
Name 3
fiddle
Please take a look at the following table:
I am building a search engine which returns card_id values, based on search of category_id and value_id values.
To better explain the search mechanism, imagine that we are trying to find a car (card_id) by supplying information what part (value_id) the car should has in every category (category_id).
In example, we may want to find a car (card_id), where category "Fuel Type" (category_id) has a value "Diesel" (value_id), and category "Gearbox" (category_id) has a value "Manual" (value_id).
My problem is that my knowledge is not sufficient to build a query, which will returns card_ids which contains more than one pair of category_id and value_id.
For example, if I want to search a car with diesel engine, I could build a query like this:
SELECT card_id FROM cars WHERE category_id=1 AND value_id=2
where category_id = 1 is a category "Fuel Type" and value_id = 2 is "Diesel".
My question is, how can I build a query, which will look for more category-value pairs? For example, I want to look for diesel cars with manual gearbox.
Any help will be very appreciated. Thank you in advance.
You can do this using aggregation and a having clause:
SELECT card_id
FROM cars
GROUP BY card_id
HAVING SUM(category_id = 1 AND value_id = 2) > 0 AND
SUM(category_id = 3 and value_id = 43) > 0;
Each condition in the having clause counts the number of rows that match a given condition. You can add as many conditions as you like. The first, for instance, says that there is at least one row where the category is 1 and the value is 2.
SQL Fiddle
Another approach is to create a user defined function that takes a table of attribute/value pairs and returns a table of matching cars. This has the advantage of allowing an arbitrary number of attribute/value pairs without resorting to dynamic SQL.
--Declare a "sample" table for proof of concept, replace this with your real data table
DECLARE #T TABLE(PID int, Attr Int, Val int)
--Populate the data table
INSERT INTO #T(PID , Attr , Val) VALUES (1,1,1), (1,3,5),(1,7,9),(2,1,2),(2,3,5),(2,7,9),(3,1,1),(3,3,5), (3,7,9)
--Declare this as a User Defined Table Type, the function would take this as an input
DECLARE #C TABLE(Attr Int, Val int)
--This would be populated by the code that calls the function
INSERT INTO #C (Attr , Val) VALUES (1,1),(7,9)
--The function (or stored procedure) body begins here
--Get a list of IDs for which there is not a requested attribute that doesn't have a matching value for that ID
SELECT DISTINCT PID
FROM #T as T
WHERE NOT EXISTS (SELECT C.ATTR FROM #C as C
WHERE NOT EXISTS (SELECT * FROM #T as I
WHERE I.Attr = C.Attr and I.Val = C.Val and I.PID = T.PID ))
i'm trying to create an procedure that returns an table with some information of my database, it lists the number of the HOTEL by how many clients used each type of their credit cards on the hotel
Keeping in mind that there is more than 50 hotels and 3 types of credit cards, i want the procedure to run through the data and list then in the table
DELIMITER //
DROP PROCEDURE IF EXISTS `testing` //
CREATE PROCEDURE `testing`(OUT param1 VARCHAR(40))
BEGIN
DECLARE id_cnpjEstabelecimento VARCHAR(40);
DECLARE id_redeCartão VARCHAR(255);
SELECT (cnpjEstabelecimento)
FROM fpcsmovatlantica201308tst04;
SET id_cnpjEstabelecimento := cnpjEstabelecimento;
SELECT (id_redeCartão)
FROM fpcsmovatlantica201308tst04;
SET id_redeCartão := id_redeCartão;
SELECT count(*)
FROM fpcsmovatlantica201308tst04;
WHERE redeCartão like 'id_redeCartão%';
AND cnpjEstabelecimento like 'id_cnpjEstabelecimento%';
END //
DELIMITER ;
An example of an select
SELECT count(*)
FROM fpcsmovatlantica201308tst04
WHERE redeCartão like 'Cielo%'
AND cnpjEstabelecimento like '02223966000466%'
the cnpjEstabelecimento got several values, more than 100+, so it's inviable to make all the selects
I don't even have to use procedures to make it, the final result was
SELECT cnpjEstabelecimento, redeCartão, count(*)
FROM fpcsmovatlantica201308tst04
WHERE redeCartão like 'Cielo%'
GROUP BY cnpjEstabelecimento,redeCartão like 'Cielo%'
ORDER BY cnpjEstabelecimento ASC;
I'm assuming you have one table, which looks kind of like this:
|hotelId|cardType|etc...
I'd go with:
Select hotelId, cardType, count(*)
from myTable
group by hotelId, cardType
I tested it here with the following SQL:
SELECT country, city, count(*)
from customers
group by country, city
ORDER BY Country;
The idea is simple - I have two tables, categories and products.
Categories:
id | parent_id | name | count
1 NULL Literature 6020
2 1 Interesting books 1000
3 1 Horrible books 5000
4 1 Books to burn 20
5 NULL Motorized vehicles 1000
6 5 Cars 999
7 5 Motorbikes 1
...
Products:
id | category_id | name
1 1 Cooking for dummies
2 3 Twilight saga
3 5 My grandpa's car
...
Now while displayed, the parent category contains all the products of all the children categories. Any category may have children categories. The count field in the table structure contains (or at least I want it to contain) count of all products displayed in this particular category. On the front-end, I select all subcategories with a simple recursive function, however I'm not so sure how to do this in a SQL procedure (yes it has to be a SQL procedure).The tables contain about a hundread categories of any kind and there are over 100 000 products.
Any ideas?
Bill Karwin made some nice slides about hierachical data, and the current Adjacency Model certainly as pros, but it's not very suited for this (getting a whole subtree).
For my Adjacency tables, I solve it by storing / caching the path (possibly in a script, or in a 'before update trigger'), on change of parent_id id, a new path-string is created. Your current table would look like this:
id | parent_id | path | name | count
1 NULL 1 Literature 6020
2 1 1:2 Interesting books 1000
3 1 1:3 Horrible books 5000
4 1 1:4 Books to burn 20
5 NULL 5 Motorized vehicles 1000
6 5 5:6 Cars 999
7 5 5:7 Motorbikes 1
(choose any delimiter not found in the id you like)
So, now to get all products from a category + subcategories:
SELECT p.*
FROM categories c_main
JOIN categories c_subs
ON c_subs.id = c_main.id
OR c_subs.path LIKE CONCAT(c_main,':%')
JOIN products p
ON p.category_id = c_subs.id
WHERE c_main.id = <id>
Take a look at this article on managing heirachical trees in MySQL.
It explains the disadvantages to your current method and some more optimal solutions.
See especially the section towards the ended headed 'Aggregate Functions in a Nested Set'.
There's a whole chapter in "SQL Antipatterns Avoiding the Pitfalls of Database Programming" by Bill Karwin about managing hierachical data in SQL.
As you havent accepted an answer yet i thought i'd post my method for handling trees in mysql and php. (single db call to non recursive sproc)
Full script here : http://pastie.org/1252426 or see below...
Hope this helps :)
PHP
<?php
$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
$result = $conn->query(sprintf("call product_hier(%d)", 3));
echo "<table border='1'>
<tr><th>prod_id</th><th>prod_name</th><th>parent_prod_id</th>
<th>parent_prod_name</th><th>depth</th></tr>";
while($row = $result->fetch_assoc()){
echo sprintf("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>",
$row["prod_id"],$row["prod_name"],$row["parent_prod_id"],
$row["parent_prod_name"],$row["depth"]);
}
echo "</table>";
$result->close();
$conn->close();
?>
SQL
drop table if exists product;
create table product
(
prod_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_id smallint unsigned null,
key (parent_id)
)engine = innodb;
insert into product (name, parent_id) values
('Products',null),
('Systems & Bundles',1),
('Components',1),
('Processors',3),
('Motherboards',3),
('AMD',5),
('Intel',5),
('Intel LGA1366',7);
delimiter ;
drop procedure if exists product_hier;
delimiter #
create procedure product_hier
(
in p_prod_id smallint unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_id smallint unsigned,
prod_id smallint unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier select parent_id, prod_id, v_depth from product where prod_id = p_prod_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from product p inner join hier on p.parent_id = hier.prod_id and hier.depth = v_depth) then
insert into hier
select p.parent_id, p.prod_id, v_depth + 1 from product p
inner join tmp on p.parent_id = tmp.prod_id and tmp.depth = v_depth;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
select
p.prod_id,
p.name as prod_name,
b.prod_id as parent_prod_id,
b.name as parent_prod_name,
hier.depth
from
hier
inner join product p on hier.prod_id = p.prod_id
inner join product b on hier.parent_id = b.prod_id
order by
hier.depth, hier.prod_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
call product_hier(3);
call product_hier(5);
What you want is a common table expression. Unfortunately it looks like mysql doesn't support them.
Instead you will probably need to use a loop to keep selecting deeper trees.
I'll try whip up an example.
To clarify, you're looking to be able to call the procedure with an input of say '1' and get back all the sub categories and subsub categories (and so on) with 1 as an eventual root?
like
id parent
1 null
2 1
3 1
4 2
?
Edited:
This is what I came up with, it seems to work.
Unfortunately I don't have mysql, so I had to use sql server. I tried to check everythign to make sure it will work with mysql but there may still be issues.
declare #input int
set #input = 1
--not needed, but informative
declare #depth int
set #depth = 0
--for breaking out of the loop
declare #break int
set #break = 0
--my table '[recursive]' is pretty simple, the results table matches it
declare #results table
(
id int,
parent int,
depth int
)
--Seed the results table with the root node
insert into #results
select id, parent, #depth from [recursive]
where ID = #input
--Loop through, adding notes as we go
set #break = 1
while (#break > 0)
begin
set #depth=#depth+1 --Increase the depth counter each loop
--This checks to see how many rows we are about to add to the table.
--If we don't add any rows, we can stop looping
select #break = count(id) from [recursive]
where parent in
(
select id from #results
)
and id not in --Don't add rows that are already in the results
(
select id from #results
)
--Here we add the rows to the results table
insert into #results
select id, parent, #depth from [recursive]
where parent in
(
select id from #results
)
and id not in --Don't add rows that are already in the results
(
select id from #results
)
end
--Select the results and return
select * from #results
Try to get rid of the hierarchy that is implemented that way. Recursion in stored procedures aren't nice, and for example, on MS SQL they fail after 64th level.
Also, to get for example everything from some category and it's subcategories, you will have to recursively go all the way down, which is impractical for SQL - nevertheless to say slow.
Instead, use this; create category_path field, and make it look like:
category_path name
1/ literature
1/2/ Interesting books
1/3/ Horrible books
1/4/ Books to burn
5/ Motorized vehicles
5/6/ Cars
5/7/ Motorbikes
By using that method, you will be able to SELECT categories and subcategories very fast. Updates will be slow, but I guess that they CAN be slow. Also, you can keep your old child-parent relationship fields, to help you maintain your tree structure.
For example, getting all cars, without any recursion, will be:
SELECT * FROM ttt WHERE category_path LIKE '5/%'
Hello im having a hard time with this stored procedure. im getting the error:
Result consisted of more than one row.
here is my stored procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `dss`.`COSTRET` $$
CREATE DEFINER=`dwadmin`#`192.168.%.%` PROCEDURE `COSTRET`( TDATE DATE)
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ls_id VARCHAR(8);
DECLARE ld_cost DECIMAL(10,4);
DECLARE ld_retail DECIMAL(10,4);
DECLARE cur1 CURSOR FOR SELECT DISTINCT `id` FROM `prod_performance` WHERE `psc_week` = TDATE;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
-- Get the Cost
CREATE TEMPORARY TABLE IF NOT EXISTS `prod_itemcost`
SELECT DISTINCTROW `itemcode` ID, `mlist` COST
FROM (SELECT `itemcode`, `pceffdate`, `mlist`
FROM `purchcost` a
where `pceffdate` = (SELECT MAX(z.`pceffdate`) FROM `purchcost` z WHERE z.`itemcode` = a.`itemcode`
AND z.`pceffdate` <= TDATE)) tb
ORDER BY `itemcode`;
OPEN cur1;
REPEAT
FETCH cur1 INTO ls_id;
IF NOT done THEN
SELECT DISTINCTROW `cost` INTO ld_cost FROM `prod_itemcost` WHERE id = ls_id;
UPDATE LOW_PRIORITY `prod_performance` SET `current_cost` = ld_cost WHERE `psc_week` = TDATE and `id` = ls_id;
END IF;
UNTIL done END REPEAT;
CLOSE cur1;
-- Destroy Temporary Tables
DROP TEMPORARY TABLES IF EXISTS `prod_itemcost`;
END $$
DELIMITER ;
Any solutions and recommendations are much appreciated!
I'd say the problem is here :
SELECT DISTINCTROW `cost` INTO ld_cost FROM `prod_itemcost` WHERE id = ls_id;
and caused by this returning more than one row.
How you solve it depends on your requirements. Does the existence of multiple rows imply the database is in need of some cleaning, for example? Or should you be taking the first value of 'cost', or perhaps the sum of all 'cost' for id = ls_id?
Edit :
Your INTO clause is attempting to write multiple rows to a single variable. Looking at your SQL, I'd say the underlying problem is that your initial query to pull back just the latest cost for each ID is being hamstrung by duplicates of pceffdate. If this is the case, this SQL :
SELECT DISTINCTROW `itemcode` ID, `mlist` COST
FROM (SELECT `itemcode`, `pceffdate`, `mlist`
FROM `purchcost` a
where `pceffdate` = (SELECT MAX(z.`pceffdate`) FROM `purchcost` z WHERE z.`itemcode` = a.`itemcode`
AND z.`pceffdate` <= TDATE)) tb
will return more rows than just this :
SELECT DISTINCTROW `itemcode` ID
FROM (SELECT `itemcode`, `pceffdate`, `mlist`
FROM `purchcost` a
where `pceffdate` = (SELECT MAX(z.`pceffdate`) FROM `purchcost` z WHERE z.`itemcode` = a.`itemcode`
AND z.`pceffdate` <= TDATE)) tb
This line
SELECT MAX(z.`pceffdate`) FROM `purchcost` z WHERE z.`itemcode` = a.`itemcode`
AND z.`pceffdate` <= TDATE
has got to be the problem. It must be returning more than 1 row. So, the DBMS is trying to set multiple values to the same thing, which of course it cannot do.
Do you need something else in your WHERE clause there?
The problem is that
SELECT DISTINCTROW `itemcode` ID, `mlist` COST
could store multiple costs against each ID, and so
SELECT DISTINCTROW `cost` INTO ld_cost FROM `prod_itemcost` WHERE id = ls_id;
could return multiple rows for each id.
For example, if purchcost contained the following:
itemcode mlist pceffdate
1 10.99 10-apr-2009
1 11.99 10-apr-2009
1 9.99 09-apr-2009
Then temporary table prod_itemcost would contain:
itemcode mlist
1 10.99
1 11.99
These both being values that were in effect on the most recent pceffdate for that itemcode.
This would then cause a problem with selecting mlist into ld_cost for itemcode 1 because there are two matching values, and the scalar ld_cost can only hold one.
You really need to look at the data in purchcost. If it is possible for 1 item to have more than one entry with different mlist values for the same date/datetime, then you need to decide how that should be handled. Perhaps take the highest value, or the lowest value, or any value. Or perhaps this is an error in the data.
There is another possibility, that is your parameter "TDATE" same as table field name in uppercase or lowercase or mixed. such as 'tdate', 'tDate', 'TDATE'.
so you should check that. I hit this before.
You are inserting an array in a variable instead of a single value that's why its problem occurs.
Like:
DECLARE name varchar;
select f_name into name from student;
here name will accept only single name instead of multiple name;