Bind values for query with plpgsql in Yii2 - php

I execute SQL query with Yii2 DAO.
$db->createCommand("
DO $$
DECLARE
rec RECORD;
pos INT := 0;
BEGIN
FOR rec IN (SELECT * FROM table1 WHERE "type" = :t LOOP
UPDATE table1 SET position = pos WHERE id = rec.id;
pos := pos + 2;
END LOOP;
END;
$$ language 'plpgsql'
", [':t' => 0])->execute();
But it fails with error:
SQLSTATE[42P18]: Indeterminate datatype: 7
ERROR: could not determine data type of parameter $1
type column has INT type. I tried to set param type explicitly with [':t' => [0, \PDO::PARAM_INT]]. But the error is still here. If I concatenate the value right into the SQL string, it works, but that's not a solution. :t is only one parameter in this query.
Other simple SQL queries work successfully. This problem exists only for queries with procedures. If I run this query from the DataGrip, it works. But in PHP it fails.
Why does it not work and how can I bind params for such queries?

If you create an actual function and call it, you can pass values as parameters.
But while you execute a DO statement, the function body (enclosed in dollar-quotes $$ in your example) is just a string literal. No parameter passing and nothing returned. You would have to concatenate values as strings into the plpgsql code.
But you don't need either. Use a simple prepared statement instead. Much cheaper than looping anyway:
UPDATE table1 t
SET position = t1.pos
FROM (
SELECT id, (row_number() OVER () - 1) * 2 AS pos
FROM table1
WHERE "type" = :t
) t1
WHERE t.id = t1.id
It's trivial to pass a parameter value now.
Aside: The result is arbitrary, unless you add ORDER BY to the OVER clause. That weakness is in your original, too.
Related:
What are '$$' used for in PL/pgSQL

Related

How to remove all non-numeric characters from a MySQL table entry

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

Mysql Stored Procedure

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.)

Pass array literal to PostgreSQL function

I have a Postgres function which contains a select statement. I need to add a condition using a passed in variable containing an array of string values.
CREATE OR REPLACE FUNCTION get_questions(vcode text)
RETURN return_value as $f$
DECLARE vresult return_value;
BEGIN
--snip--
SELECT id, title, code
FROM questions WHERE code NOT IN (vcode);
--snip--
questions table:
id ,title, code
1, "title1", "qcode1"
2, "title2", "qcode2"
3, "title3", "qcode3"
4, "title4", "qcode4"
How should the vcode literal be formatted in PHP and what should be the syntax of the condition?
Using PostgreSQL 9.1.1, PHP 5.3.6, pg_query_params.
SQL NOT IN works with sets. Since you are passing an array, use <> ALL.
You have to be careful not to involve any NULL values with such an expression, because NULL <> anything never evaluates to TRUE and therefore never qualifies in a WHERE clause.
Your function could look like this:
CREATE OR REPLACE FUNCTION get_questions(vcode text[])
RETURNS TABLE(id int, title text, code text)
LANGUAGE sql AS
$func$
SELECT q.id, q.title, q.code
FROM questions q
WHERE q.code <> ALL ($1);
$func$;
Call with array literal:
SELECT * FROM get_questions('{qcode2, qcode2}');
Or with an array constructor):
SELECT * FROM get_questions(ARRAY['qcode2', 'qcode2']);
Or you could use a VARIADIC parameter:
CREATE OR REPLACE FUNCTION get_questions(VARIADIC vcode text[]) ...
... and pass a list of values:
SELECT * FROM get_questions('qcode2', 'qcode2');
Details:
Return rows matching elements of input array in plpgsql function
Major points:
Using a simple SQL function since there is nothing in your question that would require the procedural elements of PL/pgSQL.
The input parameter is an array of text: text[]
To return multiple rows from your query use RETURNS TABLE for the return type.
Referring to the in parameter with the positional parameter $1 since referring by name was only introduced with version 9.2 for SQL functions (as opposed to plpgsql functions where this has been around for some versions now).
Table-qualify column names that would otherwise conflict with OUT parameters of the same name defined in the RETURNS clause.
LEFT JOIN unnest($1) / IS NULL
Faster for long arrays (> ~ 80 elements, it depends):
SELECT q.id, q.title, q.code
FROM questions q
LEFT JOIN unnest($1) c(code) USING (code)
WHERE c.code IS NULL;
This variant (as opposed to the above) ignores NULL values in the input array.

How can I pass a comma-separated list of integers from PHP as a parameter to a stored procedure in SQL Server 2008?

