T-SQL: merge or upsert child table - c#

I have three tables with data as shown in the following screenshot:
I have a working MERGE statement for the Users table that is sent to the SQL Server from C#. The MERGE statement correctly merges data into the ICS_USERS table. The #temp table is also created and populated in C#:
MERGE INTO ICS_Users AS Target
USING #temp AS Source ON TARGET.USerID = Source.UserId
WHEN MATCHED THEN
UPDATE
SET TARGET.UserName = Source.UserName, TARGET.Active = Source.Active
WHEN NOT MATCHED THEN
INSERT(UserName, Active, UserInitials)
VALUES(Source.UserName, Source.Active, Source.UserInitials)
I want to allow the user to add/change/delete the role for a user and send in the MERGE statement to handle it. Note that the user will only ever be allowed to change a single user at one time.
How do I change the merge statement to account for the Role and User/Role associative table?

You can follow these steps to achieve this.
Create a stored procedure which will accept all the input parameters which are required to join, insert, update and delete the values using source and target. Make all parameter optional or null able so that you can run your merge even without input values.
Insert all these values in a table variable which will have same column as your target tables along with the same data type.
Use your actual source table and along with table variable and union them and then use the output in merge logic.
This will handle your both requirement in single merge statement. Even when you input columns will be null first query in union will always have values to suffice the merge logic.
Try if this will not work comment and I will provide you sql script. Here I am not providing sql intentionally so that at least you can read and try by your self and write the logic in sql.

Related

SQL Server pre-deploy script to recreate table new columns and preserve data

I created a project in ASP.NET MVC with a separate database project which I run every time there is a table change. My only problem is that if I add one column for example, it will drop the entire database and recreate it and delete all data in the table.
Does anyone know of a pre-deployment script or a method I can use to add / remove / rename tables or column and at the same time preserve the integrity of my data? i.e keep my data while modifying my database
You can rename columns using SQL Server functions, but doing this risks breaking scripts used by other functions or stored procedures in your database. I don't endorse this practice, so I'm not posting about it below. Adding or removing columns is fair game.
You can add columns to a table by using the following query:
ALTER TABLE [YourTable]
ADD [ColumnName] [Datatype];
And you can drop columns using this query:
ALTER TABLE [YourTable]
DROP COLUMN [ColumnName];
These SQL commands will preserve the other columns in your table. If you want to change a column name I encourage you to set up a View in your SQL Server client and give the column an alias.
This can be accomplished using:
CREATE VIEW [ViewName]
AS
SELECT [ColumnName] AS [ColumnAlias]
FROM [TableName]
GO
You'd be able to perform SELECTS on the view in just the same way you can query SELECT on a normal table, except you can query the [ColumnAlias] instead of the [ColumnName]. You cannot perform INSERT or DELETE queries on a view, however

Add a Column to a Table via Linq or Triggers in SQL

The issue is like , I got two tables X & Y. When records are added to table X , columns should be added to the table Y in parallel.
http://prntscr.com/3owqfe <-- Provides a clear Idea.
I tried it with triggers , but seems like the triggers doesnt allow CREATE TABLE or ALTER TABLE. Anyway as I'm using Linq , Im trying to achieve that via Linq. Any suggestions ?
Edited: For the record the below trigger worked , but with an exception.
create trigger AddItemToCommon ON [SEP].[dbo].[ItemMaster]
FOR INSERT
AS BEGIN
declare #PAYID varchar(max)
select #PAYID = payCode from INSERTED
ALTER TABLE [SEP].[dbo].[CommonPayrollItems]
ADD sampleCol varchar(max)
END
Anyway it will only run once because , there cannot be more than 1 column of the same name. But if I can retrieve the row values grom ItemMaster table , it's still possible with triggers. Which I tried to replace sampleCol with #PAYID which results in Syntax error.
Linq does not have functionality to create or alter tables. You could however use the underlying connection of your Linq Datacontext to execute any SQL statement:
DataContext.ExecuteCommand(...)
And use that to run your alter table statement. This will not update your Linq model of that table though.
If you plan to do this at runtime, you might want to reconsider your data model and create a third table (eg. STable2Extended) that contains a foreign key to both STable and STable2 combined with the applicable value for that column.
You could then add the STable2Extended to the datamodel and query it easily when you need the extended properties on STable2 without dynamically modifying tables.

Primary key violation error in sql server 2008

