I am executing a PLSQL Block using OCI from my PHP site which is running some procedures inside. The final procedure is returning the inserted records rowid of a specific table.
BEGIN
proc1(1);
proc2(2, rowid_);
END;
What I want to do is, I want to get the primary key values of the record for this rowid?
Is there a way to run it somehow like below and get the select results out to PHP with oci_fetch_row or something?
BEGIN
proc1();
proc2(rowid_); -- out variable
SELECT column1, column2
FROM my_table
WHERE rowid = rowid_;
END;
There's a better way. Try something like:
DECLARE
nPK_col NUMBER;
nCol1 NUMBER := 1;
nCol2 NUMBER := 2;
BEGIN
INSERT INTO SOME_TABLE(COL1, COL2)
VALUES (nCol1, nCol2)
RETURNING PK_COL INTO nPK_col;
END;
This example assumes that the primary key column named PK_COL is populated in some way during the execution of the INSERT statement, e.g. by a trigger. The RETURNING clause of the INSERT statement specifies that the value of PK_COL from the inserted row should be put into the variable specified, in this case nPK_col. You can specify multiple columns and variables in the RETURNING clause - documentation here. You may need to put this into whatever procedure performs the actual INSERT and then add an OUT parameter to allow the value to be passed back to the caller - or use a FUNCTION instead of a PROCEDURE and have the primary key value be the return value of the FUNCTION.
Share and enjoy.
Related
I'm trying to sum columns x through x+n in an SQL table. Essentially, I have multiple tables that contain grades in them and a user_id. I want to sum all the grades to come up with a total grade column without specifying the column names as the names and number of columns changes with each table. For instance, one table might have columns (user_id, calculations, prelab, deductions) while another might have (user_id, accuracy, precision, graphs, prelab, deductions).
I could rename my columns col1, col2, col3, col4, col5, etc., but I can't figure out how to get around the varying number of columns.
As far as I know, there is no way to sum groups of columns without actually specifying the column names directly in SQL. It seems to me like this is a badly designed schema, but that's a separate topic.
In any your case, you're going to need to create a new column in each table that contains the sum of all the grades in that particular table, say called total, and then, do something like this:
select user_id, sum(table1.total, table2.total, table3.total)
from table1, table2, table3
where table1.user_id = table2.user_id
and table2.user_id = table3.user_id
group by user_id
1) You could write some pl/sql to go and hit the data dictionary and get the columns and then construct dynamic sql to do the work of adding them up correctly.
2) Or you could create views on top of the tables that contain the user_id and the sum of the interesting columns (the views themselves could be constructed programmatically - but that only needs to happen once rather than every time you want the totals).
But either of the above is probably over-kill compared to simply fixing your schema.
The following procedure would likely do the trick.
It will look for all column names for the given tableName in the INFORMATION_SCHEMA.COLUMNS table (excluding 'userid' - This may be subject to change if the name you use is different).
The procedure also creates a temporary table (this is also subject to improvement - it would probably be better to do a 'drop if exists before the create) to store the sum up to a point.
The items inside the loop is just building an SQL UPDATE statement with the given tableName argument and the columnName from the cursor and doing the math.
To test this (after creation):
call myProcedure('tableName');
DELIMITER //
DROP PROCEDURE IF EXISTS myProcedure //
CREATE PROCEDURE
myProcedure( tableName varchar(32) )
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE columnName varchar(64);
DECLARE cur1 CURSOR FOR SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = tableName and COLUMN_NAME <> 'userid';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
CREATE TEMPORARY TABLE intermediateresults(userid integer, sumOfScores integer);
SET #st1 = CONCAT('INSERT INTO intermediateresults (SELECT DISTINCT userid, 0 FROM ',tableName,' )' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
looping: LOOP
FETCH cur1 into columnName;
IF done THEN
LEAVE looping;
END IF;
SET #st1 = CONCAT('UPDATE intermediateresults set sumOfScores = sumOfScores + COALESCE( (SELECT ', columnName, ' FROM ',tableName, ' t WHERE t.userid=intermediateresults.userid) , 0)' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END LOOP;
CLOSE cur1;
SELECT * FROM intermediateresults;
DROP table intermediateresults;
END
//
DELIMITER ;
What might be of interest when doing this kind of thing:
INFORMATION_SCHEMA also has data on:
DATA_TYPE: which can be used to test if a specific column has the actual type you are expecting - a condition such as DATA_TYPE='int' can be added to the cursor definition to make sure that it is in fact an int (assuming that the columns to be summed are in fact INTs)
ORDINAL_POSITION: which can be used if you know in which order the columns are supposed to arrive (for cases where the last four are housekeeping, for instance)
TABLE_SCHEMA: the procedure above rather assumes that the table is only present in the current default schema. Using this would require an additional parameter in the procedure and a slight change in the constructed SQL statements.
What is the purpose of OUT in MySQL stored procedures?
If I have a simple stored procedure which looks like this:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `new_routine`(
IN iID int
)
BEGIN
select * from table1 where id = iID;
END
This would give me all the results I want by running:
call new_routine(7);
So why would I want/need to use OUT?
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `new_routine`(
IN iID int,
OUT vName varchar(100)
)
BEGIN
select name into vName from table1 where id = iID;
END
and call it like this
call new_routine(7, #name);
select #name;
Which will give me just the name instead of everything from the rows returned?
I've tried Googling, but clearly haven't asked Google the right question to get a clear answer.
As quoted from MySQL doc on PROCEDURE
For each OUT or INOUT parameter, pass a user-defined variable in the
CALL statement that invokes the procedure so that you can obtain its
value when the procedure returns. If you are calling the procedure
from within another stored procedure or function, you can also pass a
routine parameter or local routine variable as an IN or INOUT
parameter.
And later, an example:
mysql> CREATE PROCEDURE simpleproc (OUT param1 INT)
-> BEGIN
-> SELECT COUNT(*) INTO param1 FROM t;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> CALL simpleproc(#a);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #a;
+------+
| #a |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
Yeah, you're right, with that second call you will now only get the name itself.
Out-Parameters for many people are generally considered bad practice, but they can be handy if you want a value that you can work with after the call (which could also be calculated by a function obviously). And in most cases there is a better way to achieve what you want without using out-parameters.
However the only "advantage" if you will is that you have a value in a variable instead of a result set which might seem more handy if you decide to use only that value further in your sql or whereever you want to work with it.
So in most cases you should really not use out-parameters, use functions instead.
If you have procedures that return result sets AND out-parameters try to break them down into smaller functions/procedures to avoid out-parameters, because it's just not nice to read and maintain ;)
Some documentation: http://dev.mysql.com/doc/refman/5.0/en/call.html maybe it will help, quote:
To get back a value from a procedure using an OUT or INOUT parameter,
pass the parameter by means of a user variable, and then check the
value of the variable after the procedure returns. (If you are calling
the procedure from within another stored procedure or function, you
can also pass a routine parameter or local routine variable as an IN
or INOUT parameter.) For an INOUT parameter, initialize its value
before passing it to the procedure. The following procedure has an OUT
parameter that the procedure sets to the current server version, and
an INOUT value that the procedure increments by one from its current
value:
CREATE PROCEDURE p (OUT ver_param VARCHAR(25), INOUT incr_param INT)
BEGIN
# Set value of OUT parameter
SELECT VERSION() INTO ver_param;
# Increment value of INOUT parameter
SET incr_param = incr_param + 1;
END;
The use of the OUT keyword is stated in the following article
13.2.1. CALL Syntax
CALL can pass back values to its caller using parameters that are
declared as OUT or INOUT parameters.
My question is pretty simple but answer might be tricky.
I'm in PHP and I want to manage manually a unique ID for my objects.
What is tricky is to manage atomicity. I dont want that 2 elements get the same ID.
"Elements" are grouped in "Groups". In each group I want elements ID starting from 1 and grow incrementally for each insert in that group.
My first solution is to have a "lastID" column in the table "Groups" :
CREATE TABLE groups ( id INT AUTO_INCREMENT, lastId INT )
CREATE TABLE elements ( myId INT, multiple values ...)
In order to avoid many elements with the same ID, I have to update lastId and select it in an atomic SQL Query.
After that, one retrieved, I have a unique ID that can't be picked again and I can insert my element.
My question is how to solve the bold part ? My database is MySQL with MyISAM engine so there is no transaction support.
UPDATE groups
SET lastId = lastId + 1
WHERE id = 42
SELECT lastId
FROM groups
WHERE id = 42
Is there something more atomic than these 2 requests ?
Thanks
UPDATE groups SET lastId = last_insert_id(lastId + 1)
and then you can get your new id with
SELECT last_insert_id()
Using last_insert_id with a parameter will store the value and return it when you call it later.
This method of generating autonumbers works best with MyISAM tables having only a few rows (MyISAM always locks the entire table). It also has the benefit of not locking the table for the duration of the transaction (which will happen if it is an InnoDB table).
This is from the MySQL manual:
If expr is given as an argument to LAST_INSERT_ID(), the value of the
argument is returned by the function and is remembered as the next
value to be returned by LAST_INSERT_ID(). This can be used to simulate
sequences:
Create a table to hold the sequence counter and initialize it:
CREATE TABLE sequence (id INT NOT NULL);
INSERT INTO sequence VALUES (0);
Use the table to generate sequence numbers like this:
UPDATE sequence SET id=LAST_INSERT_ID(id+1);
SELECT LAST_INSERT_ID();
The UPDATE statement increments the sequence counter
and causes the next call to LAST_INSERT_ID() to return the updated
value. The SELECT statement retrieves that value. The
mysql_insert_id() C API function can also be used to get the value.
See Section 21.8.3.37, “mysql_insert_id()”.
You can generate sequences without calling LAST_INSERT_ID(), but the
utility of using the function this way is that the ID value is
maintained in the server as the last automatically generated value. It
is multi-user safe because multiple clients can issue the UPDATE
statement and get their own sequence value with the SELECT statement
(or mysql_insert_id()), without affecting or being affected by other
clients that generate their own sequence values.
One option is for you to use the nifty MyISAM feature that let's auto_increment values be incremented for each group.
CREATE UNIQUE INDEX elements_ix1 ON elements (groupId, myID)
myID INT NOT NULL AUTO_INCREMENT
That's more "atomic" than anything that involves updating a separate table. Note that this only works for MyISAM, not InnoDB.
excerpt from http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html
MyISAM Notes
For MyISAM tables, you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. This is useful when you want to put data into ordered groups.
I would assume your MySQL installation also has InnoDB engine which does support transactions. You just need to change the engine type of you tables.
I want to put a timestamp when a specific column is updated.
For example:
column1: a value
dateColumn1: date column1 was updated
column2 : a value
dateColumn2: date column2 was updated
I use the function getTimestamp(), but it doesn't seem to work.
Can anyone advise me on how to do this in PHP and MYSQL?
Thanks.
If you want to do this only in the database, you could write a trigger that checks your conditions and updates specific timestamps if needed. But I'm assuming you don't want to fiddle around with triggers. Triggers have an advantage though: you can access the old and the new values of a row without having to write any php code.
Anyway, in case you need it here is some example code for a trigger (SQL, beware):
DELIMITER $$
DROP TRIGGER IF EXISTS Table1_UpdateTrigger $$
CREATE TRIGGER Table1_UpdateTrigger BEFORE UPDATE ON Table1
FOR EACH ROW BEGIN
IF OLD.column1 != NEW.column1 THEN
SET NEW.dateColumn1 = NOW();
END IF;
IF OLD.column2 != NEW.column2 THEN
SET NEW.dateColumn2 = NOW();
END IF;
END;
$$
DELIMITER ;
Substite Table1 with your real table names, column1, etc. with real column names.
The other way is to compare the old and the new values in php. E.g. do a query fetching the old data, compare the fields you want to check, and then do one update query per field that has changed to set the new timestamps.
UPDATE table
SET column1='new value', timestampcolumn=NOW()
WHERE ...
is one way. If you don't mind the timestamp changing anytime anything in the record is updated, then use the native "timestamp" field type, which'll update itself to "now" when the record's inserted or changed.
I prefer using the MySQL function NOW(), like so:
UPDATE table1 SET column2 = value, dateColumn2 = NOW() WHERE somethingsomething
Use a conditional statement. For example in the following trigger the 'password changed time' will be updated only when there is change in password column.
CREATE TRIGGER update_password
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF OLD.password <> NEW.password THEN
SET NEW.password_changed_on = NOW();
END IF;
END //
What is the best way to get the auto-id value in the same SQL with a SELECT?
A forum said adding this "; has Return Scope_Identity()"
in the end of the SQL works in ASP.
Is there a corresponding way in PHP?
It depends on your database server. Using MySQL, call mysql_insert_id() immediately after your insert query. Using PostgreSQL, first query "select nextval(seq)" on the sequence and include the key in your insert query.
Querying for "select max(id) + 1 from tbl" could fail if another request inserts a record simultaneously.
In postgres the best way is to do something like:
insert into foos(name) values ('my_foo') returning id;
It depends on the database engine you are using. Some DBMS, like Firebird for example, have RETURNING clause you can add to your query. For example, if you have a table named TABLE1 with autoincrement column named ID, you can use this:
insert into TABLE1(columns...) values (values...) returning ID;
And it would return the inserted ID just like a regular select statement.
In Microsoft Transact SQL you can use ##IDENTITY.
e.g.
DECLARE #Table TABLE ( col0 INT IDENTITY, col1 VARCHAR(255), col2 VARCHAR(255))
INSERT INTO #Table (col1, col2) VALUES ('Hello','World!')
SELECT ##Identity
SELECT * FROM #Table
In php: mysql_insert_id()
http://us3.php.net/mysql_insert_id
or
If you wanted to genterate the number from your mySql select query, you could use this
EDIT:
SELECT LAST_INSERT_ID(`1`) + 1 FROM table
Be very careful: Apparently select nextval(seq) does not work in high concurrency - some other connection can insert between the time when you inserted and the time when you called select nextval(seq). Always test such code in high concurrency test harnesses.
In SQL Server a insert using the select statement can have an output clause which will return the identity value and whatever other columns you might need to identify which identity goes to which record. If you are using a values clause, then use select scope_identity () immediately after the insert.