Using if exists - c#

I'm having an issue with duplicates being inserted on refresh. Our team here decided using 'if exists' in sql is the best way to stop duplicate inserts. However, what if a parameter is set to null?
string cmdText = " if (not exists(select * from table where field1 = #field1 and field2 = #field2 and field3 = #field3)) Insert into table(field1,field2,field3) Values(#field1,#field2,#field3)";
if (txtfield1.text != "")
cmd.Parameters.Add(new SqlParameter("#field1", txtfield1.text));
else
cmd.Parameters.Add(new SqlParameter("# field1", DBNull.Value));
cmd.Parameters.Add(new SqlParameter("#field2", txtfield2));
cmd.Parameters.Add(new SqlParameter("#field3", txtfield3));
This does not work when there is a null value in field1.

You could wrap your fields around isnull's or something to take into account for the nulls
isnull(field1, '') = isnull(#field1, '') and
isnull(field2, '') = isnull(#field2, '') and
isnull(field3, '') = isnull(#field, '')

Would it not be a better idea to reduce the load on the database and attack the problem at the source?
Usually this kind of error occurs when you have a page that handles its own submission, ie the form action property points back to itself, so when someone hits refresh after they posted something, the POST data is still 'live' and gets posted back to the page.
A better way is to have the data submitted to a second object which deals with insertion and then redirects back to where it came from, the redirection clears the POST data and you save yourself a LOT of unnecessary queries.
Just my 2c

where field1 IS #field1
is not valid syntax
Use isnull()
So:
string cmdText = " if (not exists(select * from table where isnull(field1, '') = isnull(#field1, '') ...

If you don't want duplicates in your table, maybe your table should have a primary key or at least a unique clustered index on your field1, field2 and field3.
That way, you could just try ton insert and catch the error if the row already exists.

following condition will help.
field1 is null or #field1 = field1

In field definition you can use NOT NULL constraint so that null element's wouldn't be able to be there.
http://www.w3schools.com/SQl/sql_notnull.asp
And use sql UNIQUE constraint so they have to be unique.
http://www.w3schools.com/SQl/sql_unique.asp
CREATE TABLE YourTable
(
Field1 varchar(255),
Field2 varchar(255),
Field3 varchar(255),
CONSTRAINT uc_fields UNIQUE (Field1, Field2, Field3)
)
CREATE TRIGGER table_null_convert
ON YourTable
FOR INSERT, UPDATE
REFERENCING NEW ROW AS n
FOR EACH ROW
SET n.Field1 = ISNULL(n.Field1, '')
SET n.Field2 = ISNULL(n.Field2, '')
SET n.Field3 = ISNULL(n.Field3, '');
And you'll be allowed to insert if these conditions are met.
Hope I got the trigger right. :)

Well you would have to use IS instead of = if you are going to set #field1 to NULL. NULL is not gonig to work with the equality operator. You need to use an equivalence type 'IS' for that.
string cmdText = " if (not exists(select * from table where field1 = #field1 and field2 = #field2 and field3 = #field3)) Insert into table(field1,field2,field3) Values(#field1,#field2,#field3)";
goes to
string cmdText = "";
if (txtfield1.text != "")
cmdText = " if (not exists(select * from table where field1 = #field1 and field2 = #field2 and field3 = #field3)) Insert into table(field1,field2,field3) Values(#field1,#field2,#field3)";
else
cmdText = " if (not exists(select * from table where field1 IS #field1 and field2 = #field2 and field3 = #field3)) Insert into table(field1,field2,field3) Values(#field1,#field2,#field3)";
end if
Rinse and repeat and refactor :)

Related

SQL; how to skip 'ALTER TABLE' if the columns already exists

So, I don't want to ALTER my table if it's already altered previously. In my WPF app, I got a button for creating new empty table, then with another button I'm ALTERing the table, in that process I'm adding new columns to the empty table. After that I'm inserting data to the columns. Now my problem is, when I click that button again, it wants to alter the table again, before the instert, but throwing an error since the columns already exists.
What I want is to skip the "alter table" when the columns already exists.
This is my base code which works for one click:
public bool updateTable(string tableNamee, string question, string Atype)
{
try
{
string query = $"ALTER TABLE appdb.{tableNamee} ADD question VARCHAR(100) NOT NULL AFTER questionID, ADD Atype VARCHAR(20) NOT NULL AFTER question; ";
string query2 = $"INSERT INTO appdb.{tableNamee} (question, Atype) VALUES('{question}','{Atype}'); ";
etc
This is what I tried:
public bool updateTable(string tableNamee, string question, string Atype)
{
try
{
string q = $"IF NOT EXISTS( SELECT NULL FROM appdb.{tableNamee} WHERE table_name = {tableNamee} AND table_schema = appdb " +
$"AND column_name = question, Atype) THEN ALTER TABLE appdb.{tableNamee} ADD question VARCHAR(100) NOT NULL AFTER questionID, ADD Atype VARCHAR(20) NOT NULL AFTER question; END IF; ";
string query2 = $"INSERT INTO appdb.{tableNamee} (question, Atype) VALUES('{question}','{Atype}'); ";
etc
But I'm getting the following error: "You have an error in your SQL syntax"
I tired to combine the code in many variations but I can't see what is the problem in syntax. Can someone help me with this?
As #tim-biegeleisen wrote: Check for existence first.
As #bradbury9 wrote: Check the schema and not the data.
Call a query like this:
SHOW COLUMNS FROM 'tablename' LIKE 'fieldname';
or this:
SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'tablename' AND COLUMN_NAME = 'fieldname'
Then depending on the result call the ALTER TABLE... query or not.

SQL update query in C# foreach loop

I want to run an UPDATE SQL query within a C# foreach loop, such as:
var alterQuery = $#"
IF NOT EXISTS(SELECT 1 FROM sys.columns WHERE Name = 'MyColumn' AND Object_ID = Object_ID('MyTable'))
ALTER TABLE MyTable
ADD [MyColumn] nvarchar(255)
";
using (var connection = myConnection)
{
connection.Execute(alterQuery);
foreach (var obj in myObjects)
{
var query = $#"UPDATE [MyTable]
SET [MyColumn] = '{obj.Val}'
WHERE [ID] = '{obj.ID}'
";
// note: my Execute method uses ExecuteNonQuery() behind the scenes
connection.Execute(query);
}
}
But, I receive the following SQL Exception. I receive it when there are two or more values in myObjects but not when there is only one:
Additional information: Column names in each table must be unique. Column name MyColumn in table MyTable is specified more than once.
I believe it may be due to the queries running simultaneously and trying to access the same column (MyColumn). Should I be running my queries in such a way that each must wait until the previous completes?
How can I successfully run these queries?
I solved this issue by adding BEGIN and END around my ALTER TABLE query.
Does not work:
IF NOT EXISTS(SELECT 1 FROM sys.columns WHERE Name = 'MyColumn' AND
Object_ID = Object_ID('MyTable'))
ALTER TABLE MyTable
ADD [MyColumn] nvarchar(255)
Works:
IF NOT EXISTS(SELECT 1 FROM sys.columns WHERE Name = 'MyColumn' AND
Object_ID = Object_ID('MyTable'))
BEGIN
ALTER TABLE MyTable
ADD [MyColumn] nvarchar(255)
END
I find it weird that in the non-working case, the only line that the IF NOT EXISTScheck controls is the ALTER TABLE MyTable line, rather than both the ALTER TABLE MyTable and the ADD [MyColumn] nvarchar(255) lines, which should be interpreted as one statement.

Safely query SQL table with variable table name

I'm trying to make some common code for retrieving identities from tables and that involves making an unsafe query string to inject the table name.
I read everywhere that I cannot safely inject the table name. So I want to query if the table exists, then based on the result, perform a real or dummy query.
var unsafeTableQuery = "SELECT [Id] FROM [dbo].[" + tableName + "] WHERE [BulkInsertSessionID] = #bulkInsertSessionId";
var guardQuery =
"DECLARE #Exists BIT = ( SELECT CAST( COUNT(1) AS BIT ) FROM sys.tables WHERE name = #TableName AND type = 'U' );" +
"IF (#Exists = 0) SELECT TOP 0 NULL 'Id'" +
"ELSE " + unsafeTableQuery;
var cmd = new SqlCommand(guardQuery, conn, tran);
cmd.Parameters.Add(new SqlParameter("#TableName", tableName));
cmd.Parameters.Add(new SqlParameter("#bulkInsertSessionId", bulkInsertSessionId));
using (SqlDataReader reader = cmd.ExecuteReader())
{
int index = 0;
while (reader.Read())
{
int id = (int)reader[0];
entities[index++].Id = id;
}
}
Even though I have an unsafe concatenation, I'm first querying the table name against the sys.tables by a parameter. And if it doesn't exist, the IF..ELSE block will never step into the unsafe query.
For easier readability I'm expecting to run the following query:
DECLARE #Exists BIT = ( SELECT CAST( COUNT(1) AS BIT ) FROM sys.tables WHERE name = #TableName AND type = 'U' );
IF(#Exists = 0)
SELECT TOP 0 NULL 'Id'
ELSE
SELECT [Id] from <InjectedTableName> where BulkInsertSessionID = #bulkSessionId
Am I correct in my assumption that this is safe?
Suppose your users have an access to change the variable tableName. I suppose some user types it on some form. Suppose he types this:
Users]; DROP TABLE Users;--
Then your whole command will be:
DECLARE #Exists BIT = ( SELECT CAST( COUNT(1) AS BIT ) FROM sys.tables WHERE name = #TableName AND type = 'U' );
IF(#Exists = 0)
SELECT TOP 0 NULL 'Id'
ELSE
SELECT [Id] from [Users]; DROP TABLE Users;-- where BulkInsertSessionID = #bulkSessionId
This will do its IF ELSE part and then will go to next statement which is:
DROP TABLE Users;
Note that drop statement will execute in any case even if ELSE part is not executed, because you don't have BEGIN END. Note that the rest is commented out... This is most basic injection method...

Update a table from two comma separated parameter as input

I have a Gridview in front end where Grid have two columns : ID and Order like this:
ID Order
1 1
2 2
3 3
4 4
Now user can update the order like in front end Gridview:
ID Order
1 2
2 4
3 1
4 3
Now if the user click the save button the ID and order data is being sent to Stored Procedure as #sID = (1,2,3,4) and #sOrder = (2,4,1,3)
Now if I want to update the order and make save I want to store it into database. Through Stored procedure how can update into the table so that the table is updated and while select it gives me the results like:
ID Order
1 2
2 4
3 1
4 3
There is no built in function to parse these comma separated string. However, yo can use the XML function in SQL Server to do this. Something like:
DECLARE #sID VARCHAR(100) = '1,2,3,4';
DECLARE #sOrder VARCHAR(10) = '2,4,1,3';
DECLARE #sIDASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sID, ',', '</s><s>') +
'</s></root>');
DECLARE #sOrderASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sOrder, ',', '</s><s>') +
'</s></root>');
;WITH ParsedIDs
AS
(
SELECT ID = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sIDASXml.nodes('/root/s') T(c)
), ParsedOrders
AS
(
SELECT "Order" = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sOrderASXml.nodes('/root/s') T(c)
)
UPDATE t
SET t."Order" = p."Order"
FROM #tableName AS t
INNER JOIN
(
SELECT i.ID, p."Order"
FROM ParsedOrders p
INNER JOIN ParsedIDs i ON p.RowNumber = i.RowNumber
) AS p ON t.ID = p.ID;
Live Demo
Then you can put this inside a stored procedure or whatever.
Note that: You didn't need to do all of this manually, it should be some way to make this gridview update the underlying data table automatically through data binding. You should search for something like this instead of all this pain.
You could use a table valued parameter to avoid sending delimiter-separated values or even XML to the database. To do this you need to:
Declare a parameter type in the database, like this:
CREATE TYPE UpdateOrderType TABLE (ID int, Order int)
After that you can define the procedure to use the parameter as
CREATE PROCEDURE UpdateOrder (#UpdateOrderValues UpdateOrderType readonly)
AS
BEGIN
UPDATE t
SET OrderID = tvp.Order
FROM <YourTable> t
INNER JOIN #UpdateOrderValues tvp ON t.ID=tvp.ID
END
As you can see, the SQL is trivial compared to parsing XML or delimited strings.
Use the parameter from C#:
using (SqlCommand command = connection.CreateCommand()) {
command.CommandText = "dbo.UpdateOrder";
command.CommandType = CommandType.StoredProcedure;
//create a table from your gridview data
DataTable paramValue = CreateDataTable(orderedData)
SqlParameter parameter = command.Parameters
.AddWithValue("#UpdateOrderValues", paramValue );
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.UpdateOrderType";
command.ExecuteNonQuery();
}
where CreateDataTable is something like:
//assuming the source data has ID and Order properties
private static DataTable CreateDataTable(IEnumerable<OrderData> source) {
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
table.Columns.Add("Order", typeof(int));
foreach (OrderData data in source) {
table.Rows.Add(data.ID, data.Order);
}
return table;
}
(code lifted from this question)
As you can see this approach (specific to SQL-Server 2008 and up) makes it easier and more formal to pass in structured data as a parameter to a procedure. What's more, you're working with type safety all the way, so much of the parsing errors that tend to crop up in string/xml manipulation are not an issue.
You can use charindex like
DECLARE #id VARCHAR(MAX)
DECLARE #order VARCHAR(MAX)
SET #id='1,2,3,4,'
SET #order='2,4,1,3,'
WHILE CHARINDEX(',',#id) > 0
BEGIN
DECLARE #tmpid VARCHAR(50)
SET #tmpid=SUBSTRING(#id,1,(charindex(',',#id)-1))
DECLARE #tmporder VARCHAR(50)
SET #tmporder=SUBSTRING(#order,1,(charindex(',',#order)-1))
UPDATE dbo.Test SET
[Order]=#tmporder
WHERE ID=convert(int,#tmpid)
SET #id = SUBSTRING(#id,charindex(',',#id)+1,len(#id))
SET #order=SUBSTRING(#order,charindex(',',#order)+1,len(#order))
END

Get all or part result from sql using one TSQL commnd

Here is my condition.
There is a Text box in a form, if you don't input any thing, it would return all rows in this table. If you input something, it would return rows whose Col1 matches the input. I try to use the sql below to do it. But there is one problem, these columns allows Null value. It wouldn't return the row with NULL value. Is there any way to return all or matched row based on the input?
Update
I use the ObjectDataSource and ControlParameter to pass the parameter, when the input of control is empty, the ObjectDataSource would pass a DBNULL to the TSQL commnd.
Col1 Col2 Col3
ABCD EDFR NULL
NULL YUYY TTTT
NULL KKKK DDDD
select * from TABLE where Col1 like Coalesce('%'+#Col1Val+'%',[Col1])
Have you tried
SELECT *
FROM TABLE
WHERE COALESCE(Col1, '') LIKE COALESCE ('%'+#Col1Val+'%', [Col1])
Something like this might work.
select * from TABLE where Coalesce(Col1,'xxx') like Coalesce('%'+#Col1Val+'%',Col1, 'xxx')
SELECT * FROM [TABLE]
WHERE (Col1 LIKE '%'+#Col1Val+'%' OR (#Col1Val = '' AND Col1 IS NULL))
Use this:
SELECT *
FROM TABLE
WHERE
( #Col1Value IS NOT NULL AND COALESCE(Col1, '') LIKE '%' + #Col1Val +'%' )
OR #Col1Value IS NULL
Or perhaps use this, so null won't creep in to your query:
string nullFilteredOutFromQueryString = #"SELECT *
FROM TABLE
WHERE
( #Col1Value <> '' AND COALESCE(Col1, '') LIKE '%' + #Col1Val +'%' )
OR #Col1Value = ''";
var da = new SqlDataAdapater(nullFilteredOutFromQueryString, c);
// add the TextBox's Text property directly to parameter's value.
// this way, null won't creep in to your query
da.SelectCommand.Parameters.AddWithValue("Col1Value", txtBox1.Text);
da.Fill(ds, "tbl");

Categories

Resources