How to get RowsAffected in Go-Gorm's Association Mode - mysql

I insert a relation by the following code:
db.Where(exercise).FirstOrCreate(&exercise).Model(&User{ID: userID}).Association("Exercises").Append(&exercise)
Corresponding SQL printed by debug console to the code is:
INSERT INTO `user_exercise` (`user_id`,`exercise_id`) SELECT 1,1 FROM DUAL WHERE NOT EXISTS (SELECT * FROM `user_exercise` WHERE `user_id` = 1 AND `exercise_id` = 1)
I want know if there are new record created in user_exercise, but due to the generated SQL, if a relation in user_exercise already exists, it won't insert and produce no error.
Go-Gorm's Association object doesn't have a RowsAffected attribute, so I can't get RowsAffected from the query to confirm if a new record is created.
Though I can get RowsAffected from the first db object, like
db.Where(exercise).FirstOrCreate(&exercise).Model(&User{ID: userID}).Association("Exercises").Append(&exercise)
if db.RowsAffected == 1 {
// do something
}
I wonder since the db is shared by all queries, if another query executed at the same time and affected rows > 0, is it safe to get RowsAffected from the global db object?

Assuming that the user_execise table has an unique constraint (user_id, exercise_id) the insert should return an error if you try to do it to an already created record. (Exactly what you want)
So just do something like this...
db.Where(exercise).FirstOrCreate(&exercise)
ue := struct {
UserID uint
ExerciseID uint
}{
UserID: userID,
ExerciseID exercise.ID
}
if err := db.Table("user_exercise").Create(&ue).Error; err != nil {
// will enter here if it wasn't created
}
If it doesn't returns an Error means that a new record was created

Related

How to insert with select using GORM?

Is there a way to insert(create) rows with select in a single query using GORM?
What I am trying to do is to join two tables and insert rows into another table using a selected value from the table (using insert/select) but I'm having a hard time finding a way to call create along with select using GORM.
Basically what I hope to do can be done in a below SQL query:
INSERT INTO table_two (val, name, age)
SELECT table_one.some_value, '', 0
FROM table_one
WHERE table_one.some_value = 50
This inserts new rows into table_two with val column values set to the some_value of each of the matched rows in table_one.
Thanks in advance.
I was able to implement your request in two ways.
The first approach is to use a raw SQL Query (the same that you provide in your question). Thanks to the db.Exec() function you're able to run a raw SQL statement against your MySQL instance.
The second approach is to split the operation into two parts:
First you read the data with Where and Find
Second you insert the data with Create
Below, you can find both solutions:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type TableOne struct {
SomeValue int
Name string
Age int
}
type TableTwo struct {
Val int
Name string
Age int
}
func main() {
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
dsn := "root:root#tcp(127.0.0.1:3306)/todo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// first approach - Raw SQL
if dbTrn := db.Exec(`INSERT INTO table_twos (val, name, age)
SELECT table_ones.some_value, '', 0
FROM table_ones
WHERE table_ones.some_value = 50`).Error; dbTrn != nil {
panic(dbTrn.Error)
}
// second approach - split into two actions
var recordsOne []TableOne
recordsTwo := make([]TableTwo, 0)
db.Where("some_value=?", 50).Find(&recordsOne)
for _, v := range recordsOne {
recordsTwo = append(recordsTwo, TableTwo{Val: v.SomeValue, Name: "", Age: 0})
}
db.Create(&recordsTwo)
}
Let me know if this works also for you.

Yii2 mysql how to insert a record into a table where column value already exists and should be unique?

