Why is this SQLite Script Affecting Multiple Rows? - c#

I have the following simple SQLite script to create a new database with a versioning table:
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS `db_versions` (
`version` integer NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`name` varchar ( 50 ) DEFAULT NULL UNIQUE,
`date_defined` datetime DEFAULT NULL,
`comments` text
);
INSERT INTO `db_versions` VALUES (0,'initial-create','2017-12-02 14:41:56',NULL);
COMMIT;
Running this script in the DB Browser for SQLite logs correctly logs that only 1 row is affected (inserted). However, when I try to execute this script in code with Mono (Mono.Data.Sqlite), the script apparently affects 2 rows. Here is that code:
using (var conn = new SqliteConnection(_connStr)) {
await conn.OpenAsync(cancellationToken);
using (SqliteCommand comm = conn.CreateCommand()) {
comm.CommandType = CommandType.Text;
comm.CommandText = #"
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS `db_versions` (
`version` integer NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`name` varchar ( 50 ) DEFAULT NULL UNIQUE,
`date_defined` datetime DEFAULT NULL,
`comments` text
);
INSERT INTO `db_versions` VALUES (0,'initial-create','2017-12-02 14:41:56',NULL);
COMMIT;
";
int rowsAffected = await comm.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 1) {
// Why is this code running??
}
}
}
Does anyone know why I'm getting these different results?

Gah, I figured it out. The integer version field was being defined AUTOINCREMENT, which also adds the largest ROWID to an internal sqlite_sequence table. So there was a 2nd row being added.

Related

How do I get primary key of colliding row on failed insert?

