I am using PostgreSQL 9.1.11.
I need to return result of SELECT to my php script. The invocation in php is like this:
$res = $pdb->getAssoc("SELECT * FROM my_profile();");
The class code to illustrate what is going on in php
public function getAssoc($in_query) {
$res = pg_query($this->_Link, $in_query);
if($res == FALSE) {
return array("dberror", iconv("utf-8", "windows-1251", pg_last_error($this->_Link)));
}
return pg_fetch_all($res);
}
Next comes my function in Postgres. I fully re-create database by dropping in a script when I update any function. (The project is in the early stage of development.) I have little to no experience doing stored procedures.
I get this error:
structure of query does not match function result type
CONTEXT: PL/pgSQL function "my_profile" line 3 at RETURN QUERY )
Trying to write:
CREATE FUNCTION my_profile()
RETURNS TABLE (_nick text, _email text) AS $$
BEGIN
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
END;
$$
LANGUAGE 'plpgsql' SECURITY DEFINER;
Table structure is:
CREATE TABLE my_users(
id integer NOT NULL,
nick text,
email text,
pwd_salt varchar(32),
pwd_hash character(128),
CONSTRAINT users_pk PRIMARY KEY (id)
);
When I return 1 column in a table the query works. Tried to rewrite procedure in LANGUAGE sql instead of plpgsql with some success, but I want to stick to plpgsql.
The Postgres 9.1.11, php-fpm I am using is latest for fully updated amd64 Debian wheezy.
What I want to do is to return a recordset containing from 0 to n rows from proc to php in an associative array.
This part is incorrect:
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
You should remove the parentheses around nick,email otherwise they form a unique column with a ROW type.
This is why it doesn't match the result type.
#Daniel already pointed out your immediate problem (incorrect use of parentheses). But there is more:
Never quote the language name plpgsql in this context. It's an identifier, not a string literal. It's tolerated for now since it's a wide-spread anti-pattern. But it may be considered a syntax error in future releases.
The SECURITY DEFINER clause should be accompanied by a local setting for search_path. Be sure to read the according chapter in the manual.
Everything put together, it could look like this:
CREATE FUNCTION my_profile()
RETURNS TABLE (nick text, email text) AS
$func$
BEGIN
RETURN QUERY
SELECT m.nick, m.email FROM my_users m WHERE m.id = 1;
END
$func$
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, pg_temp;
Replace public whit the actual schema of your table.
To avoid possible naming conflicts between OUT parameters in RETURNS TABLE ... and table columns in the SELECT statement I table-qualified column names with the given alias m.
Related
I'm trying to write a PHP program to update a MySQL table entry according to a phone number. The phone numbers in the database are entered without limitations and are typically formatted in the XXX-XXX-XXXX way, but sometimes have other characters due to typos. In order to ensure the query works every time, I want to remove all non-numeric characters from the entries so that I can compare the entries to phone numbers formatted like XXXXXXXXXX coming from a separate source.
I've done some research and found some solutions but am unsure how to incorporate them into the PHP script. I am fairly new to MySQL and most of the solutions provided user defined MySQL functions and I don't know how to put them into the PHP script and use them with the query I already have.
Here's one of the solutions I found:
CREATE FUNCTION [dbo].[CleanPhoneNumber] (#Temp VARCHAR(1000))
RETURNS VARCHAR(1000) AS BEGIN
DECLARE #KeepValues AS VARCHAR(50)
SET #KeepValues = '%[^0-9]%'
WHILE PATINDEX(#KeepValues, #Temp) > 0
SET #Temp = STUFF(#Temp, PATINDEX(#KeepValues, #Temp), 1, '')
RETURN #Temp
END
And this is the query I need the solution for:
$sql = "SELECT pid AS pid FROM patient_data " .
"WHERE pid = '$pID' AND phone_cell = '$phone_number';";
The query should return the data in the pid column for a single patient, so if the phone number is 1234567890 and the pid is 15, 15 should be returned. I have no output at the moment.
The example function definition is Transact-SQL (i.e. for Microsoft SQL Server), it's not valid MySQL syntax.
A function like this doesn't go "into" the PHP code. The function gets created on the MySQL database as a separate step, similar to creating a table. The PHP code can call (reference) the defined function just like it references builtin functions such as DATE_FORMAT or SUBSTR.
The SELECT statement follows the pattern of SQL that is vulnerable to SQL Injection. Any potentially unsafe values that are incorporated into SQL text must be properly escaped. A better pattern is to use prepared statements with bind placeholders.
As an example of a MySQL function:
DELIMITER $$
CREATE FUNCTION clean_phone_number(as_phone_string VARCHAR(1024))
RETURNS VARCHAR(1024)
DETERMINISTIC
BEGIN
DECLARE c CHAR(1) DEFAULT NULL;
DECLARE i INT DEFAULT 0;
DECLARE n INT DEFAULT 0;
DECLARE ls_digits VARCHAR(20) DEFAULT '0,1,2,3,4,5,6,7,8,9';
DECLARE ls_retval VARCHAR(1024) DEFAULT '';
IF ( as_phone_string IS NULL OR as_phone_string = '' ) THEN
RETURN as_phone_string;
END IF;
SET n := CHAR_LENGTH(as_phone_string);
WHILE ( i < n ) DO
SET i := i + 1;
SET c := SUBSTR(as_phone_string,i,1);
IF ( FIND_IN_SET(c,ls_digits) ) THEN
SET ls_retval := CONCAT(ls_retval,c);
END IF;
END WHILE;
RETURN ls_retval;
END$$
DELIMITER ;
We can execute these statements in the mysql command line client, connected as a user with sufficient privilege, to create the function.
This isn't necessarily the best way to write the function, but it does serve as a demonstration.
Once the function is created, we can reference it a SQL statement, for example:
SELECT t.foo
, clean_phone_number(t.foo)
FROM ( SELECT '1' AS foo
UNION ALL SELECT '1-888-TAXICAB'
UNION ALL SELECT '888-555-1212'
UNION ALL SELECT '+=_-()*&^%$##"''<>?/;:"abc...xyz'
UNION ALL SELECT ''
UNION ALL SELECT NULL
) t
Using PHP 5.3.2 and Oracle 11G, I'm trying to pass an array from PHP into an oracle stored proc. Here is my PL/SQL:
create or replace type NUM_ARRAY as table of number;
create or replace package txa as
procedure upsert_txa_compliance_slct( v_compl_id_array in num_array);
end txa;
create or replace package body txa as
procedure upsert_txa_compliance_slct(v_compl_id_array in num_array)
is
begin
.
. -- sql code removed for brevity. package and body compile no errors
.
end upsert_txa_compliance_slct;
end;
The Query:
$sql = "begin txa.upsert_txa_compliance_slct(:my_array); end;";
And the PHP Code I've tried to bind the array and execute :
First:
<?
$this->conn = ociplogon($dbuser, $dbpass, $dbname);
$this->commit_mode = OCI_COMMIT_ON_SUCCESS;
$this->sth = #ociparse($this->conn, $sql);
oci_bind_array_by_name($this->sth,
':my_array',
$my_array,
count($my_array),
-1,
SQLT_CHR);
$r = #ociexecute($this->sth, $this->commit_mode);
?>
Which generates this error:
PLS-00306: wrong number or types of arguments in call to 'UPSERT_TXA_COMPLIANCE_SLCT'
I'm clearly passing 1 arg. So, what's wrong with/how do I fix the type issue?
Additionally I found this
http://www.oracle.com/technetwork/articles/seliverstov-multirows-098120.html
And tried it the old way using oci collection like so:
$collection = oci_new_collection($this->conn,"NUM_ARRAY");
After I changed my oracle type to this:
create or replace type NUM_ARRAY as varray(100) of number;
I got this error:
oci_new_collection(): ORA-22318: input type is not an array type
Any help would be MUCH appreciated.
EDIT 7:08PM ET Aug 14, 2014
I changed my php oci_bind function call to use SQLT_NUM as the type. This had no impact. Then I changed my package to include:
type num_array is table of number index by binary_integer;
( i also dropped the original num_array from my schema )
This change made it possible to pass my array to the stored proc, but then I can't use the array as a nested table like so:
delete
from my_table
where id not in (select column_value from table(v_compl_id_array));
I get this error when i try to compile the package body with that statement in it:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
And all the documentation tells me to return to the schema level type? But when I do I get that other error. I know I can find another way to do this using a loop over my pl/sql array, but I would really love to be able to use that schema level type.
The answer is this. You can't use a globally created or schema level type as a parameter to a stored procedure. PHP's oci_bind_array_by_name just doesn't seem to work with globally created types, but you need the globally created type to be able to use your array as a nested table in subselects. So.... here is how I got this to work. I'm MORE THAN HAPPY TO HEAR OTHER SOLUTIONS!! but for now, here's what I did.
-- globally create a type table of number
create or replace type num_array is table of number;
-- in my package i created an internal type table of number
type i_num_array is table of number index by binary_integer;
-- i then used i_num_array (internal type) as the type for my IN parameter to the procedure
upsert_TXA_compliance_slct( v_compl_id_array in i_num_array)
-- in my procedure i also created a variable that is the type of my globally created type
v_num_array num_array := num_array();
-- then i populated that variable in a loop inside my procedure with the values in my IN param
for i in 1 .. v_compl_id_array.count
loop
v_num_array.extend(1);
v_num_array(i) := v_compl_id_array(i);
end loop;
-- then i used v_num_array as my nested table so this now works:
delete from my_table where id in (select * from table(v_num_array));
DELIMITER $$
CREATE DEFINER= 'sports'`#`'%'` PROCEDURE `CheckMembership`(IN BrandId int,
IN Email varchar(128),
IN PhoneNumber varchar(10),
OUT ResponseCode int,
OUT ResponseMessage varchar(256))
BEGIN
declare _count int;
select count(*) from Member where Phone1 = PhoneNumber into _count;
set ResponseCode = 1;
set ResponseMessage = '';
END
Can anyone please help me to resolve this stored procedure as I a new to this and couldnot find any good solution in the internet . All I need to do is to get the count value by running it in php
For one thing, the syntax of the SELECT statement is wrong because the INTO clause is misplaced.
That should follow the SELECT list and come before the FROM keyword. For example:
SELECT COUNT(*) INTO _count
FROM Member
WHERE Phone1 = PhoneNumber ;
Syntax help is available in MySQL Reference Manual: http://dev.mysql.com/doc/refman/5.5/en/select-into.html
I'm not sure if this answers the question you were asking. (It's not at all clear what question you were asking.)
Q: I don't know how to call this stored procedure in a php file to get the answers.
From PHP, I think you would need to use MySQL user-defined variables for the OUT arguments, and retrieve those values with a separate SELECT statement:
As a simple demonstration of a pattern you could use, given the arguments of the procedure as shown in the question:
-- set user defined variables
SELECT #brandid = 'fee', #email = 'fi', #phonenumber = 'fo', #rc := '', #rm := '';
-- execute stored procedure with user-defined variables as arguments
CALL CheckMembership(#brandid, #email, #phonenumber, #rc, #rm);
-- retrieve user-defined variables
SELECT #rc AS ResponseCode, #rm AS ResponseMessage;
To return the "count" value, you could follow the same pattern. Modify the procedure to add another OUT parameter, and then set that to the value of that parameter in the procedure.
(This seems an unnecessary rigmarole, using a stored procedure to return a result which could just as easily be returned by a simple query.)
In MySQL, I have a trigger:
BEGIN
IF (EXISTS(SELECT * FROM devices WHERE device_id = NEW.device_id)) THEN
SET NEW.id = NULL;
ELSE
INSERT INTO objects (object_type) VALUES ('3');
SET NEW.id = LAST_INSERT_ID();
END IF;
END
When this trigger gets a new id (from the objects table) it inserts the id into the id column of the devices table.
When I refer to it (for example with mysql_insert_id(); in PHP), its empty.
How can I return the insert id from the trigger (LAST_INSERT_ID();) to the function in PHP as the mysql_insert_id(); ?
Personally I use stored procedures.
Here is a basic example with PDO:
Code to create the Stored Procedures:
CREATE DEFINER=`user`#`localhost` PROCEDURE `InsertUser`(IN `Input_username` INT, OUT `Out_ID` INT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
INSERT INTO users(
username)
VALUES (
Input_username);
SET Out_ID = LAST_INSERT_ID();
SELECT Out_ID;
END
And PHP code:
$insert = "CALL InsertUser(:Input_username,
#Out_ID)";
$bdd = new PDO('mysql:host=localhost;dbname=db-name', 'user', 'password');
$stmt = $bdd->prepare($insert);
$stmt->bindParam(':Input_username', rand(), PDO::PARAM_STR); // to create random name
$stmt->execute();
$tabResultat = $stmt->fetch();
$id_user = $tabResultat['Out_ID'];
var_dump($id_user);
I hope I have helped. :)
This behaviour is by design:
If a stored procedure executes statements that change the value of LAST_INSERT_ID(), the changed value is seen by statements that follow the procedure call.
For stored functions and triggers that change the value, the value is restored when the function or trigger ends, so following statements will not see a changed value.
Workaround 1: Stored Procedures
Unfortunately this introduces a risk of inconsistencies between your table and objects, as insertions could still happen outside of this procedure (this problem could be adressed with convoluted access restrictions on the table)
Workaround 2:
Save the value in a user variable:
CREATE TRIGGER
....
BEGIN
INSERT INTO objects (object_type) VALUES ('3');
SET NEW.id = LAST_INSERT_ID();
SET #myLastInsertID = LAST_INSERT_ID();
END //
INSERT INTO your_table... -- trigger the above
SELECT #myLastInsertID; -- here is your value
Workaround 3:
Simply get the value from object ;)
INSERT INTO your_table... -- trigger the above
SELECT MAX(autoinc_column) FROM objects; -- here is your value!
Workarounds 2 and 3 should be wrapped in a transaction to ensure no-one interferes with #myLastInsertID or object during the process.
I have a custom type
CREATE TYPE mytype as (id uuid, amount numeric(13,4));
I want to pass it to a function with the following signature:
CREATE FUNCTION myschema.myfunction(id uuid, mytypes mytype[])
RETURNS BOOLEAN AS...
How can I call this in postgres query and inevitably from PHP?
You can use the alternative syntax with a array literal instead of the array constructor, which is a Postgres function-like construct and may cause trouble when you need to pass values - like in a prepared statement:
SELECT myschema.myfunc('0d6311cc-0d74-4a32-8cf9-87835651e1ee'
, '{"(0d6311cc-0d74-4a32-8cf9-87835651e1ee, 25)"
, "(6449fb3b-844e-440e-8973-31eb6bbefc81, 10)"}'::mytype[]);
I added a line break between the two row types in the array for display. That's legal.
How to find the correct syntax for any literal?
Just ask Postgres. Here is a demo:
CREATE TABLE mytype (id uuid, amount numeric(13,4));
INSERT INTO mytype VALUES
('0d6311cc-0d74-4a32-8cf9-87835651e1ee', 25)
,('6449fb3b-844e-440e-8973-31eb6bbefc81', 10);
SELECT ARRAY(SELECT m FROM mytype m);
Returns:
{"(0d6311cc-0d74-4a32-8cf9-87835651e1ee,25.0000)","(6449fb3b-844e-440e-8973-31eb6bbefc81,10.0000)"}
db<>fiddle here
Any table (including temporary tables) implicitly creates a row type of the same name.
select myschema.myfunc('0d6311cc-0d74-4a32-8cf9-87835651e1ee'
, ARRAY[('ac747f0e-93d4-43a9-bc5b-09df06593239', '25.00')
, ('6449fb3b-844e-440e-8973-31eb6bbefc81', '10.00')]::mytype[]
);
Still need PHP portion of this resolved though, still not sure how to call a function populating with the custom array parameter.