I have created two threads in C# and I am calling two separate functions in parallel. Both functions read the last ID from XYZ table and insert new record with value ID+1. Here ID column is the primary key. When I execute the both functions I am getting primary key violation error. Both function having the below query:
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
Seems like both functions are reading the value at a time and trying to insert with the same value.
How can I solve this problem.. ?
Let the database handle selecting the ID for you. It's obvious from your code above that what you really want is an auto-incrementing integer ID column, which the database can definitely handle doing for you. So set up your table properly and instead of your current insert statement, do this:
insert into XYZ values('Name')
If your database table is already set up I believe you can issue a statement similar to:
alter table your_table modify column you_table_id int(size) auto_increment
Finally, if none of these solutions are adequate for whatever reason (including, as you indicated in the comments section, inability to edit the table schema) then you can do as one of the other users suggested in the comments and create a synchronized method to find the next ID. You would basically just create a static method that returns an int, issue your select id statement in that static method, and use the returned result to insert your next record into the table. Since this method would not guarantee a successful insert (due to external applications ability to also insert into the same table) you would also have to catch Exceptions and retry on failure).
Set ID column to be "Identity" column. Then, you can execute your queries as:
insert into XYZ values('Name')
I think that you can't use ALTER TABLE to change column to be Identity after column is created. Use Managament Studio to set this column to be Identity. If your table has many rows, this can be a long running process, because it will actually copy your data to a new table (will perform table re-creation).
Most likely that option is disabled in your Managament Studio. In order to enable it open Tools->Options->Designers and uncheck option "Prevent saving changes that require table re-creation"...depending on your table size, you will probably have to set timeout, too. Your table will be locked during that time.
A solution for such problems is to have generate the ID using some kind of a sequence.
For example, in SQL Server you can create a sequence using the command below:
CREATE SEQUENCE Test.CountBy1
START WITH 1
INCREMENT BY 1 ;
GO
Then in C#, you can retrieve the next value out of Test and assign it to the ID before inserting it.
It sounds like you want a higher transaction isolation level or more restrictive locking.
I don't use these features too often, so hopefully somebody will suggest an edit if I'm wrong, but you want one of these:
-- specify the strictest isolation level
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
or
-- make locks exclusive so other transactions cannot access the same rows
insert into XYZ values((SELECT max(ID)+1 from XYZ WITH (XLOCK)),'Name')

How to insert to table with one-to-one relationship via dataset

I use asp.net 4 and DataSets for accessing the database. There are two tables with one-to-one relationship in the database. It means that both tables have the same column as a primary key (say Id), and one of tables has #identity on this column set.
So in general if we want to insert, we insert first into the first table, than insert into the second table with id.table2 = id of the corresponding record in table1.
I can imagine how to achieve this using stored procedure (we would insert into the first table and have id as an out parameter and then insert into the second table using this id, btw all inside one transaction).
But is there a way to do it without using a stored procedure? May be DataSets \ DataAdapters have such functionality built in?
Would appreciate any help.
Today it is so quiet here... Ok if anybody is also looking for such a solution, I've found a way to do it.
So our main problem is to get the id of the newly created record in the first table. If we're able to do that, after that we simply supply it to the next method which creates a corresponding record in the second table.
I used a DataSet Designer in order to enjoy the code autogeneration feature of the VS. Let's call the first table TripSets. In DataSet Designer right click on the TripSetsTableAdapter, then Properties. Expand InsertCommand properties group. Here we need to do two things.
First we add a new parameter into the collection of parameters using the Parameters Collection Editor. Set ParameterName = #TripId, DbType = Int32 (or whatever you need), Direction = Output.
Second we modify the CommandText (using Query Builder for convenience). Add to the end of the command another one after a semicolon like that:
(...);
SELECT #TripId = SCOPE_IDENTITY()
So you will get something like this statement:
INSERT INTO TripSets
(Date, UserId)
VALUES
(#Date,#UserId);
SELECT #TripId = SCOPE_IDENTITY()
Perhaps you will get a parser error warning, but you can just ignore it. Having this configured now we are able to use in our Business logic code as follows:
int tripId;
int result = tripSetsTableAdapter.Insert(tripDate, userId, out tripId);
// Here comes the insert method into the second table
tripSetTripSearchTableAdapter.Insert(tripId, amountPersons);
Probably you will want to synchronize this operations somehow (e.g. using TransactionScope) but it is completely up to you.

How do I get the C# Query component to recognise columns returned data from a temporary table in a sql stored procedure

I've created a stored procedure similar to the one below (I'm using this cut down version to try and figure our the problem).
CREATE PROCEDURE bsp_testStoredProc
AS
BEGIN
CREATE TABLE #tmpFiles
(
AuthorName NVARCHAR(50),
PercentageHigh INT
)
-- Insert data into temp table
SELECT AuthorName, PercentageHigh FROM #tmpFiles
ORDER BY PercentageHigh DESC
DROP TABLE #tmpFiles
RETURN 0
END
From my C# code in VS2008, I'm trying to use the Query component with the Use Existing Stored Procedure option to connect this up to a DataTable / DataGridView to display the results.
However, because I'm selecting from a temporary table, in the Query component properties Visual Studio does not display any columns being returned from the stored procedure. I assume that it has trouble determining the data types being used since the SP is not based on a real schema.
Connecting to different stored procedures that select from real tables do show the columns correctly.
Does anyone know away around this? Is there some sort of hint I can add somewhere to explicitly state what sort of data will be returned?
Thanks in advance.
For info, you might consider using a "table variable" rather than a temporary table (i.e. #FOO rather than #FOO) - this might help a little, and it certainly helps a few tempdb issues.
With temporary tables - no there is no way of explicitly declaring the SPs schema. I would perhaps suggest using a simplified version of the SP while you generate your wrapper classes - i.e. have it do a trivial SELECT of the correct shape.
Alternatively, I would use LINQ to consume a UDF, which does have explicit schema.

Categories

Resources