After I try to insert a row into database and it fails due to duplicate (not primary key collision - another key), how do I get the primary key of the colliding row that prevented the insert?
Example:
The table:
CREATE TABLE `my_table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`unique_value` varchar(60) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_data` (`unique_values`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Some initial data:
INSERT INTO `my_table`(`unique_value`) VALUES ('unique string 1');
INSERT INTO `my_table`(`unique_value`) VALUES ('unique string 2');
And now, this will fail because of the collision in the index:
MySqlConnection conn = new MySqlConnection(connString);
conn.Open();
MySqlCommand comm = conn.CreateCommand();
comm.CommandText = "INSERT INTO `my_table`(`unique_value`) VALUES 'unique string 2'";
comm.ExecuteNonQuery();
conn.Close();
Can I get the primary key of the colliding row without making another query?
while creating the table you can use "Identity" keyword which will do all the "not null and Auto Increment" Stuff.
But this is for MsSQL
Try this:
create table my_table
(
ID int Primary key identity,
unique_value varchar(60) NOT NULL
)
As you said this that you are not worry about PK hence you can check the value of the column which has unique index.If there is already a raw with the value that you passing in new row then you are trying to insert duplicate otherwise not.

Foreign Key conflict on INSERT statement

I'm developing a WinForm desktop application for users to input employees retirement data, using SQL Server 2008 as DB.
One of the tables that gets part of the user data has a reference to another table whose records were defined at design time, adding a Foreign Key constraint for consistency.
CREATE TABLE tbCongedo (
ID int IDENTITY(0,1) PRIMARY KEY,
ID_ANAGRAFICA int NOT NULL,
ID_TIPOLOGIA int NOT NULL,
DECORRENZA datetime NOT NULL,
PROT_NUM nvarchar(7) NOT NULL,
PROT_DATA datetime NOT NULL
);
CREATE TABLE tbTipologia (
ID int IDENTITY(0,1) PRIMARY KEY,
TIPOLOGIA nvarchar(20)
);
INSERT INTO tbTipologia VALUES ('CONGEDO'), ('POSTICIPO'), ('ANTICIPO'), ('REVOCA'), ('DECESSO')
ALTER TABLE tbCongedo
ADD CONSTRAINT FK_tbCongedo_tbTipologia
FOREIGN KEY (ID_TIPOLOGIA) REFERENCES tbTipologia(ID)
Then, I have this code that should execute the INSERT statement
public int Insert(SqlConnection Connessione)
{
using (SqlCommand Comando = new SqlCommand("INSERT INTO tbCongedo VALUES (#ID_ANAGRAFICA, #PROT_NUM, #PROT_DATA, #DECORRENZA, #ID_TIPOLOGIA); SELECT SCOPE_IDENTITY()", Connessione))
{
Comando.Parameters.AddWithValue("#ID_ANAGRAFICA", ID_ANAGRAFICA);
Comando.Parameters.AddWithValue("#PROT_NUM", PROT_NUM);
Comando.Parameters.AddWithValue("#PROT_DATA", PROT_DATA);
Comando.Parameters.AddWithValue("#DECORRENZA", DECORRENZA);
Comando.Parameters.AddWithValue("#ID_TIPOLOGIA", ID_TIPOLOGIA);
ID = Int32.Parse(Comando.ExecuteScalar().ToString());
}
return ID;
}
but I'm given this SqlException:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_tbCongedo_tbTipologia". The conflict occurred in database "Scadenziario_ver4_TEST", table "dbo.tbTipologia", column 'ID'
These are the data that I was trying to get inserted in the table:
ID_ANAGRAFICA = 2
ID_TIPOLOGIA = 0
PROT_DATA = {16/03/2018 00:00:00}
DECORRENZA = {16/03/2018 00:00:00}
PROT_NUM = 123456
Funny thing is that when I try to insert those same data manually through SQL Server Management Studio, I'm given no error at all.
Thanks.-
Try specifying fields: (col_name1, col_name2, ...).
Without that the VALUES may not be applied as how you might hope. Variable names are NOT automagically matched with similarly-named columns.
So like this:
... new SqlCommand
(
"INSERT INTO tbCongedo " +
" (ID_ANAGRAFICA, PROT_NUM, PROT_DATA, DECORRENZA, ID_TIPOLOGIA) "
"VALUES (#ID_ANAGRAFICA, #PROT_NUM, #PROT_DATA, #DECORRENZA, #ID_TIPOLOGIA); " +
"SELECT SCOPE_IDENTITY()", Connessione
)
...
I think the problem isn't in the data but in the INSERT statement itself. You are trying to insert the values to the wrong columns using the wrong order. To solve the issue you should either specify the columns in the INSERT statement or correct the order of the values. In your case the query will try to insert the value of #PROT_NUM in the ID_TIPOLOGIA instead.

How to execute transactions (or multiple sql queries) in firebird using c#

I have tried several ways, including on SO.
The following MYSQL code does not work in Firebird:
CREATE TABLE publications (
INT NOT NULL AUTO_INCREMENT ,
PRIMARY KEY (`id`),
filename varchar(500) not null unique,
title varchar(500) DEFAULT NULL,
authors varchar(1000) DEFAULT NULL,
uploader int DEFAULT NULL,
keywords varchar(500) DEFAULT NULL,
rawtext text,
lastmodified timestamp default CURRENT_TIMESTAMP
);
So to achieve this in Firebird, I am using:
CREATE TABLE publications (
id int NOT NULL PRIMARY KEY,
filename varchar(500) NOT NULL UNIQUE,
title varchar(500) DEFAULT NULL,
authors varchar(1000) DEFAULT NULL,
uploader int DEFAULT NULL,
keywords varchar(500) DEFAULT NULL,
rawtext text,
file_data BLOB SUB_TYPE 0,
insertdate timestamp DEFAULT NULL
);
CREATE GENERATOR gen_t1_id;
SET GENERATOR gen_t1_id TO 0;
set term !! ;
CREATE TRIGGER journalInsertionTrigger FOR publications
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.ID is NULL) then NEW.ID = GEN_ID(GEN_T1_ID, 1);
END!!
set term ; !!
And with the above, I get the error:
Batch execution aborted The returned message was: Dynamic SQL Error SQL error code = -104 Token unknown - line 13, char 2 CREATE"
When I uncomment //FbTransaction fbt = Connection.BeginTransaction(); and //fbt.Commit();
Execute requires the Command object to have a Transaction object when
the Connection object assigned to the command is in a pending local
transaction. The Transaction property of the Command has not been
initialized.
I am using the following C# code:
//FbTransaction fbt = Connection.BeginTransaction(); //
FbBatchExecution fbe = new FbBatchExecution( Connection );
fbe.SqlStatements = new System.Collections.Specialized.StringCollection();//.Add( queryString ); // Your string here
fbe.SqlStatements.Add( queryString ); // Your string here
fbe.Execute();
//fbt.Commit();
NB: Setting set term ; !! at the beginning of the sql code gives the error: The type of the SQL statement could not be determinated
How do I do this?
Firebird can only execute individual SQL statements, and most drivers for Firebird follow that same rule. You cannot execute a script at once like this.
The Firebird.net provider contains a utility class to split scripts into individual statements.
You need to do something like:
using (var connection = new FbConnection(#"User=sysdba;Password=masterkey;Database=D:\data\db\testdatabase.fdb;DataSource=localhost"))
{
connection.Open();
FbScript script = new FbScript(dbScript);
script.Parse();
FbBatchExecution fbe = new FbBatchExecution(connection);
fbe.AppendSqlStatements(script);
fbe.Execute();
}
Note that for your current script to work you also need to replace:
rawtext text,
with
rawtext BLOB SUB_TYPE TEXT CHARACTER SET UTF8
Technically you could leave off the character set clause, but unless you defined a default character set for your database, you should specify the character set otherwise it will be NONE which might lead to problems later.
You cannot start a transaction yourself when you use FbBatchExecution, because the transaction is handled internally in the Execute method. Note that if you also want to insert (or otherwise modify) data in the script, then you should use Execute(true), so that each DDL statement is committed immediately. Firebird doesn't allow DDL changes in a transaction to be used by DML in the same transaction.
The problem with SET TERM is caused by the fact that SET TERM is not part of the Firebird syntax. It is part of the syntax used by tools like ISQL and FlameRobin, and for example FbScript.
If you want to execute these statements individually and have control over the transaction, you'd do something like:
using (var connection = new FbConnection(#"User=sysdba;Password=masterkey;Database=D:\data\db\testdatabase.fdb;DataSource=localhost"))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
using (var command = new FbCommand())
{
command.Connection = connection;
command.Transaction = transaction;
command.CommandText = #"CREATE TABLE publications (
id int NOT NULL PRIMARY KEY,
filename varchar(500) NOT NULL UNIQUE,
title varchar(500) DEFAULT NULL,
authors varchar(1000) DEFAULT NULL,
uploader int DEFAULT NULL,
keywords varchar(500) DEFAULT NULL,
rawtext BLOB SUB_TYPE TEXT CHARACTER SET UTF8,
file_data BLOB SUB_TYPE 0,
insertdate timestamp DEFAULT NULL
)";
command.ExecuteNonQuery();
command.CommandText = "CREATE GENERATOR gen_t1_id";
command.ExecuteNonQuery();
command.CommandText = #"CREATE TRIGGER journalInsertionTrigger FOR publications
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.ID is NULL) then NEW.ID = GEN_ID(GEN_T1_ID, 1);
END";
command.ExecuteNonQuery();
transaction.Commit();
}
}
As Mark pointed, Firebird can execute only individual statements. If you group them into a block, then the block has its own transaction and you can't start the transaction yourself. I worked around this issue by creating an extension method ExecuteBatch() that you can use instead of ExecuteNonQuery(). Here's the code:
static class FbCommandExtension
{
public static void ExecuteBatch(this FbCommand cmd)
{
var script = new FbScript(cmd.CommandText);
script.Parse();
foreach (var line in script.Results)
{
using (var inner = new FbCommand(line.Text, cmd.Connection, cmd.Transaction))
{
CopyParameters(cmd, inner);
inner.ExecuteNonQuery();
}
}
}
private static void CopyParameters(FbCommand source, FbCommand inner)
{
foreach (FbParameter parameter in source.Parameters)
{
inner.Parameters.AddWithValue(parameter.ParameterName, parameter.Value);
}
}
}

insert and update sql statement in single sql statement

I want to create insert and update sql statements in single statement. But insert statement is for one table and update statement for another table...
this is Appliance_Location table
CREATE TABLE [dbo].[Appliance_Location] (
[Appliance_Id] NCHAR (10) NOT NULL,
[RoomId] NVARCHAR (20) NOT NULL,
[ApplianceName] NCHAR (10) NOT NULL,
[AddDate] DATE NULL,
CONSTRAINT [PK_Appliance_Location] PRIMARY KEY CLUSTERED ([Appliance_Id] ASC)
);
and this is Appliance_Count table
CREATE TABLE [dbo].[Appliance_Count] (
[RoomId] NVARCHAR (20) NOT NULL,`enter code here`
[Bulb] INT NOT NULL,
[Fan] INT NOT NULL,
[AC] INT NOT NULL,
[Computer] INT NOT NULL,
CONSTRAINT [PK_Appliance_Count] PRIMARY KEY CLUSTERED ([RoomId] ASC)
);
when I insert an appliance to the Appliance_Location table then count of that particular appliance in Appliance_Count table should be updated
It is not possible to insert and update in single query. But You have one option for this task. You can create trigger to inserting of one table. And trigger through update value in other table.
Not sure how about other SQL servers, but you are allowed to execute multiple queries using the same syntax in MS SQL.
using (SqlConnection connection = CreateConnection())
{
SqlCommand command = connection.CreateCommand();
command.CommandText = #"INSERT INTO `TableA` VALUES (1, 2, 3);
UPDATE `TableB` SET [Val] = 'value' WHERE [Id] > 10";
command.CommandType = CommandType.Text;
// ... other initializations
return command.ExecuteNonQuery();
}
You even can return values:
INSERT INTO `TableC` VALUES (1, 'value');
SELECT SCOPE_IDENTITY() AS Id;
After this query you can get SQL reader and read "Id" value from the response.
Why don't you just try before asking?

Insert a record in table fails everytime

I have a database connection where i insert data into the table having 2 columns i.e. id,first_name,last_name.
Following is the code:
private void Insert_Click(object sender, EventArgs e)
{
try
{
String query = "INSERT INTO data_table (first_name,last_name) VALUES (#f_name,#l_name)";
using (SqlConnection connection1 = new SqlConnection(
Properties.Settings.Default.DatabaseConnection))
using (SqlCommand insertCommand = new SqlCommand(query, connection1))
{
//a shorter syntax to adding parameters
// insertCommand.Parameters.Add("#id", SqlDbType.Int).Value = 1;
insertCommand.Parameters.Add("#f_name", SqlDbType.NChar).Value = "JAVED";
insertCommand.Parameters.Add("#l_name", SqlDbType.NChar).Value = "GHAMJN";
//make sure you open and close(after executing) the connection
connection1.Open();
insertCommand.ExecuteNonQuery();
connection1.Close();
MessageBox.Show("Record inserted. Please check your table data. :)");
}
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
}
Following is T-SQL script:
CREATE TABLE [dbo].[data_table] (
[Id] INT NOT NULL IDENTITY,
[first_name] NCHAR (10) NULL,
[last_name] NCHAR (10) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
The issue is created by id: I have set it auto-increment.
When i click insert button a MessageBox pops saying:
Cannot insert the value NULL into column 'Id',table 'C:\Users.......\Database1.MDF.dbo.data_table;column doesn't allows null.INSERT fails
Add identity seed and increment values to your table...
CREATE TABLE [dbo].[data_table] (
[Id] INT NOT NULL IDENTITY(1,1),
[first_name] NCHAR (10) NULL,
[last_name] NCHAR (10) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Your Id field should be modified as shown below
Deleting that table alongwith all the old copies of old table and creating a new table with suggested Identity(1,1) resolved the issue
try this. The behaviour of Identity Columns can be selectively enabled or disabled with this statement.
If your Identity Column is configured correctly, then this is the only logical explanation for it not behaving as expected
SET IDENTITY_INSERT [dbo].[data_table] ON;
However, it is more likely that your assertion of correctness is flawed and you have not configured the Identity Column correctly.

Categories