Error passing an array to PL/pgSQL stored procedure - php

I have this procedure:
CREATE OR REPLACE FUNCTION get_saldo_conto(idUtente integer, idConto integer, categories int[], end_date date) RETURNS numeric(8,2) AS $$
DECLARE
row_transazione transazione%ROWTYPE;
saldoIniziale numeric(8,2);
totale numeric(8,2);
BEGIN
saldoIniziale = (SELECT saldo_iniziale FROM conto WHERE id = idConto AND id_utente = idUtente);
totale = 0;
FOR row_transazione IN SELECT *
FROM transazione
LEFT JOIN categoria ON id_categoria = categoria.id
WHERE id_conto = idConto
AND transazione.id_utente = idUtente
AND id_categoria = ANY (categories)
AND data <= end_date
LOOP
IF(row_transazione.tipo = 'entrata') THEN
totale = totale + row_transazione.importo;
ELSE
totale = totale - row_transazione.importo;
END IF;
END LOOP;
RETURN (saldoIniziale + totale) AS saldo_corrente;
END;
$$ LANGUAGE 'plpgsql';
When I call it with for example with
SELECT get_saldo_conto('1','19','{1,2,4,5,6}', '20/01/2015');
gives me an error
ERROR: op ANY/ALL (array) requires array on right side
Am I doing something wrong passing the array?
I tried also passing like '{1,2,4,5,6}'::int[] with no success.
CREATE TABLE transazione(
id SERIAL PRIMARY KEY,
tipo VARCHAR(7) NOT NULL CHECK(tipo IN('spesa', 'entrata')),
importo NUMERIC(8,2) NOT NULL,
descrizione VARCHAR(40),
data DATE DEFAULT CURRENT_TIMESTAMP,
id_conto INTEGER NOT NULL REFERENCES conto(id) ON UPDATE CASCADE ON DELETE CASCADE,
id_utente INTEGER NOT NULL REFERENCES utente(id) ON UPDATE CASCADE ON DELETE CASCADE,
id_categoria INTEGER REFERENCES categoria(id) ON UPDATE CASCADE ON DELETE SET NULL
);

Debug
You defined the row variable
row_transazione transazione%ROWTYPE;
But then you assign SELECT * FROM transazione LEFT JOIN categoriato it, which obviously does not fit the type.
The error message you display, however, does not make sense. The only case of ANY/ALL in your code looks correct. Are you sure you are calling the function you think you are calling? Investigate with:
SELECT n.nspname, p.proname
, pg_get_function_identity_arguments(p.oid) AS params
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname = 'get_saldo_conto';
.. to find all functions with the given name. And
SHOW search_path;
.. to check if the search_path leads to the right one.
Audited function
Your function would work like this:
CREATE OR REPLACE FUNCTION get_saldo_conto(_id_utente integer
, _id_conto integer
, _categories int[]
, _end_date date)
RETURNS numeric(8,2) AS
$func$
DECLARE
row_trans transazione;
saldoIniziale numeric(8,2) := (SELECT saldo_iniziale
FROM conto
WHERE id_utente = _id_utente
AND id = _id_conto);
totale numeric(8,2) := 0;
BEGIN
FOR row_trans IN
SELECT t.*
FROM transazione t
-- LEFT JOIN categoria ON id_categoria = categoria.id -- useless anyway
WHERE t.id_utente = _id_utente
AND t.id_conto = _id_conto
AND t.id_categoria = ANY (_categories)
AND data <= _end_date
LOOP
IF row_trans.tipo = 'entrata' THEN
totale := totale + row_trans.importo;
ELSE
totale := totale - row_trans.importo;
END IF;
END LOOP;
RETURN (saldoIniziale + totale); -- AS saldo_corrente -- no alias here!
END
$func$ LANGUAGE plpgsql;
But that's just to showcase the syntax. The function is expensive nonsense.
Superior simple query
Replace with a simple SELECT:
SELECT COALESCE((
SELECT saldo_iniziale
FROM conto
WHERE id_utente = _id_utente
AND id = _id_conto), 0)
+ COALESCE((
SELECT sum(CASE WHEN tipo = 'entrata' THEN importo ELSE 0 END)
- sum(CASE WHEN tipo = 'spesa' THEN importo ELSE 0 END)
FROM transazione
WHERE id_conto = _id_conto
AND id_utente = _id_utente
AND id_categoria = ANY (_categories)
AND data <= _end_date), 0) AS saldo;
Assuming rows in conto are unique on (id_utente,id).
Depending on implementation details the best query can vary. I chose a variant that is safe against missing rows and NULL values. Either way, a plain query should be much faster than a loop over all rows.
You can wrap this into a function (SQL or plpgsql) if you want.
Aside:
transazione.tipo should rather be an enum type - or even just "char" or boolean. varchar(7) is a waste for tag with two possible values.
And data DATE DEFAULT CURRENT_TIMESTAMP should really be data DATE DEFAULT CURRENT_DATE.