Hopefully I'm going about this the right way, if not I'm more than open to learning how this could be done better.
I need to pass a comma separated list of integers (always positive integers, no decimals) to a stored procedure. The stored procedure would then use the integers in an IN operator of the WHERE clause:
WHERE [PrimaryKey] IN (1,2,4,6,212);
The front-end is PHP and connection is made via ODBC, I've tried wrapping the parameter in single quotes and filtering them out in the stored procedure before the list gets to the query but that doesn't seem to work.
The error I'm getting is:
Conversion failed when converting the varchar value '1,2,4,6,212' to data type int.
I've never done this before and research so far has yielded no positive results.
Firstly, let's use a SQL Function to perform the split of the delimited data:
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
To use this, you would simply pass the function the delimited string as well as the delimiter, like this:
SELECT
*
FROM
TableName
WHERE
ColumnName IN (SELECT Data FROM dbo.Split(#DelimitedData, ','))
If you still have issues, due to the datatype, try:
SELECT
*
FROM
TableName
WHERE
ColumnName IN (SELECT CONVERT(int,Data) FROM dbo.Split(#DelimitedData, ','))
You can pass a comma separate list of values. However, you cannot use them as you like in an in statement. You can do something like this instead:
where ','+#List+',' like '%,'+PrimaryKey+',%'
That is, you like to see if the value is present. I'm using SQL Server syntax for concatenation because the question is tagged Microsoft.

Passing an array as an argument into an Oracle stored procedure

I am in a bit of a pickle. I have a stored procedure that accepts an argument and returns the results of a query. The query is using an IN statement.
Here's the structure of the SP:
CREATE OR REPLACE
PROCEDURE EXAMPLE_SP
(
arg VARCHAR2,
argRS1 OUT cursors.rs
)
AS
l_test VARCHAR2(255) := arg;
BEGIN
OPEN argRS1 FOR
SELECT * FROM TABLE1
WHERE LOCATION IN (l_test);
END EXAMPLE_SP;
The number of values within the IN statement can be variable. The options for IN are coming from selected form checkboxes on the UI side.
I am using PHP to retrieve the selected checkbox values. I have already tried imploding the values into a comma deliminated string.
My logic for that was that the query would then look like this:
l_test = 'val1, val2, val3';
SELECT * FROM TABLE1
WHERE LOCATION IN (val1, val2, val3);
But that didn't work. I am not sure how to proceed. Thanks in advance for any constructive comments or suggestions.
You can add this comma separated input parameter as a varchar() and use following where statement:
where (','||l_test||',' like '%,'||LOCATION||',%')
for example if l_test='2,3,4,5' and LOCATION=3 we get:
where (',2,3,4,5,' like '%,3,%')
and it's TRUE if LOCATION value is in this list.
I think the location that you have selected is VARCHAR,so for that you need to convert the list as shown below
l_test = '''val1''||','|| ''val2''||','||''val3''';
So that your final query look like
SELECT * FROM TABLE1
WHERE LOCATION IN ('val1', 'val2', 'val3');
You can do like this also
CREATE OR REPLACE
PROCEDURE EXAMPLE_SP
(
arg VARCHAR2,
argRS1 OUT cursors.rs
)
AS
l_test VARCHAR2(255) := arg;
BEGIN
l_test:=''''||replace(l_test,',',''',''')||'''';
OPEN argRS1 FOR
SELECT * FROM TABLE1
WHERE LOCATION IN (l_test);
END EXAMPLE_SP;
Note:I have not tested this ,but i think this way you will achieve what you want
I would do this without using string manipulation. Theoretically there may currently be little risk of SQL Injection because you're using checkboxes it's best to implement good practice at the beginning so if anything changes you don't put yourself at risk.
The second benefit is that you are still able to utilise any indexes on your column, which you wouldn't be able to do if you use like '%....
To do this you can utilise a table function and an external object to populate your "in" list.
As an example I'm going to return the OBJECT_NAME from USER_OBJECTS.
If I create two tables:
create table tmp_test ( a number );
create table tmp_test2 ( a number );
and an object to hold the list of tables, or in your case locations.
create type t_test_object is table of varchar2(30);
Next, here's the equivalent of your procedure. It's a function that returns a SYS_REFCURSOR. It accepts the T_TEST_OBJECT as a parameter, which means this first needs to be populated before being passed to the function.
create or replace function select_many (
Ptest_object in t_test_object
) return sys_refcursor is
l_curs sys_refcursor;
begin
open l_curs for
select object_name
from user_objects
where object_name in ( select *
from table(Ptest_object)
);
return l_curs;
end;
Lastly, here's an example of how to use this set-up. Notice how an instance of T_TEST_OBJECT gets populated with multiple values. This object then gets passed to the function to return your cursor. Lastly, to display the values I loop through the cursor. Obviously you may want to utilise the cursor and populate the TYPE differently.
SQL> declare
2
3 l_nested_table t_test_object := new t_test_object();
4 l_cursor sys_refcursor;
5 -- Used for display demonstration only.
6 l_object_name user_objects.object_name%type;
7
8 begin
9
10 l_nested_table.extend;
11 l_nested_table(l_nested_table.last) := 'TMP_TEST';
12 l_nested_table.extend;
13 l_nested_table(l_nested_table.last) := 'TMP_TEST2';
14
15 l_cursor := select_many(l_nested_table);
16
17 loop -- Display example, not really relevant
18 fetch l_cursor into l_object_name;
19 exit when l_cursor%notfound;
20 dbms_output.put_line(l_object_name);
21 end loop;
22
23 end;
24 /
TMP_TEST
TMP_TEST2
PL/SQL procedure successfully completed.
You can use Oracle examples from Oracle Documentation: http://docs.oracle.com/cd/B28359_01/win.111/b28378/basfeat.htm#sthref207
Look here - return a table:
Can an SQL procedure return a table?
And here's another example:
PACKAGE emp_actions IS
TYPE EnameTabTyp IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
TYPE SalTabTyp IS TABLE OF emp.sal%TYPE INDEX BY BINARY_INTEGER;
...
PROCEDURE hire_batch (ename_tab IN EnameTabTyp, sal_tab IN SalTabTyp, ...);
PROCEDURE log_names (ename_tab IN EnameTabTyp);
END emp_actions;

Categories