I have a table, say 'mytable' that use a "rank" column that is unique. After having created some record where rank is successively rec A(rank=0), rec B (rank=1), rec C (rank=2), rec D (rank=3), rec E (rank=4).
I need to insert a new record that will take an existing rank, say 1, and modify the rank value of the following records accordingly.
The result being : rec A(rank=0), new rec (rank=1), rec B (rank=2), rec C (rank=3), rec D (rank=4), rec E (rank=5).
How can I do this ? Can this be solved with mysql only or should I write some important bunch of code in PHP (Yii2) ?
Assuming no rank is skipped you need to shift existing ranks before saving the new record. To do that you can use beforeSave() method of your ActiveRecord like this:
class MyModel extends \yii\db\ActiveRecord
{
public function beforeSave($insert)
{
if (!parent::beforeSave($insert)) {
return false;
}
if ($insert) { //only if we are saving new record
{
$query = self::find()
->where(['rank' => $this->rank]);
if ($query->exists()) { //check if the rank is already present in DB
//we will create the query directly because yii2
// doesn't support order by for update
$command = static::getDb()->createCommand(
"UPDATE " . static::tableName() .
" SET rank = rank + 1 WHERE rank >= :rank ORDER BY rank DESC",
[':rank' => $this->rank]
);
$command->execute();
}
}
return true;
}
// ... other content of your model ...
}
MySQL allows use of ORDER BY in UPDATE query, that will help us deal with fact that doing UPDATE on table is not atomic and the UNIQUE constraint is checked after each row is updated.
It would be more problematic if there are skipped ranks. In that case you will need to shift ranks only until you hit first skipped rank.
Another option might be creating an before insert trigger on the table that would do the rank shifting.
Note:
It might be a good idea to also implement afterDelete method to shift the ranks in oposite direction when some record is removed to avoid skipped ranks.
Resources:
\yii\db\BaseActiveRecord::beforeSave()
\yii\db\ActiveRecord::updateAllCounters() - replaced with direct update
MySQL triggers
MySQL UPDATE syntax

Why won't resultSet.next() advance when there's clearly more data?

So I'm having trouble understanding why a resultSet from an executed query will not continue to advance when there's clearly more entries to iterate over. I have a table called all_projects, and when I run this query:
SELECT project_title, created_date, isActive FROM all_projects WHERE project_lead='myUser' ORDER BY created_date DESC;
in my PSQL shell, this is the result I get:
The column data types are: String, Timestamp, and Boolean.
I'm attempting to get each row, create an Array[String] from it, to create, ultimately, a list of an array of strings ( List[Array[String]] )
When I iterate over this with resultSet.next(), it is able to retrieve the first two values, but then when I call next after acquiring the first timestamp value, it fails and returns false.
Below is my code - riddled with a lot of println debug statements to see what happens, stack/and print trace will be at the bottom.
def getAll(userName: String, db: Database): List[Array[String]] = {
val tablesQuery = s"SELECT project_title, created_date, isActive FROM all_projects WHERE project_lead=? ORDER BY created_date DESC;"
var returnResult = new ListBuffer[Array[String]]
db.withConnection { conn =>
val ps = conn.prepareStatement(tablesQuery)
ps.setString(1, userName)
val qryResult = ps.executeQuery()
val columnCount = qryResult.getMetaData.getColumnCount
println("RETRIEVED THIS MANY COLUMNS: " + columnCount)
while (qryResult.next()) {
println("Achieved next in while loop >>>>>>>>>>>")
val row = new Array[String](columnCount)
for (i <- 0 to columnCount - 1) {
println(s"Inserting into Array($i) from the column index(${i + 1})")
if (i < 2) {
println("Tried to get string: ");
row(i) = qryResult.getString(i + 1)
} else { // note I also just tried to keep it all as .getString()
println("Tried to get boolean: ");
row(i) = qryResult.getBoolean(i+1).toString
} // retrieve by column index SQL columns start at 1
println("Row before we move on: " + row.mkString(", "))
if (i <= columnCount - 2) {
println("Called next? -> " + qryResult.next())
}
}
returnResult += row
}
}
returnResult.toList
}
And here is the resulting print stack, which should have been fine, but as you can see, returns false when attempting next() when the cursor is on the first timestamp value.
THIS MANY COLUMNS: 3
Achieved next in while loop >>>>>>>>>>>
Inserting into Array(0) from the column index(1)
Tried to get string:
Row before we move on: Wild Elephants of Mexico, null, null
Called next? -> true
Inserting into Array(1) from the column index(2)
Tried to get string:
Row before we move on: Wild Elephants of Mexico, 2017-08-05 11:00:44.078232, null
Called next? -> false
Inserting into Array(2) from the column index(3)
Tried to get boolean:
[error] o.j.ResultSetLogger - java.sql.ResultSet.getBoolean:
throws exception: org.postgresql.util.PSQLException: ResultSet not positioned properly, perhaps you need to call next.
org.postgresql.util.PSQLException: ResultSet not positioned properly, perhaps you need to call next.
What is happening here?
You are calling qryResult.next() within what you mean to be the handling of a single row, breaking everything. (Boy are you making your life much too difficult.)
A ResultSet represents a query result as a movable pointer to a single row of the query result. You handle rows one at a time, and then call next() only when you have fully completed handling of that row.
Let's make things much much simpler. (I'm just writing this out in a web page, don't have time to check or compile it, so please excuse my boo-boos.)
def handleRow( qryResult : ResultSet, columnCount : Int ) : Array[String] = {
(1 to columnCount).map( i => qryResult.getString(i) ).toArray
}
def getAll(userName: String, db: Database): List[Array[String]] = {
val tablesQuery = s"SELECT project_title, created_date, isActive FROM all_projects WHERE project_lead=? ORDER BY created_date DESC;"
db.withConnection { conn =>
val ps = conn.prepareStatement(tablesQuery)
ps.setString(1, userName)
val qryResult = ps.executeQuery()
val columnCount = qryResult.getMetaData.getColumnCount
println("RETRIEVED THIS MANY COLUMNS: " + columnCount)
var buffer = new ListBuffer[Array[String]]
while (qryResult.next()) {
buffer += handleRow( qryResult, columnCount )
}
buffer.toList
}
}

Select count return 0 mysql JDBC

I'm trying to store messages, so i create 2 tables in mysql with HeidiSql, the first one is msg and the second one is users. I wrote a store procedure to get the number of messages that have as sender the first parameter, as recipient the second parameter and viceversa, i stored this value in id variable. I need the variable to count the messages between 2 people. When i run the application from java, i always get id=0. I can't understand the mistake.
...
sql="call getIdMsg(?,?,?)";
sql2="call addRecord(?,?,?,?)";
try (Connection conn= DriverManager.getConnection(db_url,username,password);
CallableStatement statement = conn.prepareCall(sql);
CallableStatement statement2 = conn.prepareCall(sql2)){
statement.setInt(1,sender);
System.out.println(sender);
statement.setInt(2,recipient);
System.out.println(recipient);
statement.registerOutParameter(3, Types.INTEGER);
statement.execute();
id=statement.getInt(3);
System.out.println("ID is: "+id);
statement2.setInt(1,sender);
statement2.setInt(2,recipient);
statement2.setInt(3,id);
statement2.setString(4,msg);
statement2.execute();
System.out.println("The record is been added");
}
catch (SQLException sqle){sqle.printStackTrace();}
...
This is my sql code:
BEGIN
set #id = ((select count(*) from msg WHERE sender = #thisrecipient and recipient = #thissender)+(SELECT COUNT(*) FROM msg WHERE sender = #thissender and recipient = #thisrecipient));
END
These are my parameters:
thissender INT IN
thisrecipient INT IN
id INT OUT
Could you help me please?

How to persist tables with master-detail relationship within a single transaction?

I'm trying to persist two tables with master-detail relationship in MySQL 5.6 using Delphi XE3 and Zeos 7.0.4. When I do ApplyUpdates on the master, the auto increment field stays with 0 as value. I need the auto increment value, so I can link the detail table with the master table's ID field coming from ApplyUpdates. I'm using ZConnection with AutoCommit = FALSE and TransactionIsolationLevel = tiReadCommitted, ZQuery with CachedUpdates = TRUE. What am I missing?
ZQPerson.Append;
ZQEmployee.Append;
try
ZQPersonName.Value := Edit1.Text;
ZQPerson.ApplyUpdates; //Here I expected to have the auto increment value on the Id field of ZQPerson, but it returns always 0
ZQEmployeePersonID.Value := ZQPersonId.Value; //Here I'd link Employee to it's Person record
ZQEmployeeRegNo.Value := StrToInt(Edit2.Text);
ZQEmployee.ApplyUpdates;
ZConnection1.Commit; //Here I would persist both tables in a single transaction to avoid master table without details
except
ZQPerson.CancelUpdates;
ZQEmployee.CancelUpdates;
ZConnection1.Rollback; //In case of exceptions rollback everything
raise;
end;
ZQPerson.CommitUpdates;
ZQEmployee.CommitUpdates;
My ZSQLMonitor trace is this:
2013-08-29 00:01:23 cat: Execute, proto: mysql-5, msg: INSERT INTO person (Id, name) VALUES (NULL, 'Edit1') --> This is just after ZQPerson.ApplyUpdates
2013-08-29 00:01:50 cat: Execute, proto: mysql-5, msg: INSERT INTO employee (Id, RegNo, ProductId) VALUES (NULL, 1000, 0), errcode: 1452, error: Cannot add or update a child row: a foreign key constraint fails (`test`.`employee`, CONSTRAINT `FK_A6085E0491BDF8EE` FOREIGN KEY (`PersonId`) REFERENCES `person` (`Id`) --> This is just after ZQEmployee.ApplyUpdates
2013-08-29 00:02:05 cat: Execute, proto: mysql-5, msg: Native Rollback call --> Rollback after Exception on the ZQEmployee.ApplyUpdates
Are you starting the transaction with ZConnection1.StartTransaction? I think too that you must Refresh ZQuery1 after calling ZQuery1.ApplyUpdates to get the new id-
Reading your comment, you must be doing a select * without a where clause? right? I can recommend you use this approach:
1) select and increment the current autoincrement value
2) select from master table where id=[step1 id] // it will be empty, of course
3) add detail using the id in step 1
4) assign the id in the master dataset
5) apply both updates
The workaround I found was this one. It not satiesfies me completely because it doesn't make transparent the use of the database's auto increment feature, making me use Last_Insert_ID() function. I'm in contact with zeos develpers to check this out.
function LastInsertID(ATableName: string): Integer;
var DBQuery: TZQuery;
begin
DBQuery := TZQuery.Create(Self);
with DBQuery do
begin
Connection := ZConnection1;
SQL.Clear;
SQL.Add('Select Last_Insert_ID() as Last_Insert_ID from ' + ATableName);
Open;
Result := FieldByName('Last_Insert_ID').Value;
Free;
end;
end;
procedure Persist;
var LastID: Integer;
begin
ZQPerson.Append;
ZQEmployee.Append;
try
ZQPersonName.Value := Edit1.Text;
ZQPerson.ApplyUpdates; // Here I expected to have the auto increment value on the Id field of ZQPerson, but it returns always 0
LastID := LastInsertID('Person'); //Getting the Last_Insert_ID(), even on the uncommitted transction, works
ZQEmployeePersonId.Value := LastID; //Link the two tables using the Last_Insert_ID() result
ZQEmployeeRegNo.Value := StrToInt(Edit2.Text);
ZQEmployee.ApplyUpdates;
ZConnection1.Commit; // Here I persist both tables in a single transaction to avoid master table without details
except
ZQPerson.CancelUpdates;
ZQEmployee.CancelUpdates;
ZConnection1.Rollback; // In case of exceptions rollback everything
raise;
end;
ZQPerson.CommitUpdates;
ZQEmployee.CommitUpdates;
I tested it in a simple database with two master and detail tables nested with TDataSource and relating by the where of the detail table:
object conMysql: TZConnection
     TransactIsolationLevel = tiReadCommitted
object zqryMaster: TZQuery
     Connection = conMysql
SQL.Strings = (
       'select * from temp.master')
object dsNestedMaster: TDataSource
     DataSet = zqryMaster
object zqryDetail: TZQuery
     Connection = conMysql
     SQL.Strings = (
       'select * from temp.detail'
       'where id_master =: id')
After starting the transaction all updates must wait for confirmation or rollback if an error occurs:
try
zqryMaster.Connection.StartTransaction;
zqryMaster.Edit;
zqryDetail.Edit;
zqryMaster.FindField('dt_mov').Value := Now;
while not zqryDetail.Eof do
begin
zqryDetail.Edit;
zqryDetail.FindField('dt_mov').Value := Now;
zqryDetail.ApplyUpdates;
zqryDetail.Next;
//raise Exception.Create('simple error'); //use for tests, check database after perform
end;
zqryMaster.ApplyUpdates;
zqryMaster.Connection.Commit;
except
zqryMaster.Connection.Rollback;
zqryMaster.CancelUpdates;
zqryDetail.CancelUpdates;
end;