I am importing data from large excel sheet and storing it in a stateTable. Now I have to push this data into a database table. The table does have an identity column(1,1).
I have created a similar table type in DB and a procedure to take a parameter as table type to insert in the particular table. I have also set ON the identity insert.
My code is:
using (SqlCommand command = new SqlCommand("InsertStateTable") {
CommandType = CommandType.StoredProcedure})
{
SqlParameter param = command.Parameters.AddWithValue("#statetable", dt);
param.TypeName = "StateTable";
param.SqlDbType = SqlDbType.Structured;
command.Connection = con;
con.Open();
command.ExecuteNonQuery();
con.Close();
}
But the error that arises is
"INSERT into an identity column not allowed on table variables."
I have gone thru many sites but no specific reason is given.....
Thanks in advance.
The error is fairly clear: you are not allowed to do what you are trying to do. Basically, you are going to have to find a design that is not dependent on inserting the identity value into the table-variable / table-valued-parameter. My advice would be to create a separate table-variable (unrelated to the table-valued-parameter) which has the same data, but which does not have the IDENTITY column, so...
declare #foo table (id int not null, name nvarchar(200) not null /* etc */)
insert #foo (id, name /* etc */)
select id, name /* etc */ from #statetable
at which point #foo has the original data, but does not have an identity column - you can then do whatever you want with #foo.
Without seeing what you are doing with your identity insert, it is hard to comment much further.
Related
I tried everything I could knew on this, basically I have a table of Clients ( Sql Server )
The table looks like this
create table Client
(
Name nvarchar(100),(I want to be null)
EmployeesNo nvarchar(50),
CompanyCapital int
)
This simple table, the problem I encounter is when I want to update.
The problem is I can have multiple data with same values in Name so how to differentiate between the Values in Name to make them unique, Update looks like:
using(SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Data"].ToString())
{
if(con.State.ToString() == "Closed")
{
con.Open();
}
using(SqlCommand com = new SqlCommand("Update Client set Name = #name, EmployeesNo = #emp, CompanyCapital = #com where Name = #name2 (Should I update by some another thing that it will make it unique ???)",con))
{
com.Parameters.Add(new SqlParameter("#name2",Mod));(Mod = string I initialized in constructor from another form;)
com.Parameters.Add(new SqlParameter("#name",textBox1.Text));
com.Parameters.Add(new SqlParameter("#emp",textBox2.Text));
com.Parameters.Add(new SqlParameter("#com",textBox3.Text));
com.ExecuteNonQuery();
}
}
So when i update or user updates those values if i have multiple data with same values it will overwrite all of them and i don't want that, remember also that values are inserted and updated from textboxes.
I tried thinking of using Primary key but how do i know where to update at wich primary key, cause im using textboxes and i don't want the user to type in the ID of something, thanks.
You should use a primary key (like ID) if you need to uniquely identify your records.
In order to know the ID of the record that needs to be updated, just store the ID in your form as a hidden and readonly field.
The second parameter of Parameters.Add method, its SqlDbType. Not the Parameter Value.
instead use Parameters.AddWithValue
or put value in object initializer,
com.Parameters.Add(new SqlParameter("#name2", SqlDbType.VarChar) {Value = Mod});
I have a DataTable and need the entire thing pushed to a Database table.
I can get it all in there with a foreach and inserting each row at a time. This goes very slow though since there are a few thousand rows.
Is there any way to do the entire datatable at once that might be faster?
The DataTable has less columns than the SQL table. the rest of them should be left NULL.
I discovered SqlBulkCopy is an easy way to do this, and does not require a stored procedure to be written in SQL Server.
Here is an example of how I implemented it:
// take note of SqlBulkCopyOptions.KeepIdentity , you may or may not want to use this for your situation.
using (var bulkCopy = new SqlBulkCopy(_connection.ConnectionString, SqlBulkCopyOptions.KeepIdentity))
{
// my DataTable column names match my SQL Column names, so I simply made this loop. However if your column names don't match, just pass in which datatable name matches the SQL column name in Column Mappings
foreach (DataColumn col in table.Columns)
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
bulkCopy.BulkCopyTimeout = 600;
bulkCopy.DestinationTableName = destinationTableName;
bulkCopy.WriteToServer(table);
}
Since you have a DataTable already, and since I am assuming you are using SQL Server 2008 or better, this is probably the most straightforward way. First, in your database, create the following two objects:
CREATE TYPE dbo.MyDataTable -- you can be more speciifc here
AS TABLE
(
col1 INT,
col2 DATETIME
-- etc etc. The columns you have in your data table.
);
GO
CREATE PROCEDURE dbo.InsertMyDataTable
#dt AS dbo.MyDataTable READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.RealTable(column list) SELECT column list FROM #dt;
END
GO
Now in your C# code:
DataTable tvp = new DataTable();
// define / populate DataTable
using (connectionObject)
{
SqlCommand cmd = new SqlCommand("dbo.InsertMyDataTable", connectionObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#dt", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
cmd.ExecuteNonQuery();
}
If you had given more specific details in your question, I would have given a more specific answer.
Consider this approach, you don't need a for loop:
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName =
"dbo.BulkCopyDemoMatchingColumns";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(ExistingSqlTableName);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
If can deviate a little from the straight path of DataTable -> SQL table, it can also be done via a list of objects:
1) DataTable -> Generic list of objects
public static DataTable ConvertTo<T>(IList<T> list)
{
DataTable table = CreateTable<T>();
Type entityType = typeof(T);
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);
foreach (T item in list)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
row[prop.Name] = prop.GetValue(item);
}
table.Rows.Add(row);
}
return table;
}
Source and more details can be found here. Missing properties will remain to their default values (0 for ints, null for reference types etc.)
2) Push the objects into the database
One way is to use EntityFramework.BulkInsert extension. An EF datacontext is required, though.
It generates the BULK INSERT command required for fast insert (user defined table type solution is much slower than this).
Although not the straight method, it helps constructing a base of working with list of objects instead of DataTables which seems to be much more memory efficient.
You can do this with a table value parameters.
Have a look at the following article:
http://www.codeproject.com/Articles/39161/C-and-Table-Value-Parameters
I would prefer user defined data type : it is super fast.
Step 1 : Create User Defined Table in Sql Server DB
CREATE TYPE [dbo].[udtProduct] AS TABLE(
[ProductID] [int] NULL,
[ProductName] [varchar](50) NULL,
[ProductCode] [varchar](10) NULL
)
GO
Step 2 : Create Stored Procedure with User Defined Type
CREATE PROCEDURE ProductBulkInsertion
#product udtProduct readonly
AS
BEGIN
INSERT INTO Product
(ProductID,ProductName,ProductCode)
SELECT ProductID,ProductName,ProductCode
FROM #product
END
Step 3 : Execute Stored Procedure from c#
SqlCommand sqlcmd = new SqlCommand("ProductBulkInsertion", sqlcon);
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Parameters.AddWithValue("#product", productTable);
sqlcmd.ExecuteNonQuery();
Possible Issue : Alter User Defined Table
Actually there is no sql server command to alter user defined type
But in management studio you can achieve this from following steps
1.generate script for the type.(in new query window or as a file)
2.delete user defied table.
3.modify the create script and then execute.
I am using C# and using SqlBulkCopy. I have a problem though. I need to do a mass insert into one table then another mass insert into another table.
These 2 have a PK/FK relationship.
Table A
Field1 -PK auto incrementing (easy to do SqlBulkCopy as straight forward)
Table B
Field1 -PK/FK - This field makes the relationship and is also the PK of this table. It is not auto incrementing and needs to have the same id as to the row in Table A.
So these tables have a one to one relationship but I am unsure how to get back all those PK Id that the mass insert made since I need them for Table B.
Edit
Could I do something like this?
SELECT *
FROM Product
WHERE NOT EXISTS (SELECT * FROM ProductReview WHERE Product.ProductId = ProductReview.ProductId AND Product.Qty = NULL AND Product.ProductName != 'Ipad')
This should find all the rows that where just inserted with the sql bulk copy. I am not sure how to take the results from this then do a mass insert with them from a SP.
The only problem I can see with this is that if a user is doing the records one at a time and a this statement runs at the same time it could try to insert a row twice into the "Product Review Table".
So say I got like one user using the manual way and another user doing the mass way at about the same time.
manual way.
1. User submits data
2. Linq to sql Product object is made and filled with the data and submited.
3. this object now contains the ProductId
4. Another linq to sql object is made for the Product review table and is inserted(Product Id from step 3 is sent along).
Mass way.
1. User grabs data from a user sharing the data.
2. All Product rows from the sharing user are grabbed.
3. SQL Bulk copy insert on Product rows happens.
4. My SP selects all rows that only exist in the Product table and meets some other conditions
5. Mass insert happens with those rows.
So what happens if step 3(manual way) is happening at the same time as step 4(mass way). I think it would try to insert the same row twice causing a primary constraint execption.
In that scenario, I would use SqlBulkCopy to insert into a staging table (i.e. one that looks like the data I want to import, but isn't part of the main transactional tables), and then at the DB to a INSERT/SELECT to move the data into the first real table.
Now I have two choices depending on the server version; I could do a second INSERT/SELECT to the second real table, or I could use the INSERT/OUTPUT clause to do the second insert , using the identity rows from the table.
For example:
-- dummy schema
CREATE TABLE TMP (data varchar(max))
CREATE TABLE [Table1] (id int not null identity(1,1), data varchar(max))
CREATE TABLE [Table2] (id int not null identity(1,1), id1 int not null, data varchar(max))
-- imagine this is the SqlBulkCopy
INSERT TMP VALUES('abc')
INSERT TMP VALUES('def')
INSERT TMP VALUES('ghi')
-- now push into the real tables
INSERT [Table1]
OUTPUT INSERTED.id, INSERTED.data INTO [Table2](id1,data)
SELECT data FROM TMP
If your app allows it, you could add another column in which you store an identifier of the bulk insert (a guid for example). You would set this id explicitly.
Then after the bulk insert, you just select the rows that have that identifier.
I had the same issue where I had to get back ids of the rows inserted with SqlBulkCopy.
My ID column was an identity column.
Solution:
I have inserted 500+ rows with bulk copy, and then selected them back with the following query:
SELECT TOP InsertedRowCount *
FROM MyTable
ORDER BY ID DESC
This query returns the rows I have just inserted with their ids. In my case I had another unique column. So I selected that column and id. Then mapped them with a IDictionary like so:
IDictionary<string, int> mymap = new Dictionary<string, int>()
mymap[Name] = ID
Hope this helps.
My approach is similar to what RiceRiceBaby described, except one important thing to add is that the call to retrieve Max(Id) needs to be a part of a transaction, along with the call to SqlBulkCopy.WriteToServer. Otherwise, someone else may insert during your transaction and this would make your Id's incorrect. Here is my code:
public static void BulkInsert<T>(List<ColumnInfo> columnInfo, List<T> data, string
destinationTableName, SqlConnection conn = null, string idColumn = "Id")
{
NLogger logger = new NLogger();
var closeConn = false;
if (conn == null)
{
closeConn = true;
conn = new SqlConnection(_connectionString);
conn.Open();
}
SqlTransaction tran =
conn.BeginTransaction(System.Data.IsolationLevel.Serializable);
try
{
var options = SqlBulkCopyOptions.KeepIdentity;
var sbc = new SqlBulkCopy(conn, options, tran);
var command = new SqlCommand(
$"SELECT Max({idColumn}) from {destinationTableName};", conn,
tran);
var id = command.ExecuteScalar();
int maxId = 0;
if (id != null && id != DBNull.Value)
{
maxId = Convert.ToInt32(id);
}
data.ForEach(d =>
{
maxId++;
d.GetType().GetProperty(idColumn).SetValue(d, maxId);
});
var dt = ConvertToDataTable(columnInfo, data);
sbc.DestinationTableName = destinationTableName;
foreach (System.Data.DataColumn dc in dt.Columns)
{
sbc.ColumnMappings.Add(dc.ColumnName, dc.ColumnName);
}
sbc.WriteToServer(dt);
tran.Commit();
if(closeConn)
{
conn.Close();
conn = null;
}
}
catch (Exception ex)
{
tran.Rollback();
logger.Write(LogLevel.Error, $#"An error occurred while performing a bulk
insert into table {destinationTableName}. The entire
transaction has been rolled back.
{ex.ToString()}");
throw ex;
}
}
Depending on your needs and how much control you have of the tables, you may want to consider using UNIQUEIDENTIFIERs (Guids) instead of your IDENTITY primary keys. This moves key management outside of the database and into your application. There are some serious tradeoffs to this approach, so it may not meet your needs. But it may be worth considering. If you know for sure that you'll be pumping a lot of data into your tables via bulk-insert, it is often really handy to have those keys managed in your object model rather than your application relying on the database to give you back the data.
You could also take a hybrid approach with staging tables as suggested before. Get the data into those tables using GUIDs for the relationships, and then via SQL statements you could get the integer foreign keys in order and pump data into your production tables.
I would:
Turn on identity insert on the table
Grab the Id of the last row of the table
Loop from (int i = Id; i < datable.rows.count+1; i++)
In the loop, assign the Id property of your datable to i+1.
Run your SQL bulk insert with your keep identity turned on.
Turn identity insert back off
I think that's the safest way to get your ids on an SQL bulk insert because it will prevent mismatched ids that could caused by the application be executed on another thread.
Disclaimer: I'm the owner of the project C# Bulk Operations
The library overcome SqlBulkCopy limitations and add flexible features like output inserted identity value.
Behind the code, it does exactly like the accepted answer but way easier to use.
var bulk = new BulkOperation(connection);
// Output Identity
bulk.ColumnMappings.Add("ProductID", ColumnMappingDirectionType.Output);
// ... Column Mappings...
bulk.BulkInsert(dt);
I know that in Oracle I can get the generated id (or any other column) from an inserted row as an output parameter.
Ex:
insert into foo values('foo','bar') returning id into :myOutputParameter
Is there a way to do the same, but using ExecuteScalar instead of ExecuteNonQuery?
I don't want to use output parameters or stored procedures.
ps: I'm using Oracle, not sql server!!!
If you are on oracle, you have to use ExecuteNonQuery and ResultParameter. There is no way to write this as query.
using (OracleCommand cmd = con.CreateCommand()) {
cmd.CommandText = "insert into foo values('foo','bar') returning id into :myOutputParameter";
cmd.Parameters.Add(new OracleParameter("myOutputParameter", OracleDbType.Decimal), ParameterDirection.ReturnValue);
cmd.ExecuteNonQuery(); // an INSERT is always a Non Query
return Convert.ToDecimal(cmd.Parameters["myOutputParameter"].Value);
}
Oracle uses sequences as for his identity columns, if we may say so.
If you have set a sequence for your table primary key, you also have to write a trigger that will insert the Sequence.NextValue or so into your primary key field.
Assuming that you are already familiar with this concept, simply query your sequence, then you will get your answer. What is very practiced in Oracle is to make yourself a function which will return an INT, then within your function, you perform your INSERT. Assuming that you have setup your trigger correctly, you will then be able to return the value of your sequence by querying it.
Here's an instance:
CREATE TABLE my_table (
id_my_table INT PRIMARY KEY
description VARCHAR2(100) NOT NULL
)
CREATE SEQUENCE my_table_seq
MINVALUE 1
MAXVALUE 1000
START WITH 1
INCREMENT BY 2
CACHE 5;
If you want to manage the auto-increment yourself, here's how:
INSERT INTO my_table (
id_my_table,
description
) VALUES (my_table_seq.NEXTVAL, "Some description");
COMMIT;
On the other hand, if you wish not to care about the PRIMARY KEY increment, you may proceed with a trigger.
CREATE OR REPLACE TRIGGER my_table_insert_trg
BEFORE INSERT ON my_table FOR EACH ROW
BEGIN
SELECT my_table_seq.NEXTVAL INTO :NEW.id_my_table FROM DUAL;
END;
Then, when you're inserting, you simply type the INSERT statement as follows:
INSERT INTO my_table (description) VALUES ("Some other description");
COMMIT;
After an INSERT, I guess you'll want to
SELECT my_table_seq.CURRVAL
or something like this to select the actual value of your sequence.
Here are some links to help:
http://www.orafaq.com/wiki/Sequence
http://www.orafaq.com/wiki/AutoNumber_and_Identity_columns
Hope this helps!
You can use below code.
using (OracleCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"INSERT INTO my_table(name, address)
VALUES ('Girish','Gurgaon India')
RETURNING my_id INTO :my_id_param";
OracleParameter outputParameter = new OracleParameter("my_id_param", OracleDbType.Decimal);
outputParameter.Direction = ParameterDirection.Output;
cmd.Parameters.Add(outputParameter);
cmd.ExecuteNonQuery();
return Convert.ToDecimal(outputParameter.Value);
}
one possible way if one can add one column named "guid" to the table :
when inserting one record from c#, generate a guid and write it to the guid column.
then perform a select with the generated guid, and you have got the id of inserted record :)
Select t.userid_pk From Crm_User_Info T
Where T.Rowid = (select max(t.rowid) from crm_user_info t)
this will return your required id
I looked at the SQLTeam website but now am having a new problem cause I have an IDENTITY column and their example does not.
I have SQL Server 2008 and VS 2008. I am trying to execute the InsertPIF SP using C# and a table UDT, but am getting an exception. I have looked at a SQLTeam website
example with a Table UDT, but their example doesn't have an identity column like mine does.
However, their example table doesn't have an identity column like mine.
What I am trying to do should be simple. I have tried many different variations of this, but essentially:
ALTER PROCEDURE [dbo].[InsertPIF] #InputFileParam parseInputFile READONLY
AS
INSERT dbo.ParentTable SELECT strRow FROM #InputFileParam
GO
where
CREATE TABLE [dbo].[ParentTable](
[ParentID] [int] IDENTITY(1,1) NOT NULL,
[strInput] [varchar](8000) NULL,
PRIMARY KEY NONCLUSTERED
(...)
and type:
CREATE TYPE [dbo].[parseInputFile] AS TABLE(
[NumCols] [int] IDENTITY(1,1) NOT NULL,
[strRow] [varchar](500) NOT NULL,
PRIMARY KEY CLUSTERED
(...)
What I am trying to do is to execute the above SP to insert new records into the ParentTable. Example:
INSERT INTO ParentTable(strInput) VALUES ('A3|BB|C|DDD')
INSERT INTO ParentTable(strInput) VALUES ('A4|GOB|BLDY|GOOK')
INSERT INTO ParentTable(strInput) VALUES ('A5|Hello|My|Darling')
Here is more of my C# code prior to the AppendData section:
DataTable dt = new DataTable();
String[] header = myStringArray[0].Split('|');
DataRow dr = dt.NewRow();
DataColumn dc = new DataColumn();
dc.DataType = typeof(Int32);
dc.ColumnName = “NumCols”;
dc.AutoIncrement=true;
dt.Columns.Add(dc);
DataColumn dc2 = new DataColumn();
dc2.DataType = typeof(string);
dc2.ColumnName = “strRow”;
dt.Columns.Add(dc2);
dr["NumCols"] = 1;
dr["strRow"] = "AA|BBB|CC|DD|E";
dt.Rows.Add(dr);
...
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "dbo.InsertPIF";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
SqlParameter param = cmd.Parameters.AddWithValue("#InputFileParam", SqlDbType.Structured);
param.Value = dt;
conn.Open();
RowsAffected = cmd.ExecuteNonQuery();
I set identity to be turned on for the ParentTable, but it seems like I have to execute this each session, so I'm not sure how to remove identity requirement or else to make identity remain on. Exception:
INSERT into an identity column not allowed on table variables. The data for table-valued parameter '#InputFileParam' doesn't conform to the table type of the parameter.
How do I execute this SP?
I think Roland is on the right track - the IDENTITY columns definitely are the problem. Since your ultimate target table already has an IDENTITY column which will be filled automagically for you, I would recommend not specifying the same column in your user-defined table type (and also do not declare a primary key on the table type):
CREATE TYPE [dbo].[parseInputFile] AS TABLE
([strRow] [varchar](500) NOT NULL)
Then in your C# code, just set up a DataTable with a single column:
DataTable dt = new DataTable();
DataColumn dc = new DataColumn();
dc.DataType = typeof(string);
dc.ColumnName = “strRow”;
dt.Columns.Add(dc);
and then add your columns to your DataTable "dt" - with just the one string "strRow".
In your stored proc that then gets called with your user-defined table type, just simply insert your strings:
ALTER PROCEDURE [dbo].[InsertPIF] #InputFileParam parseInputFile READONLY
AS
INSERT INTO dbo.ParentTable(strInput)
SELECT strRow FROM #InputFileParam
GO
That should do it, I hope! (haven't had the time to test this myself)
Marc
Okay, I haven't actually worked with TVPs yet, so I could be wrong--but from my reading, & general SS background, I think your problem is this:
Your permanent table has an Identity column, so SS wants to handle that for you. You can set Identity Insert on, but that's not a good idea....
One thing to try is to specify the column to insert in your SP:
ALTER PROCEDURE [dbo].[InsertPIF] #InputFileParam parseInputFile READONLY
AS
INSERT dbo.ParentTable (strInput) SELECT strRow FROM #InputFileParam
GO
The other thing to look at, as Tim Lentine suggested (while I was writing this answer), is whether or not you're actually getting to the server. Since you've defined the NumCols column in your DataTable as AutoIncrement, it could be that you're getting your exception in the C# code, at:
...
dr["NumCols"] = 1;
...
< edit>
Don't know what I was thnking when I clicked Submit...
If that's the case, try leaving out the AutoIncrement when you creat your DataTable. If the column data types are consistent, this property shouldn't matter when lining up the TVP as being "the same" as the SS Type.
< /edit>
Could the error be coming from the parseInputFile table instead? NumCols is defined as an identify field, but in the C# code you are setting the value of the record to 1.