I'm running a little method in CodeIgniter to insert some lines in the database (same table). I would like to see which insertion have failed inside a transaction (by returning an array of titles). My code is :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if (($this->db->_error_message())!=null) {
$failure[] = $ressourceCsv['title'];
}
}
$this->db->trans_complete();
return $failure;
The fact is that if I don't make it a transaction (no $this->db->trans_...), it works perfectly and I have an array containing a few titles. But with the transaction, the array contains every titles since the first error. Is there a way to just get the title from the insertion which caused the transaction to rollback?
I have also tried with :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
if (!$this->ajout_ressource($ressourceCsv)) { //active record insertion return true
$failure[] = $ressourceCsv['title']; // if successful
}
}
$this->db->trans_complete();
return $failure;
I believe that once an error occurs inside a transaction, you must rollback before any more DB mods can be made. That would explain the behavior you are seeing. After the first error, the transaction is "aborted" and you continue your loop, causing every subsequent SQL command to fail as well. This can be illustrated as follows:
db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=# begin;
BEGIN
db=# insert into test1 (foo, bar) values (1, 'One');
INSERT 0 1
db=# insert into test1 (foo, bar) values (Oops);
ERROR: column "oops" does not exist
LINE 1: insert into test1 (foo, bar) values (Oops);
^
db=# insert into test1 (foo, bar) values (2, 'Two');
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# select * from test1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# commit;
ROLLBACK
ace_db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=#
Note it seems that "commit" does a "rollback" if there was an error (it was not a typo.)
Also BTW: use $this->db->trans_status() === FALSE to check for an error during the transaction.
Update: Here's some (untested) code to do it in a transaction so that the inserts are not seen by others until you are ready:
$failure = array(); //the array where we store what failed
$done = false;
do {
$this->db->trans_begin();
foreach ($data as $key => $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if ($this->db->trans_status() === false) { // an insert failed
$failure[] = $ressourceCsv['title']; // save the failed title
unset($data[$key]); // remove failed insert from data set
$this->db->trans_rollback(); // rollback the transaction
break; // retry the insertion
}
}
$done = true; // completed without failure
} while (count($data) and ! $done); // keep going until no data or success
/*
* Two options (uncomment one):
* 1. Commit the successful inserts even if there were failures.
$this->db->trans_commit();
* 2. Commit the successful inserts only if no failures.
if (count($failure)) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
}
*/
return $failure;
Related
There is a condition if not satisfied then I just want to return no rows as my application will pick no row and will show no record msg on front end. Is there any other professional way?
For now I am using following query to return no row.
select 0
where 1 = 0
Even when you return no rows, you are still returning a schema. And most applications expect the same schema to be returned regardless of the number of rows. Even when 0 rows are returned.
If you can change the SQL in #SqlStr that you are executing with sp_executesql, I would insert into a temporary table in that query and then return the results of selecting from that temporary table:
Select * from #myTempTable where <conditionRequiredForResults>
You said you want to return no rows on your frontend and return nothing. Neither message nor row.
You just need to use if statement to avoid returning anything to your frontend
require_once 'db_connection.php';
$sql = "SELECT * FROM table_name WHERE <your filtering Condition>";
$result = mysqli_query($db_connection, $sql);
if (mysqli_num_rows($result) > 0) {
// If row to be fetched is more than 0
// Return something Example:
echo "Rows detected";
} else {
// If there is no row to be fetched
// Return nothing
}
And this should return nothing if no row fetched from database
my values
user = [[34, 'Victoria', '17:34:50', None], [40, 'Meherin', '00:04:00', '23:56:10'], [30, 'Micahle', '18:58:43', None]]
I have a postgresql function the name of merge_db() and it takes 4 argument. Now i want to insert value from user with python.
postgresql function.
CREATE FUNCTION merge_db(id1 integer, name1 character varying, login1 time, logout1 time) RETURNS VOID AS
$$
BEGIN
LOOP
-- first try to update the id
UPDATE my_company SET (name, login, logout) = (name1, login1, logout1) WHERE id = id1;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO my_company(id, name, login, logout) VALUES (id1, name1, login1, logout1);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Do nothing, and loop to try the UPDATE again.
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
my python code such like
insert_query = "SELECT merge_db(%s) values %s"
execute_values(cur, insert_query, user)
conn.commit()
In this case throwing ValueError "ValueError: the query contains more than one '%s' placeholder"
I don't understand clearly that how to send user values as a merger_db argument.
Any help would be appreciated.
Thanks.
for i in user:
print(i[0], i[1], i[2], i[3], )
insert_query = "SELECT merge_db({}, '{}', '{}', '{}')".format(i[0], i[1], i[2], i[3]
cur.execute(insert_query)
It'll work good but will raise error duplicate key error.
Strange issue. I have a managed OPD.Net application that calls a stored procedure to insert records. When I call the procedure normally and rollback the transaction, the records are not saved to the table (duh!). When I use the procedure with parameter array binding and still roll back the transaction, the records ARE saved to the table. Somehow, the records are getting committed even though I do a rollback!
TEST Schema:
CREATE TABLE TEST
(
ID NUMBER(15,0),
VALUE VARCHAR2(50 CHAR)
)
/
CREATE SEQUENCE TEST_ID_SEQ
INCREMENT BY 1
START WITH 50
MINVALUE 1
MAXVALUE 999999999999999
NOCYCLE
NOORDER
CACHE 100
/
CREATE OR REPLACE PROCEDURE TEST_INSERT
(
iVALUE IN VARCHAR2,
oID OUT NUMBER
)
AS
BEGIN
oID := TEST_ID_SEQ.NEXTVAL;
INSERT INTO TEST
(
ID,
VALUE
)
VALUES
(
oID,
iVALUE
);
END;
/
Test Code:
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
using System;
using System.Data;
namespace OdpTestArrayBinding
{
class Program
{
private const string cConnectioString = "Data Source=DB_DEV;User Id=TMP;Password=sqlsql";
static void Main(string[] args)
{
using (OracleConnection lConnectionA = new OracleConnection(cConnectioString))
{
lConnectionA.StateChange += ConnectionStateChanged;
lConnectionA.Open();
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Connection opened.");
int lStartCount = CountTestTableRows(lConnectionA);
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Number of rows in table at start is {lStartCount}.");
using (OracleTransaction lTransaction = lConnectionA.BeginTransaction())
{
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Transaction started.");
try
{
using (OracleCommand lCmd = new OracleCommand())
{
lCmd.Connection = lConnectionA;
lCmd.BindByName = true;
lCmd.CommandType = System.Data.CommandType.StoredProcedure;
lCmd.CommandText = "TEST_INSERT";
lCmd.Parameters.Add("iVALUE", OracleDbType.Varchar2, ParameterDirection.Input);
// The OracleDbType of the output does not seem to matter, the actual value is always OracleDecimal
lCmd.Parameters.Add("oID", OracleDbType.Int64, ParameterDirection.Output);
lCmd.ArrayBindCount = 3;
lCmd.Parameters["iVALUE"].Value = new string[] { "Foo", "Bar", "Boo" };
// Not required.
//lCmd.Parameters["oID"].Value = new long[] { -1, -1, -1 };
lCmd.ExecuteNonQuery();
OracleDecimal[] lOutIds = (OracleDecimal[])lCmd.Parameters["oID"].Value;
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Inserted 3 rows using stored procedure, out ID vales are {string.Join(",", lOutIds)}.");
}
ListRows(lConnectionA, lStartCount + 3);
using (OracleConnection lConnectionB = new OracleConnection(cConnectioString))
{
lConnectionB.StateChange += ConnectionStateChanged;
lConnectionB.Open();
Console.WriteLine($"[Connection={lConnectionB.GetHashCode()}] Connection opened.");
ListRows(lConnectionB, lStartCount);
}
}
finally
{
lTransaction.Rollback();
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Transaction rolled back.");
}
}
}
Console.WriteLine("Press the ENTER key to continue...");
Console.ReadLine();
}
private static void ConnectionStateChanged(object sender, StateChangeEventArgs e)
{
Console.WriteLine($"[Connection={sender.GetHashCode()}] State changed from {e.OriginalState} to {e.CurrentState}.");
}
private static int CountTestTableRows(OracleConnection aConnection)
{
using (OracleCommand lCmd = new OracleCommand())
{
lCmd.Connection = aConnection;
lCmd.BindByName = true;
lCmd.CommandType = System.Data.CommandType.Text;
lCmd.CommandText = "SELECT COUNT(*) FROM TEST";
return Convert.ToInt32(lCmd.ExecuteScalar());
}
}
private static void ListRows(OracleConnection aConnection, int aExpectedRowCount)
{
int lCount = CountTestTableRows(aConnection);
Console.Write($"[Connection={aConnection.GetHashCode()}] Number of rows in table {lCount}");
if (lCount == aExpectedRowCount)
Console.WriteLine(" (Test passed, actual and expected row count are the same).");
else
Console.WriteLine($" (Test FAILED!, expected {aExpectedRowCount} rows).");
}
}
}
First run console output:
[Connection=47973293] State changed from Closed to Open.
[Connection=47973293] Connection opened.
[Connection=47973293] Number of rows in table at start is 0.
[Connection=47973293] Transaction started.
[Connection=47973293] Inserted 3 rows using stored procedure, out ID vales are 50,51,52.
[Connection=47973293] Number of rows in table 3 (Test passed, actual and expected row count are the same).
[Connection=21040294] State changed from Closed to Open.
[Connection=21040294] Connection opened.
[Connection=21040294] Number of rows in table 3 (Test FAILED!, expected 0 rows).
[Connection=21040294] State changed from Open to Closed.
[Connection=47973293] Transaction rolled back.
[Connection=47973293] State changed from Open to Closed.
I have tried Oracle.ManagedDataAccess.dll 4.121.2.20141216 (ODAC RELEASE 3) and 4.121.2.20150926 (ODAC RELEASE 4), both give the same result. Any ideas or workarounds?
Can you try to change your code to use this example.
Open only a using block for your connection.
public void RunOracleTransaction(string connectionString)
{
using (OracleConnection connection = new OracleConnection(connectionString))
{
connection.Open();
OracleCommand command = connection.CreateCommand();
OracleTransaction transaction;
// Start a local transaction
transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
// Assign transaction object for a pending local transaction
command.Transaction = transaction;
try
{
command.CommandText =
"INSERT INTO Dept (DeptNo, Dname, Loc) values (50, 'TECHNOLOGY', 'DENVER')";
command.ExecuteNonQuery();
command.CommandText =
"INSERT INTO Dept (DeptNo, Dname, Loc) values (60, 'ENGINEERING', 'KANSAS CITY')";
command.ExecuteNonQuery();
transaction.Commit();
Console.WriteLine("Both records are written to database.");
}
catch (Exception e)
{
transaction.Rollback();
Console.WriteLine(e.ToString());
Console.WriteLine("Neither record was written to database.");
}
}
}
Pleaase check your command : you have forget assign the transaction on your command?
lCmd.Connection = lConnectionA;
// Assign transaction to your command
lCmd.Transaction = lTransaction;
You should create a TransactionScope that wraps your database connection and within your TransactionScope, try enlisting in the ambient transaction:
<your connection object>.EnlistTransaction(Transaction.Current);
In my database I have:
Row ID - Driver ID - Log ID.
Row ID is unique and auto-increments. What I want is for the Log ID to be unique for each row that has that Driver ID.
For example say a row is inserted with Driver ID 1 I want that row to have a Log ID of 1 but the next time a row is inserted with Driver ID 1 I want it to have a Log ID of 2.
How can I achieve this?
By way for database i am using PHPMyAdmin.
----------------Edit----------------------
This is what i have in my PHP now, but it says:
On the webpage: Incorrect integer value: '' for column 'FinesCost' at row 1
And i dump the variables and get this: string(2) "16" string(2) "16" string(2) "16" so i dont understand why it is saying incorrect integer value and why it is saying they are undefines because they are very clearly defined.
In the PHP error log: [19-Jul-2013 10:44:18 Europe/Minsk] PHP Notice: Undefined variable: FinesCostPost2 in C:\inetpub\wwwroot\hosting\Dan\JWT\drivers-log-send.php on line 336
[19-Jul-2013 10:44:18 Europe/Minsk] PHP Notice: Undefined variable: TravelExpensesPost2 in C:\inetpub\wwwroot\hosting\Dan\JWT\drivers-log-send.php on line 336
///PHP TO INSERT DRIVER'S BANK DETAILS INTO BANK DATABASE
session_start();
$host=""; // Host name
$username=""; // Mysql username
$password=""; // Mysql password
$db_name=""; // Database name
$tbl_name="jwtdriversbank"; // Table name
$un = "";
$usrname = "";
$usrpass = "";
$userID = "";
mysql_connect("$host", "$username", "$password")or die("cannot connect");
mysql_select_db("$db_name")or die("cannot select DB");
if(isset ($_SESSION['usrName']))
{
$usrname = $_SESSION['usrName'];
}
else
{
echo "4";
}
//var_dump ($usrname);
if(isset ($_SESSION['usrPass']))
{
$usrpass = $_SESSION['usrPass'];
}
else
{
echo "5";
}
$sql="SELECT * FROM jwtdrivers WHERE username='$usrname' and password='$usrpass'";
$result=mysql_query($sql);
$rows=mysql_fetch_array($result);
$userID = $rows['id'];
//var_dump ($userID);
if($userID == "")
{
echo "3";
}
else
{
$TotalProfitPost = $TotalProfit;
$LateFeePost = $LateFee;
$FinesCostPost2 = $FinesCost;
$TravelExpensesPost2 = $TravelExpenses;
$FuelCostPost = $FuelCost;
$CargoDamagePost = $CargoDamage;
$TruckDamagePost = $TruckDamage;
var_dump ($TotalProfitPost);
var_dump($FinesCostPost2);
var_dump($TravelExpensesPost2);
$sql="INSERT INTO jwtdriversbank2 (DriverID, LogID, TotalProfit, LateFee, FinesCost, TravelExpenses, FuelCost, CargoDamage, TruckDamage) VALUES ('$userID', COALESCE((Select MAX(LogID) from jwtdriversbank2 tab2 where tab2.DriverID = '$userID'),0)+1,'$TotalProfitPost','$LateFeePost', '$FinesCostPost2' , '$TravelExpensesPost2' ,'$FuelCostPost','$CargoDamagePost','$TruckDamagePost')";
$result = mysql_query($sql);
if($result)
{
}
else
{
die(mysql_error());
}
}
Add a primary key for the two columns.
It should do the trick.
Look at this link for help
ALTER TABLE table_name
ADD CONSTRAINT pk_DriverID PRIMARY KEY (DriverID,LogID)
Do not forget to drop the first primary key because you will not need it no more.
EDIT : COMPLETE WITH THE OTHER ANSWER
Here is the code to insert your data.
Insert into <table_name>
values p_RowID, p_DriverID, COALESCE((Select MAX(Log_id) from <table_name> tab2 where tab2.Driver_id = p_DriverID),0)+1;
That should close the question.
You did not defined variable because PHP can't read them.
I opened your program inside VIM editor and I found "<200c>" char inside $FineCostPost2 in the SQL query. You have to change it to make it work.
A quick solution would be to use a subquery to find the maximum log (last log id) then increment it, something like this
Insert into <table_name>
values p_RowID, p_DriverID, COALESCE((Select MAX(Log_id) from <table_name> tab2 where tab2.Driver_id = p_DriverID),0)+1;
Here p_RowID and p_DriverID are the values you pass to insert into your table. The Coalesce function would check the given value and if it is NULL then it would replace it with the second parameter, in this case 0
I've a PostgreSQL 9.0 server and I'm using heritage on some tables, for this reason I have to simulate foreign keys through triggers like this:
CREATE OR REPLACE FUNCTION othertable_before_update_trigger()
RETURNS trigger AS
$BODY$
DECLARE
sql VARCHAR;
rows SMALLINT;
BEGIN
IF (NEW.parenttable_id IS DISTINCT FROM OLD.parenttable_id) THEN
sql := 'SELECT id '
|| 'FROM parentTable '
|| 'WHERE id = ' || NEW.parenttable_id || ';';
BEGIN
EXECUTE sql;
GET DIAGNOSTICS rows = ROW_COUNT;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error when I try find in parentTable the id %. SQL: %. ERROR: %',
NEW.parenttable_id,sql,SQLERRM;
END;
IF rows = 0 THEN
RAISE EXCEPTION 'Not found a row in parentTable with id %. SQL: %.',NEW.parenttable_id,sql;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
But due to performance I try to create a equivalent trigger in C code:
#include "postgres.h"
#include "executor/spi.h" /* this is what you need to work with SPI */
#include "commands/trigger.h" /* ... and triggers */
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
extern Datum othertable_before_update_trigger(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(othertable_before_update_trigger);
Datum
othertable_before_update_trigger(PG_FUNCTION_ARGS) {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
TupleDesc tupdesc;
HeapTuple rettuple;
bool isnull;
int ret, i;
/* make sure it's called as a trigger at all */
if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "othertable_before_update_trigger: not called by trigger manager");
/* tuple to return to executor */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
rettuple = trigdata->tg_newtuple;
else
rettuple = trigdata->tg_trigtuple;
tupdesc = trigdata->tg_relation->rd_att;
/* connect to SPI manager */
if ((ret = SPI_connect()) < 0)
elog(ERROR, "othertable_before_update_trigger (fired %s): SPI_connect returned %d", "before", ret);
[A]
[B]
return PointerGetDatum(rettuple);
}
I need fill the code in:
[A]: get the previous and new values for parenttable_id. With:
int32 att = DatumGetInt32(heap_getattr(rettuple, 1, tupdesc, &isnull));
or
int32 att = DatumGetInt32(SPI_getbinval(rettuple, tupdesc, 1, &isnull));
I can get only the old value of parenttable_id but not the new value. Even if I try to use the column name instead of their number with:
GetAttributeByName (rettuple->t_data, "parenttable_id", &isnull);
Getting error: record type has not been registered
[B]: execute the query SELECT id FROM parentTable WHERE id = NEW.parenttable_id
I found the function SPI_execute_with_args, but I haven't found examples of this for my case.
Thanks in advance.
This does not strike me as the sort of trigger that will benefit from moving to C. You can take advantage of a lot of caching of plans in pl/pgsql and this is likely to help more than moving to C will speed things up. Additionally there are two big performance red flags here that strike me as worth fixing.
The first is that EXCEPTION blocks have significant performance costs. All you are doing here is reporting an exception in more friendly terms. You would do better to just remove it if performance is an issue.
The second is your EXECUTE which means that the query plan will never be cached. You really should change this to a straight query.
When you combine this with the possibility of a C-language trigger causing crashes or worse in the back-end, I think you will be putting a lot of effort into rewriting the trigger for fewer performance gains than you could get by rewriting it in pl/pgsql.