I need to update some rows of the tables and then display these rows. Is there a way to do this with one single query and avoid this 2 query ? :
UPDATE table SET foo=1 WHERE boo=2
SELECT * from table WHERE ( foo=1 ) AND ( boo=2 )
In PostgreSQL v8.2 and newer you can do this using RETURNING:
UPDATE table
SET foo=1
WHERE boo=2
RETURNING *
You can use a stored procedure in PL/pgSQL. Take a look at the [docs][1]
Something like this
CREATE FUNCTION run(fooVal int, booVal int)
RETURNS TABLE(fooVal int, booVal int)
AS $$
BEGIN
UPDATE table SET foo = fooVal WHERE boo= booVal;
RETURN QUERY SELECT fooVal, booVal from table WHERE ( foo = fooVal ) AND ( boo = booVal );
END;
$$ LANGUAGE plpgsql;
You will save the roundtrip time for sending another statement. This should not be a performance bottleneck. So short answer: Just use two queries. That's fine and this is how you do it in SQL.
[1]: http://www.postgresql.org/docs/8.4/static/plpgsql.html docs
You can use stored procedure or function. It will contains your queries.
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
I have the following stored procedure:
CREATE PROCEDURE `Get_Events_And_Deadlines_By_User`(IN `intervalStart` INT, IN `intervalEnd` INT)
BEGIN
IF (intervalStart != 0 AND intervalEnd != 0) THEN
SELECT 1 AS `test`;
ELSEIF (intervalStart != 0 AND intervalEnd = 0) THEN
BEGIN
SELECT 2 AS `test`;
IF ((SELECT FOUND_ROWS())=1) THEN
SELECT 3 AS `test`;
END IF;
END;
ELSE
SELECT 4 AS `test`;
END IF;
END
When I run call Get_Events_And_Deadlines_By_User(1,0) I only get the select query with 2 as the result, the select inside the if statement is never returned. It seems like only the first select query that is encountered is being executed before the stored procedure is returned. Why is this? What can I do to fix this? I want the select query INSIDE the if statement to be the ONLY result when the if holds.
Your procedure returns multiple resulsets.
PDO:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
$result = $pdo->query('Call Get_Events_And_Deadlines_By_User(1,0)');
do {
foreach( $result as $row ) {
echo join(', ', $row), "\r\n";
}
echo "-----\r\n";
} while($result->nextRowset());
My test:
mysql> DELIMITER $$
mysql> DROP PROCEDURE IF EXISTS `Get_Events_And_Deadlines_By_User`$$
Query OK, 0 rows affected, 1 warning (0,00 sec)
mysql> CREATE PROCEDURE `Get_Events_And_Deadlines_By_User`(
-> IN `intervalStart` INT,
-> IN `intervalEnd` INT
-> )
-> BEGIN
-> IF (intervalStart != 0 AND intervalEnd != 0) THEN
-> SELECT 1 AS `test`;
-> ELSEIF (intervalStart != 0 AND intervalEnd = 0) THEN
-> SELECT 2 AS `test`;
-> IF ((SELECT FOUND_ROWS())=1) THEN
-> SELECT 3 AS `test`;
-> END IF;
-> ELSE
-> SELECT 4 AS `test`;
-> END IF;
-> END$$
Query OK, 0 rows affected (0,00 sec)
mysql> DELIMITER ;
mysql> CALL `Get_Events_And_Deadlines_By_User`(1, 0);
+------+
| test |
+------+
| 2 |
+------+
1 row in set (0,00 sec)
+------+
| test |
+------+
| 3 |
+------+
1 row in set (0,00 sec)
Query OK, 0 rows affected (0,00 sec)
After: Retrieving Multiple Result sets with stored procedure in php/mysqli.
Q: I only get the select query with 2 as the result, the select inside the if statement is never returned. It seems like only the first select query that is encountered is being executed before the stored procedure is returned. Why is this?
A: That second resultset is being returned by the procedure. The client is responsible for requesting the second (and subsequent) resultsets. (In PHP, that would require calling the mysqli mysqli_next_result, or the PDO nextRowset function, depending on which interface library you are using.
But that doesn't seem to be your real question.
Q: What can I do to fix this?
A: That really depends on what behavior you want to achieve. It's possible for a procedure to return multiple resultsets, and for the client to process them.
Q: I want the select query INSIDE the if statement to be the ONLY result when the if holds.
A: You want to a run a query (the SELECT 2 query in the example procedure) but you don't want the procedure to return that as a resultset. You just want to know how many rows that query returns, and to conditionally control the flow in your stored program based on how many rows are returned.
There are several constructs you can use to achieve that. It is possible to run a query within a procedure without having the result from that query returned as a resultset.
test for "more than zero" rows with SELECT EXISTS (subquery)
I suspect you don't really want to use the FOUND_ROWS function; and I suspect that you don't want to test whether the number of rows found is exactly equal to 1.
If what you are attempting to achieve is determining whether a particular query will return one or more rows, you could use a pattern like this:
IF ( SELECT EXISTS ( SELECT 2 AS `test` ) ) THEN
SELECT 2 AS `test`;
ELSE
SELECT 3 AS `test`;
END IF;
Given that the queries in the example code are guaranteed to return exactly one row, I'm just guessing at what you are trying to achieve.
If you have query, and you want to see if that query returns a row, you can wrap it in an EXISTS() condition, which will return a boolean.
EXISTS(subquery) returns a value of 1 if the subquery returns at least one row; and returns 0 if the subquery doesn't return a row.
That construct can be used within an IF for controlling the logic flow within a stored program.
IF ( SELECT EXISTS(subquery) ) THEN
-- subquery returned at least one row, so do something
ELSE
-- subquery didn't return any rows, so do something else
END IF;
get exact count of rows with SELECT COUNT(1) INTO FROM (subquery) q
If testing the "existence" of rows from a subquery isn't sufficient; if there's a reason you need to get an exact number of rows returned by a subquery, you could use a COUNT() aggregate. For example:
SELECT COUNT(1) FROM (subquery) q
To avoid returning the result from that query as a resultset from the procedure, you can assign the value returned by that into procedure variable or a user-defined variable. Assuming you've declared a procedure variable at the top of the procedure, something like this:
DECLARE myrowcount INT;
You could do something like this:
SELECT COUNT(1) INTO myrowcount FROM (subquery) q
IF ( myrowcount = 1 ) THEN
-- subquery returned exactly one row
ELSE
-- subquery either returned zero rows, or returned more than one row
END IF;
A user-defined variable could be used in place of a procedure variable. My preference is to use a procedure variable, if there's no need to persist that count of rows beyond the execution of the procedure.
(The biggest downside to using a user-defined variable is that it introduces unnecessary ambiguity. Someone who is later reading the code is left wondering whether the value stored in the user-defined variable is actually needed after the procedure ends. They don't know whether that's a purposeful, intentional side effect of the procedure, that something else is dependent on. To my mind, avoiding that ambiguity is sufficient reason to use a procedure variable.)
Based on what you mentioned in comments so far, I think you are looking for something below. All I did was to add a new parameter as an output to your procedure and instead of using SELECT to fill the value of test, I have used a variable sending from outside to your function and receive the updated value from outside and use SELECT #test to get your value and it will be only one value at a time not two.
If you have questions regarding the comment I left in procedure, I suggest you read more about FOUND_ROWS() usage.
DELIMITER $$
DROP PROCEDURE IF EXISTS `Get_Events_And_Deadlines_By_User` $$
CREATE PROCEDURE `Get_Events_And_Deadlines_By_User`(
IN `intervalStart` INT,
IN `intervalEnd` INT,
OUT `test` INT
)
BEGIN
IF (intervalStart != 0 AND intervalEnd != 0) THEN
SET test = 1;
ELSEIF (intervalStart != 0 AND intervalEnd = 0) THEN
BEGIN
SET test = 2;
# include your select query here,
# as you need if for FOUND_ROWS(), or
# it will always return 1 cause there is no successful
# SELECT query before it and it will be always 1.
SELECT * FROM someTable;
IF ((SELECT FOUND_ROWS())=1) THEN
SET test = 3;
END IF;
END;
ELSE
SET test = 4;
END IF;
END$$
DELIMITER ;
CALL Get_Events_And_Deadlines_By_User(1,0,#test);
SELECT #test;
I hope it helps.
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.
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;
We have one 2 field in of type 'timestampz' like createdon & updatedon
createdon field is inserted using 'Now()' function of postgresql.
and i need to insert updatedon field by using php.
like i try to insert using 'date( 'Y-m-d H:i:s' , $timestamp );'.
When i try to get updated field is gives me wrong result.
I want to know i want to insert timestampz using php how i can to that.
The following article describes how to implement the timestamp behavior of MySql with Postgres:
http://www.depesz.com/index.php/2008/05/08/mysqls-timestamp-in-postgresql/
It works by creating a trigger:
CREATE OR REPLACE FUNCTION trg_handle_timestamp() RETURNS TRIGGER AS $BODY$
BEGIN
IF NEW.y = OLD.y THEN NEW.y := now(); END IF;
RETURN NEW;
END;
$BODY$ LANGUAGE 'plpgsql';
CREATE TRIGGER trg_handle_timestamp
BEFORE UPDATE ON test FOR EACH ROW EXECUTE PROCEDURE trg_handle_timestamp();
Can't you do something like "UPDATE table SET ..., updatedon = NOW() WHERE ..."?