Related

Is it possible to pass multiple input values in stored procedure parameter?

I am working on a fantasy baseball optimizer and I have a stored procedure where when the user selects players it will return the best possible player, but the problem is, the sp only does it for one player at a time. I wanted to know if there was a way the sp could do it for at most nine players at one time.
Visual Reference
Here's the code for the stored procedure:
DELIMITER $$
CREATE DEFINER=`u998875936_chri`#`%` PROCEDURE `Optimizer9`(
PlayerName varchar(30),
PlayerPosition varchar(2),
PlayerSalary int,
PlayerFPPG Numeric (20,17),
CapRemaining int,
OUT ReturnPlayerName varchar(30),
OUT ReturnCap int
)
BEGIN
Declare Count int;
Declare lclSal int;
Declare lclPlayerName varchar(30);
Declare lclReturnPlayerName varchar(30);
Declare lclCap int;
Set lclPlayerName='';
Set lclReturnPlayerName='';
Set lclSal=0;
Set lclCap = CapRemaining;
Set Count=0;
create temporary Table IF NOT EXISTS TempPlayer9(
Player varchar(30),
Pos varchar(2),
Sal int,
Points Numeric (20,17)
);
Insert into TempPlayer9 (Player,Pos,Sal,Points)
SELECT Nickname
,Position
,Salary
,FPPG
FROM playerList
WHERE POSITION = PlayerPosition
and FPPG > PlayerFPPG
order by FPPG DESC;
Set Count=(Select Count(*) from TempPlayer9);
While Count > 0
DO
Set lclPlayerName=(Select Player from TempPlayer9 LIMIT 1);
Set lclSal=(Select Sal from TempPlayer9 LIMIT 1);
IF (lclsal- PlayerSalary) < lclCap
THEN
Set lclCap = lclCap - (lclSal- PlayerSalary);
Set lclReturnPlayerName = lclPlayerName;
Set Count=0;
DELETE from TempPlayer9 Where Player = lclPlayerName;
Set Count=Count-1 ;
END IF;
END WHILE;
Set ReturnPlayerName = lclReturnPlayerName;
Set ReturnCap = lclCap;
END$$
DELIMITER ;
PHP Code:
<?php
$remain=35000;
$player= $_POST['player'];
$pos= $_POST['pos'];
$sal= $_POST['sal'];
$points= $_POST['fppg'];
$cap= $remain-$sal;
$sql = $mysqli->query("call Optimizer11('$player', '$pos','$sal' , '$points', '$cap', #ReturnPlayerName, #ReturnCap)");
$results = $mysqli->query ("select #ReturnPlayerName as Player,#ReturnCap AS SalRemaining");
$rows = mysqli_fetch_array($results);
print_r($rows);
?>

Call procedure return nothing but it returns when execute in mysql 5.5

