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

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.

Related

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.

How to add data into a table only if it doesnt already exist?

I have thought about using distinct but im not too sure how to do it as a single query for efficiency of code, is there a way? I am basically trying to check if there is already an existing data entry, I am trying to check it with BookingTime. Thanks :)
This is my SQL query:
string bookingInfo = "INSERT INTO Booking(BookingDate, BookingTime, CustomerID, EmployeeID, ServiceType, BookingLength) " +
"VALUES (#BookingDate, #BookingTime, #CustomerID, #EmployeeID, #ServiceType, #BookingLength) " +
"where not exists (SELECT 1 FROM Booking WHERE BookingTime = #BookingTime)";
The error I receive: "Additional information: Query input must contain at least one table or query."
The best way is to let the database do the checking.
Create a unique index or constraint on the table:
create unique index unq_booking_bookingtime on booking(bookingtime);
Note: this is based on your query. It seems unlikely to me that only bookingtime defines uniqueness.
The database will then generate an error if it encounters duplicates. You can prevent the error using insert ignore or insert on duplicate key update (the latter is the preferred method).
The WHERE clause is invalid is invalid in the "INSERT ... VALUES" statement shown.
MySQL does provide an "INSERT ... SELECT ..." form of the INSERT statement (which does not use a VALUES clause). The SELECT statement can have a WHERE clause, but to include a WHERE clause in a SELECT, there has to be a FROM clause. You can use an inline view (aka a derived table) to return a single row, or you could use the builtin Oracle-style dummy table DUAL to return a single row. (We don't care what columns get returned, we just need one row returned.)
For example:
INSERT INTO Booking
( BookingDate
, BookingTime
, CustomerID
, EmployeeID
, ServiceType
, BookingLength
)
SELECT #BookingDate
, #BookingTime
, #CustomerID
, #EmployeeID
, #ServiceType
, #BookingLength
FROM ( SELECT 1 ) i
WHERE NOT EXISTS
( SELECT 1
FROM Booking b
WHERE b.BookingTime = #BookingTime
)
Now the SELECT statement (which can be tested separately from the INSERT) will return either zero or one rows. And whatever row it returns will be passed to the INSERT.
As an alternative to the "NOT EXISTS" predicate, you could use an anti-join pattern:
SELECT #BookingDate
, #BookingTime
, #CustomerID
, #EmployeeID
, #ServiceType
, #BookingLength
FROM ( SELECT 1 ) i
LEFT
JOIN Booking b
ON b.BookingTime = #BookingTime
WHERE b.BookingTime IS NULL
string bookingInfo = "INSERT INTO Booking(BookingDate, BookingTime, CustomerID, EmployeeID, ServiceType, BookingLength) " +
"SELECT #BookingDate, #BookingTime, #CustomerID, #EmployeeID, #ServiceType, #BookingLength " +
" FROM Booking WHERE NOT EXISTS (SELECT * FROM Booking WHERE BookingTime = #BookingTime) LIMIT 1 "
Try this SqlFiddle

Multiple table updates - avoiding multiple WHERE clauses

I looked around for a similar question (I'm sure there is one somewhere) but could not find one.
I have a list of IDs that for each of the IDs I need to update another column of that IDs row to the same string.
Essentially, I want something like this:
List<int> uniqueIDs;
UPDATE my_table
SET certainColumn = "foo bar"
WHERE uniqueID = uniqueIDs[0]
OR uniqueID = uniqueIDs[1]
...
OR uniqueID = uniqueID[uniqueIDs.Length-1]
I know this could be achieved by surrounding this in a for/foreach-loop, but I was wondering if there is a better way to get this done, possibly in one database connection?
Any help is greatly appreciated.
Well, you could use a TVP. First, create this on your server:
CREATE TYPE dbo.UniqueIDs AS TABLE(ID INT PRIMARY KEY);
Then a stored procedure:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
INNER JOIN #tvp AS tvp
ON t.uniqueID = tvp.ID;
END
Or:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
WHERE EXISTS (SELECT 1 FROM #tvp
WHERE ID = t.uniqueID);
END
Then instead of a List use a DataTable to hold your IDs in your C# application, and call the stored procedure, passing #tvp as a Structured parameter. I have simple examples of the C# side posted all over this site:
How to pass an array into a SQL Server stored procedure
Insert entire DataTable into database at once instead of row by row?
Parameters to the EXISTS clause in a stored procedure
var query = "UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID in (" + String.Join(",", uniqueIDs) + ")"
I guess you could do something like this. Get your List of Ids in some TempTable or table variable and use IN operator in your update statement. something like this .....
UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID IN (SELECT uniqueID
FROM #List_Table)

Trying to filter datagrid according to selections made in dropdown lists

I have a View Feedback page with two drop down lists to filter the gridview of answers to questions. The 1st ddList is Modules, once this is selected, the 2nd ddList of Questions becomes enabled and the user can select a question to see the answers for it in relation to the module selected, OR they can select to see all answers to all questions for the selected module.
I have it working if they select a question but if they select all I simply get all answers, not just the ones specific to the selected module.
Sorry I know thats not the clearest explanation but any help would be awesome.
My tables:
CREATE TABLE tblModules
(
Module_ID nvarchar(10) PRIMARY KEY,
Module_Title nvarchar(MAX) NOT NULL
);
CREATE TABLE tblQuestions
(
Q_ID int PRIMARY KEY IDENTITY(1,1),
Question_Text varchar(1000) NOT NULL
);
CREATE TABLE tblFeedback
(
Submission_ID int PRIMARY KEY IDENTITY(1,1),
Username varchar(100) NOT NULL,
Domain varchar(50) NOT NULL,
DateTime_Submitted datetime NOT NULL
Module_ID nvarchar(10)
FOREIGN KEY (Module_ID) REFERENCES tblModules (Module_ID);
);
CREATE TABLE tblAnswers
(
Q_ID int NOT NULL,
Submission_ID int NOT NULL,
Answer_Text varchar(max),
FOREIGN KEY (Q_ID) REFERENCES tblQuestions(Q_ID),
FOREIGN KEY (Submission_ID) REFERENCES tblFeedback(Submission_ID)
);
Here is the snippet of code I'm using to construct the Sql statement which is used to select the data which is then bound to my grid.
// If they have selected view all questions, get all answers for module
if (ddQuestions.SelectedItem.Value == "all")
{
//selectQuery = "SELECT * FROM tblAnswers ORDER BY Submission_ID";
selective = false;
//gridviewFeedback.Columns[3].Visible = true;
selectQuery = "SELECT * FROM tblAnswers A ";
selectQuery += "WHERE EXISTS (SELECT * FROM tblModules M JOIN tblFeedback F ON M.Module_ID = F.Module_ID ";
selectQuery += "WHERE F.Module_ID = '" + this.selectedModuleID + "')";
}
// Instead, if they have selected a specific question, get the information for the selected module and question
else
{
selectQuery = "SELECT * FROM tblAnswers WHERE Q_ID = '" + qID + "' ORDER BY Submission_ID";
selective = true;
//gridviewFeedback.Columns[3].Visible = false;
}
DataSet objDs = new DataSet();
SqlDataAdapter myCommand2;
myCommand2 = new SqlDataAdapter(selectQuery, myConnection);
myCommand2.SelectCommand.CommandType = CommandType.Text;
myCommand2.Fill(objDs);
gridviewFeedback.DataSource = objDs;
gridviewFeedback.DataBind();
I think its my SQL statement as I'm not very experienced at SQL and have kind of edited a statement I used elsewhere that was given to me by someone else.
UPDATE Just realised my other statement isnt working either - its not providing module specific answers either.
You don't seem to have correlated table A into Table M or Table F. This will give you all rows in Table A if there are any modules or feedback anywhere with the provided module ID. You need to add an AND statement to correlate table A with what's in your EXISTS clause.
selectQuery = #"
SELECT * FROM tblAnswers A
WHERE EXISTS (
SELECT * FROM tblModules M
JOIN tblFeedback F ON M.Module_ID = F.Module_ID
WHERE F.Module_ID = #ModuleID
AND A.Submission_ID = F.Submission_ID)";
Also please consider using parameterized queries instead of appending variables into your strings. This causes SQL Server to use the plan cache more efficiently, which is faster, and also eliminates the possibility of a SQL injection attack.
Try this query instead. I think this should do it.
This should help you get all the answers belonging to a specific module
SELECT ans.* FROM tblAnswers ans, tblFeedback fb
WHERE ans.Submission_ID = fb.Submission_ID
AND fb.Module_ID = 'selected module'

Getting autonumber primary key from MS SQL Server

I am currently working in C#, and I need to insert a new record into one table, get the new primary key value, and then use that as a foreign key reference in inserting several more records. The Database is MS SQL Server 2003. All help is appreciated!
The way to get the identity of the inserted row is with the SCOPE_IDENTITY() function. If you're using stored procedures then this would look something like the following to return the row identity as an output parameter.
CREATE PROCEDURE dbo.MyProcedure
(
#RowId INT = NULL OUTPUT
)
AS
INSERT INTO MyTable
(
Column1
,Column2
,...
)
VALUES
(
#Param1
,#Param2
,...
);
SET #RowId = SCOPE_IDENTITY();
You can then use this value for any subsequent inserts (alternatively, if you can pass the data all into the stored procedure, then you can use it in the remainder of the procedure body).
If you're passing the SQL in dynamically then you use much the same technique, but with a single string with statement delimiters (also ; in SQL), e.g.:
var sql = "INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SELECT SCOPE_IDENTITY();";
Then if you execute this using ExecuteScalar you'll be able to get the identity back as the scalar result and cast it to the right type. Alternatively you could build up the whole batch in one go, e.g.
var sql = "DECLARE #RowId INT;" +
"INSERT INTO MyTable (Column1, Column2, ...) VALUES (#P1, #P2, ...);" +
"SET #RowId = SCOPE_IDENTITY();" +
"INSERT INTO MyOtherTable (Column1, ...) VALUES (#P3, #P4, ...);";
This may not be exactly the right syntax, and you may need to use SET NOCOUNT ON; at the start (my mind is rusty as I rarely use dynamic SQL) but it should get you on the right track.
The best way of doing this is the use SCOPE_IDENTITY() function in TSQL. This should be executed as part of the insert i.e.
SqlCommand cmd = new SqlCommand(#"
INSERT INTO T (Name) VALUES(#Name)
SELECT SCOPE_IDENTITY() As TheId", conn);
cmd.AddParameter("#Name", SqlDbType.VarChar, 50).Value = "Test";
int tId = (int)cmd.ExecuteScalar();
Alternatively you can assign SCOPE_IDENTITY() to a variable to be used in successive statements. e.g.
DECLARE #T1 int
INSERT INTO T (Name) VALUES('Test')
SELECT #T1 = SCOPE_IDENTITY()
INSERT INTO T2 (Name, TId) VALUES('Test', #T1)
If you are just using SQL then check Duncan's answer. If however you are using LINQ then you can create the entity, save it to the DB and the ID parameter will be populated automatically.
Given a user entity and a user table it might look like this:
using(var db = new DataContext()) {
var user = new User { Name = "Jhon" };
db.Users.InsertOnSubmit(user);
db.SubmitChanges();
/* At this point the user.ID field will have the primary key from the database */
}

Categories

Resources