I am working on a procedure for my project but I got a problem like the pictures below.
This is when I call that procedure with mysql code but then it shows nothing
It returns the result I want when I execute it with php admin interface
And here is my procedure
DELIMITER //
CREATE PROCEDURE baocaodoanhthunam(IN nam integer)
BEGIN
DECLARE thang integer;
DECLARE countcb integer;
DECLARE doanhthu decimal;
SET #thang = 1;
create temporary table test_1 (thangg int, socb int, doanhthuu decimal);
while #thang <= 12
do
SELECT COUNT( * ), SUM(result.tonggiave) into #countcb, #doanhthu
FROM (
SELECT cb.MACHUYENBAY, SUM( ctdv.GIAVE ) AS tonggiave
FROM chuyenbay cb, dondatve ddv, chitietdatve ctdv, ve v
WHERE YEAR( cb.THOIGIANBAY ) = nam
AND MONTH( cb.THOIGIANBAY ) = #thang
AND ctdv.MAVE = v.MAVE
AND v.MACHUYENBAY = cb.MACHUYENBAY
AND ctdv.MADONDATVE = ddv.MADONDATVE
AND ddv.DATHANHTOAN =1
GROUP BY cb.MACHUYENBAY
) AS result;
insert into test_1 values (#thang, #countcb, #doanhthu);
set #thang = #thang + 1;
end while;
select t.thangg as 'Tháng', t.socb as 'Số chuyến bay', t.doanhthuu as 'Doanh thu' from test_1 t;
END //
DELIMITER ;
So my question is in mysql 5.5, does it support create temporary table or not?
If it does, then why I got this problem?
Thanks guys for your help! I'm very appreciate for you help.

php Num rows of stored procedure MySQL

I have a stored procedure in MySQL. I call my procedure in php as
$qry = $mysqli->prepare("CALL seen_table()");
i tried to get the resulting rows by
$row = $qry->num-rows;
but its resulting 0 even there is a resulting set.Then I tried also to put output parameter in my procedure where in inside of my proc is ...
SELECT COUNT(*) INTO cnt FROM TBL
...then this is my codes
$qry = $mysqli->prepare("CALL seen_table(#cnt)");
$qry1 = $mysqli->query("SELECT #cnt");
$row = $qry1->num_rows;
then now its always results 1 even there is no count. when i try to execute CALL and SELECT #cnt in Mysql . if there is no count. the result will be
|#cnt|
|(null)|
does null really count as one?please help. thanks a lot.
EDIT: Added seen_table Procedure codes
DELIMITER $$
USE `xiroosco_mundoxiro`$$
DROP PROCEDURE IF EXISTS `seen_table`$$
CREATE DEFINER=`xiroosco`#`103.16.170.%` PROCEDURE `seen_table`(bound
VARCHAR(255),IN cmntId INT,IN cmntViewID VARCHAR(255),OUT viewCNT INT)
BEGIN
DECLARE seen_ID INT DEFAULT 0;
DECLARE seen_notifica_ID INT DEFAULT 0;
DECLARE seen_viewers TEXT;
DECLARE occurance INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE splitted_value INT;
DECLARE done INT DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT seen.seen_ID, seen.seen_notifica_ID,
seen.seen_viewers
FROM seen
WHERE seen.seen_notifica_ID = cmntId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DROP TEMPORARY TABLE IF EXISTS seen2;
CREATE TEMPORARY TABLE seen2(
`seen_ID` INT NOT NULL,
`seen_notifica_ID` INT NOT NULL,
`seen_viewers` VARCHAR(255) NOT NULL
) ENGINE=MEMORY;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO seen_ID,seen_notifica_ID, seen_viewers;
IF done THEN
LEAVE read_loop;
END IF;
SET occurance = (SELECT LENGTH(seen_viewers) -
LENGTH(REPLACE(seen_viewers, bound, '')) +1);
SET i=1;
WHILE i <= occurance DO
SET splitted_value = (SELECT
REPLACE(SUBSTRING(SUBSTRING_INDEX(seen_viewers, bound, i),
LENGTH(SUBSTRING_INDEX(seen_viewers, bound, i - 1)) + 1), ',', ''));
INSERT INTO seen2 VALUES (seen_ID,seen_notifica_ID, splitted_value);
SET i = i + 1;
END WHILE;
END LOOP;
IF cmntViewID = "*" THEN
SELECT * FROM seen2 GROUP BY seen2.seen_viewers;
SELECT COUNT(*) INTO viewCNT FROM seen2;
ELSE
SELECT * FROM seen2 WHERE seen2.seen_viewers = cmntViewID GROUP BY
seen2.seen_viewers;
SELECT seen_ID INTO viewCNT FROM seen2 WHERE seen2.seen_viewers =
cmntViewID GROUP BY seen2.seen_viewers;
END IF;
CLOSE cur1;
END$$
DELIMITER ;
this is how i call my procedure example
CALL seen_table (',',2995,'356',#count);
NULL counts as one, but as far as I can see it is against the ANSI standard: If there are not results, there should also be no NULL returned.
To get a row count returned from a procedure (or any result set) in MySQL, there are information functions.
I do not know which select you want to return the count, so just giving you an example:
{your procedure before this part}
IF cmntViewID = "*" THEN
SELECT SQL_CALC_FOUND_ROWS * FROM seen2 GROUP BY seen2.seen_viewers;
SELECT COUNT(*) INTO viewCNT FROM seen2;
ELSE
SELECT * FROM seen2 WHERE seen2.seen_viewers = cmntViewID GROUP BY
seen2.seen_viewers;
SELECT seen_ID INTO viewCNT FROM seen2 WHERE seen2.seen_viewers =
cmntViewID GROUP BY seen2.seen_viewers;
END IF;
{your procedure after this part}
Then execute:
CALL seen_table();
SELECT FOUND_ROWS();
That will return the number of rows in the "SELECT SQL_CALC_FOUND_ROWS * FROM seen2 GROUP BY seen2.seen_viewers;" query.

MySQL Iterate through elements of a split string and do things with them

I have a cell in one of my MySQL community edition 5.1 tables. The contents are always certain number(s). If there is more than one number, then it is delimited by an ; semi-colon.
For example:
| Column |
1ar8fj
99dkek;adjidk3;dajdid
divdae;dadjid;
NULL
dkadjcud;dasd;adfkvdo
dkjfakj
...
I need make some code that takes each column value, splits it up by the ; and then uses each value after it was split up to do another query, and output the results.
I know I can do this with PHP but I don't need to make this into a webpage, so I was wondering if this is possible write in MySQL syntax? The PHP code would look something like this:
<?php
$result = $mysqli->query('select column from table;');
while ($row = $result->fetch_array($result)){
$id_numbers = explode($row[0],';');
foreach($id_numbers as $key => $val){
// do another query
$result2 = $mysqli->query('select * from table2 where col_val = "'.$val.'"');
while ($row2 = $result2->fetch_array($result2){
echo $row2[0].'<br>';
}
}
}
?>
Is this possible directly in MySQL syntax?
Thanks!!!
PHEW. Okay. I finally got it working, but here's a solution as a stored procedure that takes a string as an input for the delimiter and is runs on the given table called testtable
--Procedure: sprecursplit
--DROP PROCEDURE IF EXISTS sprecursplit;
DELIMITER |
CREATE PROCEDURE sprecursplit
(
IN delim nvarchar(255)
)
BEGIN
declare tdone tinyint unsigned default(0);
declare depth int unsigned default(1);
declare datas nvarchar(255) default('');
declare done tinyint unsigned default(0);
declare dlength int unsigned default(1);
declare hlength int unsigned default(0);
declare pos int unsigned default(1);
declare runpos int unsigned default(1);
declare slice nvarchar(255) default('');
drop table if exists allsubs;
create temporary table allsubs(id int unsigned auto_increment, val nvarchar(255), primary key (id))engine = memory;
while tdone <> 1 do
if depth <= (select count(*) from testtable) then
select t.datastring into datas from testtable t where t.id = depth limit 1;
if length(datas) > 0 then
set dlength = length(delim);
set hlength = length(datas);
set pos = 1;
set runpos = 1;
set slice = '';
set done = 0;
if hlength > 0 then
while done <> 1 do
if runpos > hlength then
set done = 1;
else
set pos = locate(delim, substring(datas from runpos));
if pos <> 1 then
if pos > 1 then
set slice = substring(datas from runpos for (pos - 1));
else
set slice = substring(datas from runpos);
end if;
insert into allsubs (val) values (slice);
end if;
if pos = 0 then
set runpos = runpos + hlength;
else
set runpos = runpos + pos + dlength - 1;
end if;
end if;
end while;
end if;
end if;
set depth = depth + 1;
else
set tdone = 1;
end if;
end while;
select * from allsubs;
drop table allsubs;
END|
DELIMITER ;

Echoing out a field and a value from one row while running a query that ranks all rows

I have a MySQL query called $sqlStr5 that ranks rows by a metric called totalScore2. One of the fields that $sqlStr5 returns is called username.
I would like to echo out the rank and the value of totalScore2 where username equals a variable called $u.
How can I do this?
Below is what I have so far.
Thanks in advance,
John
$result = mysql_query($sqlStr5);
$count = 1;
$arr = array();
while ($row = mysql_fetch_array($result)) {
echo '<div class="sitename1edit2a">'.$count++.'.</div>';
echo '<div class="sitename1edit2">'.number_format(($row["totalScore2"])).'</div>';
}
This should work:
$result = mysql_query($sqlStr5);
$count = 1;
while($row = mysql_fetch_array($result))
{
if($u == $row['username'])
{
echo '<div class="sitename1edit2a">'.$count.'</div>';
echo '<div class="sitename1edit2">'.number_format($row["totalScore2"]).'</div>';
}
$count++;
}
Note however that this is not the most efficient way. It should be possible to make a SQL query that returns the rank and the total score for user $u.
The following solution uses a stored procedure to work out a user's rank based on a simple ranking system which you could replace with your own more complex one.
call get_user_rank(<user_id>)
I've added another stored procedure which lists the top ten ranks and flags whether the user_id passed in is included in the top ten as I remember that being a requirement from another question of yours.
call list_top_ten_ranks(<user_id>)
Testing (call these sprocs from your php)
select count(*) from users;
count(*)
========
250000
call get_user_rank(54193);
-- 0:00:00.300: Query OK
call get_user_rank(1);
-- 0:00:00.291: Query OK
call list_top_ten_ranks(54193);
-- 0:00:00.208: Query OK
call list_top_ten_ranks(1);
-- 0:00:00.215: Query OK
PHP
$result = $conn->query(sprintf("call get_user_rank(%d)", 1));
$row = $result->fetch_assoc();
$result->close();
echo sprintf("user = %s rank = %s points = %s<br/>",
$row["user_id"],$row["rank"],$row["points"]);
$conn->close();
Hope some of this proves useful.
Script
-- TABLES
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
points smallint unsigned not null default 0
)
engine=innodb;
-- PROCEDURES
drop procedure if exists get_user_rank;
delimiter #
create procedure get_user_rank
(
p_user_id int unsigned
)
proc_main:begin
-- replace this simple ranking method by your own
set #rank = 0;
create temporary table tmp engine=memory
select
#rank:=#rank+1 as rank,
u.user_id,
u.points
from
users u
order by
u.points desc, u.user_id;
select * from tmp where user_id = p_user_id;
drop temporary table if exists tmp;
end proc_main #
delimiter ;
drop procedure if exists list_top_ten_ranks;
delimiter #
create procedure list_top_ten_ranks
(
p_user_id int unsigned
)
proc_main:begin
-- replace this simple ranking method by your own
set #rank = 0;
set #in_top_ten = 0;
create temporary table tmp engine=memory
select
#rank:=#rank+1 as rank,
u.user_id,
u.points
from
users u
order by
u.points desc, u.user_id
limit 10;
if exists (select 1 from tmp where user_id = p_user_id) then
set #in_top_ten = 1;
end if;
select tmp.*, #in_top_ten as in_top_ten from tmp order by tmp.rank;
drop temporary table if exists tmp;
end proc_main #
delimiter ;

Categories