I have a database structured as follows:
users
userid (Primary Key)
username
group
groupid (PK)
groupName
user_groups
userid (Foreign Key)
groupid (Foreign Key)
The first time a user logs in I would like their info to be added to the users table. So essentially the logic I would like to have if
if (//users table does not contain username)
{
INSERT INTO users VALUES (username);
}
How can I do this intelligently using SQL Server/C# ?
Or using the new MERGE syntax:
merge into users u
using (
select 'username' as uname
) t on t.uname = u.username
when not matched then
insert (username) values (t.uname);
Basically you can do it like this:
IF NOT EXISTS (SELECT * FROM USER WHERE username = #username)
INSERT INTO users (username) VALUES (#username)
But seriously, how you're going to know if user visited your website for the first time?
You have to insert records in table user, when somebody register on your website, not login.
IF NOT EXISTS (select * from users where username = 'username')
BEGIN
INSERT INTO ...
END
I would first create a stored proc on the db to do the check and insert if necessary:
CREATE PROCEDURE AddNewUserProc
(
#username VarChar(50) -- replace with your datatype/size
)
AS
IF NOT EXISTS (SELECT * FROM users WHERE username = #username)
BEGIN
INSERT INTO users
VALUES (#username)
END
Then a method on the app that will call this procedure
public void AddNewUserMethod(string userName)
{
SqlConnection connection = new SqlConnection("connection string");
SqlCommand command = new SqlCommand("AddNewUserProc", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("username", SqlDbType.VarChar, 50).Value = userName;
try
{
connection.Open();
command.ExecuteNonQuery();
}
finally
{
if (connection.State == ConnectionState.Open) { connection.Close(); }
}
}
Note leaving this as alternative/historical, but for purpose of correctness the correct way is using the Merge statement, see answer https://stackoverflow.com/a/9649040/167304 or checkout MS doc https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15
There is a simple solution! I Found it in this post!
INSERT INTO users (Username)
SELECT ('username')
WHERE NOT EXISTS(SELECT * FROM users WHERE Username= 'username')
In my project I needed to do it with 2 values. So my code was:
INSERT INTO MyTable (ColName1, ColName2)
SELECT 'Value1','Value2'
WHERE NOT EXISTS
(SELECT * FROM MyTable WHERE ColName1 = 'Value1' AND ColName2= 'Value2')
Hope this helps!
The following code is a method that returns 0 if user already exists and returns the new user ID that just added :
private int TryToAddUser(string UserName)
{
int res = 0;
try
{
string sQuery = " IF NOT EXISTS (select * from users where username = #username) \n\r" +
" BEGIN \n\r" +
" INSERT INTO users values (#username) \n\r" +
" SELECT SCOPE_IDENTITY() \n\r " +
" END \n\r " +
" ELSE SELECT 0";
using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand())
{
cmd.CommandText = sQuery;
cmd.Parameters.AddWithValue("#username",UserName);
cmd.Connection = new System.Data.SqlClient.SqlConnection("SomeSqlConnString");
cmd.Connection.Open();
res = (int)cmd.ExecuteScalar();
cmd.Connection.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return res;
}
#gbn answer needs SQL server 2008 or higher. I tried a non-merge way to do it. Ugly perhaps, but it works.
declare #user varchar(50)
set #user = 'username'
insert into users (username)
select #user
where not exists (
select username
from users
where username = #user
);
If you want to test any of the answers, here is the SQL for the table -
CREATE TABLE [dbo].[users](
[userid] [int] NULL,
[username] [varchar](50) NULL
)
INSERT [dbo].[users] ([userid], [username]) VALUES (1, N'John')
INSERT [dbo].[users] ([userid], [username]) VALUES (2, N'David')
INSERT [dbo].[users] ([userid], [username]) VALUES (3, N'Stacy')
INSERT [dbo].[users] ([userid], [username]) VALUES (4, N'Arnold')
INSERT [dbo].[users] ([userid], [username]) VALUES (5, N'Karen')
Here is a (probably oversimplified) example that addresses your issue. Note that it is always best to use parameters to prevent injection attacks, especially when verifying users.
CREATE PROCEDURE AddUser
#username varchar(20)
AS
IF NOT EXISTS(SELECT username FROM users WHERE username=#username)
INSERT INTO users(username) VALUES (#username)
Related
I have an application written in C#. The app writes records to a table in a SQL database. The table looks like this:
CREATE TABLE [dbo].[Products]
(
[Id] [UNIQUEIDENTIFIER] NOT NULL,
[ReferenceNumber] [INT] IDENTITY(0,1) NOT NULL,
[Name] [UNIQUEIDENTIFIER] NOT NULL,
[OnHand] [INT] NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
Please note that the Id field is a Guid that is sent from the application. The ReferenceNumber is an auto-incremented field generated by the database. I'm writing records to the table via the stored procedure.
CREATE PROCEDURE [dbo].[ProductSave]
(#id UNIQUEIDENTIFIER,
#name NVARCHAR(MAX),
#onHand INT)
AS
DECLARE #referenceNumber INT
IF EXISTS (SELECT 1 FROM Products WHERE [Id] = #id)
BEGIN
UPDATE [Products]
SET [Name] = #name,
[OnHand] = #onHand
WHERE [Id] = #id
END
ELSE BEGIN
INSERT INTO [Products] ([Id], [Name], [OnHand])
VALUES (#id, #name, #onHand)
END
SET #referenceNumber = SCOPE_IDENTITY()
RETURN #referenceNumber
GO
The stored procedure returns the ReferenceNumber of the product that was inserted or updated. I need that ReferenceNumber in my C# code. At the same time, I need to send insert / update a batch of records at once.
At this time, my C# code looks like this:
public static void SaveProducts(List<Product> products)
{
var connString = ConfigurationManager.ConnectionStrings["productDb"].ConnectionString;
using (var conn = new SqlConnection(connString)
{
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[dbo].[ProductSave]";
cmd.Parameters.Add(new SqlParameter("#id", SqlDbType.UniqueIdentifier));
cmd.Parameters.Add(new SqlParameter("#name", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("#onHand", SqlDbType.Int));
foreach (var product in products)
{
cmd.Parameters[0].Value = product.Id;
cmd.Parameters[1].Value = product.Name;
cmd.Parameters[2].Value = product.OnHand;
}
cmd.ExecuteNonQuery();
// How do I assign the ReferenceNumber to each Product instance?
}
}
}
When I execute this, there's some weird behavior. It doesn't seem all products get saved. Why would that be? At the same time, and I believe related, is how do I get the ReferenceNumbers from the database and assign them to their products in code?
I really need to save the products in a batch. I can't save them one-at-a-time. At the same time, I do need to get the ReferenceNumber back from the database and assign it to the Product for use in code. What am I missing / doing wrong?
Have a strange issue, think it's related to the OUT parameters in my stored procedure?
Stored procedure:
ALTER PROCEDURE [dbo].[uspProcessResetRequest]
#user uniqueidentifier,
#company uniqueidentifier,
#mobile nvarchar(50) OUT, -- return phone number
#system nvarchar(256) OUT -- return system name
AS
BEGIN
SET NOCOUNT ON;
SET #mobile = 'error' --for debugging
SET #system = 'error' --for debugging
-- Select matching row from ResetQueue into temporary table
INSERT INTO ResetHistory ([User_ID], MobilePhone, Company_ID, [System], [Status], ResetIP)
SELECT
rq.[User_ID], u.MobilePhone, u.Company_ID, rq.[System], '3', rq.ResetIP
FROM
ResetQueue rq
INNER JOIN
[User] u ON rq.[User_ID] = u.ObjectGuid
WHERE
u.Company_ID = #company
AND rq.[User_ID] = #user
AND rq.[Status] = '2'
SELECT #mobile = u.[MobilePhone]
FROM [user] u
WHERE u.ObjectGuid = #user
SELECT #system = rq.[system]
FROM ResetQueue rq
WHERE rq.[User_ID] = #user
DELETE FROM ResetQueue
WHERE [User_ID] = #user
END
Calling C# code:
try
{
using (SqlConnection conn = new SqlConnection(strConnection))
{
using (SqlCommand cmd = new SqlCommand("dbo.uspProcessResetRequest", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#user", SqlDbType.UniqueIdentifier).Value = strUser;
cmd.Parameters.AddWithValue("#company", SqlDbType.UniqueIdentifier).Value = strCompany;
cmd.Parameters.AddWithValue("#mobile", SqlDbType.NVarChar).Direction = ParameterDirection.Output;
cmd.Parameters.AddWithValue("#system", SqlDbType.NVarChar).Direction = ParameterDirection.Output;
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
string[] strResult = new string[] { (string)cmd.Parameters["#mobile"].Value, (string)cmd.Parameters["#system"].Value };
return strResult;
}
}
}
catch (Exception err)
{
string strError = err.Message; // Log error
return new string[] { "", "" };
}
The stored procedure runs just fine - all code gets executed but cmd.ExecuteNonQuery(); throws an exception:
SqlException: Error converting data type nvarchar to int.
so I loose the output values.
I'm suspecting that the OUT parameters are facing some issues, but can't seem to crack this nut...
Most other posts I found for this issue relates to Text values being submitted to SqlInt parameters. This seems not the case with this one as the stored procedure actually executes and moves data around as expected.
#Gwashoppa, thx for taking time to answer my question. To my understanding return values are not to be mixed with OUTPUT parameters. But in any case I found the solution - data type and length needs to be set explicitly:
cmd.Parameters["#mobile"].SqlDbType = SqlDbType.NVarChar;
cmd.Parameters["#mobile"].Size = 50;
I thought that happened in the AddWithValue function, but apparently not.
Your C# code looks fine... The fact that the line cmd.ExecuteNonQuery(); throws an exception, signifies that the error must lie with some parameter used by the SP.
You will need to isolate your SP in steps to identify the problem area...
For instance, in this statement does the ResetHistory table have the same column types used in your select?
INSERT INTO ResetHistory ([User_ID], MobilePhone, Company_ID, [System], [Status], ResetIP)
SELECT rq.[User_ID], u.MobilePhone, u.Company_ID, rq.[System], '3', rq.ResetIP
FROM ResetQueue rq
...
In this statement, does the mobilephone column return a Varchar value?
SELECT u.[MobilePhone]
FROM [user] u
WHERE u.ObjectGuid = #user
In this statement, does the system column return a Varchar value?
SELECT rq.[system]
FROM ResetQueue rq
WHERE rq.[User_ID] = #user
I've a table like this
I've another table sections in which there are 6 sections with id from 1 to 6. I get a list of section id from user which gives information about the current active section of user. Suppose the list returned to me has section ids as follows {2,3,4,5} for user with id 1. Now my question is
How can I pass this list of section ids to my stored procedure
I want to update is active flag of record where section id is 1 and user id is 1 since the list of section ids doesn't have an entry of 1
I want to insert a new record of section id 5 for user id 1 in same table as it is returned in list of section ids.
Can anyone please tell me how to achieve this?
I can get total section id's from the following query
select Id from sections
but don't know i will iterate between total list of section id's and compare the list of section ids returned from C#
To answer the complete question.
1. Like I said in the comment: Table valued parameters
First create a UDTT to store your sections ids for input for the stored procedure.
CREATE TYPE [dbo].[SectionIdUDTT] AS TABLE(
[id] int NOT NULL
)
Use this UDTT as a parameter for your stored procedure:
ALTER PROCEDURE [dbo].[YourSPname] #SectionId SectionIdUDTT readonly,
#UserId INT
AS
BEGIN
In order to call this stored procedure you must first fill the SectionIdUDTT before you call the stored procedure.
DECLARE #SectionId AS SectionIdUDTT;
INSERT INTO #SectionId
--your values here
EXEC YourSPname #SectionId, #UserId;
Take a look at DataTable class in order to call this from C#. Make sure the user has execute permissions on the UDTT.
2 in the stored procedure
Set the records to inactive of the user that already existed but are not in the table valued parameter.
UPDATE YourTable
SET IsActive = 0
WHERE UserId = #UserId
AND SectionId NOT IN (SELECT id FROM #SectionId)
AND SectionId IN (SELECT Id FROM sections) --Not really needed
3 in the stored procedure
Just insert the record that not yet exists. I just assume that id is an identity column.
INSERT INTO YourTable (UserId, SectionId, IsActive)
SELECT #UserId,
s.id,
1
FROM #SectionId s
WHERE NOT EXISTS (
SELECT 1
FROM YourTable y
WHERE y.SectionId = s.id
AND y.UserId = #UserId
)
I would recommend Transaction management in your stored procedure.
First I created a method which would call stored procedure, to that method I passed a list of sections(sections table entity) and Customer Id
public bool SaveChatSectionUserMapping(List<Sections> lstSections, int customerId)
{
con = new SqlConnection(connectionString);
bool isUpdated = false;
try
{
string xmlString = string.Empty;
xmlString = XMLOperations.WriteXML(lstSections);
SqlCommand cmd = new SqlCommand("spUpdateSections", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#XMLData", SqlDbType.Xml).Value = xmlString;
cmd.Parameters.Add("#CustomerId", SqlDbType.Int).Value = customerId;
SqlParameter param = new SqlParameter();
param.SqlDbType = SqlDbType.Bit;
param.Direction = ParameterDirection.Output;
param.ParameterName = "#Result";
cmd.Parameters.Add(param);
con.Open();
cmd.ExecuteNonQuery();
isUpdated = (param.Value != DBNull.Value) ? Convert.ToBoolean(param.Value) : false;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (con.State == ConnectionState.Open)
con.Close();
}
return isUpdated;
}
The value of xmlString I get from this line xmlString = XMLOperations.WriteXML(lstSections); is like this
<ArrayOfSection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Sections>
<UserId>1</UserId>
<SectionId>1</SectionId>
<IsActive>true</IsActive>
</Sections>
<Sections>
<UserId>1</UserId>
<SectionId>2</SectionId>
<IsActive>true</IsActive>
</Sections>
<Sections>
<UserId>1</UserId>
<SectionId>5</SectionId>
<IsActive>true</IsActive>
</Sections>
</ArrayOfSection>
Now in stored Procedure
CREATE Procedure [dbo].[spUpdateSections]
(
#XMLData as XML,
#CustomerId INT,
#Result int Output
)
AS
BEGIN
SET NOCOUNT ON;
Declare #ErrorCode Varchar(100) = '',#propertyCount VArchar(100) = '',#currentCount int=1,#SectionId int, #IsActive bit
Begin TRY
UPDATE Sections
SET
IsActive = 0
WHERE
UserId = #CustomerId
SELECT #propertyCount = convert(VARCHAR, #XMLData.query ('count(/ArrayOfSection/Sections)'))
SET #currentCount = 1
while (#currentCount<=#propertyCount)
Begin
SET #SectionId = #XMLData.value('data(/ArrayOfSection/Sections[sql:variable("#currentCount")]/SectionId)[1]', 'INT')
SET #IsActive = #XMLData.value('data(/ArrayOfSection/Sections[sql:variable("#currentCount")]/IsActive)[1]', 'BIT')
If Exists (SELECT *
FROM
Sections
WHERE
UserId = #CustomerId
AND SectionId = #SectionId)
BEGIN
IF(#IsActive=1)
BEGIN
UPDATE Sections
SET
IsActive = 1
WHERE
UserId = #CustomerId AND
SectionId = #SectionId
END
END
ELSE
BEGIN
IF(#IsActive=1)
BEGIN
INSERT INTO Sections
([SectionId]
,[UserId]
,[IsActive])
VALUES
(#SectionId,#CustomerId,1)
END
END
SET #currentCount = #currentCount + 1
End
SET #Result = 1
ErrorCode:
If(#ErrorCode !='')
BEGIN
--SELECT #BSErrorResult = doctor.GetErrorCodeDetail(#ErrorCode)
SET #Result = 2
END
END TRY
BEGIN CATCH
--Declaring Variable for formating error
Declare #ErrorMessage VARCHAR(max),
#ErrorSeverity INT,
#ErrorState INT
--SELECTING TECHNICAL ERROR
SELECT #ErrorMessage = error_message()
, #ErrorSeverity = error_severity()
, #ErrorState = error_state()
, #ErrorMessage = #ErrorMessage + ' ' + db_name() + ' ' + ' ' + object_name(##PROCID);
RAISERROR (
#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
End CATCH
END
Another way to Generate XMl would be by using XElement like this
XElement xml = new XElement("Sections",
from col in lstSections
select new XElement("rows",
new XElement("UserId", col.UserId),
new XElement("SectionId", col.SectionId),
new XElement("IsActive", col.IsActive)));
string xmlString = xml.ToString();
In my SQL database I have a table for every state in the USA. The user can create multiple pages in different states. Each table has a "UserID" column that will always match the logged in user. How do I search multiple tables for the "UserID"?
string sqlquery = "SELECT * FROM[all tables] WHERE #UserID ='" + userID.Text + "'";
SqlConnection conn1 = new SqlConnection(connString);
SqlCommand comm1 = new SqlCommand(sqlquery, conn1);
SqlDataReader DR1 = comm1.ExecuteReader();
if (DR1.Read())
{
Textbox1.Text = DR1.GetValue(0).ToString();
}
I haven't seen your database schema, but I can already tell you need to refactor it. There's no need to have a table for each state, and the maintenance on that could be rough. What you should have is a table holding all of the states, and then another table with a reference to the State table that holds whatever information you want (your "pages")
CREATE TABLE States
(
StateID int not null, --PK
StateName nvarchar(32) not null
)
CREATE TABLE Pages
(
PagesID int not null, --PK
StateID int not null, --FK
UserID int not null
//Whatever other columns you need
)
Now, you can query the Pages table based on a specific Page, State or User
SELECT * FROM Pages WHERE UserID = (userId)
SELECT * FROM Pages WHERE StateID IN (1, 5, 20)
SELECT * FROM Pages WHERE PageID = (pageID)
The right answer is that you need to change your database so that all this information is held in a single table. The easy answer is to take a few minutes to create a view that unions all the tables together so that you can access them as if they were all in one table.
The only difference in my answer from the others though is that I wouldn't union them in your c# code. I would just create an actual view on the database that unions them all together. The view will be simple (though long) and your code will be simple.
If tables have the same columns I would go for something like. Using the sql UNION.
var statesTables = new[]{"NY, Texas, Oregano, Alabama}";
var qBuild = new StringBuilder();
qBuild.Append(string.Format("SELECT * FROM {0} WHERE UserId = #userId ", statesTables[0]));
for(int i=1;i<stateTables.Length;i++){
qbuild.Append(string.Format("UNION SELECT * FROM {0} WHERE UserId = #userId ", statesTables[i]))
}
SqlConnection conn1 = new SqlConnection(connString);
SqlCommand comm1 = new SqlCommand(qBuild.ToString(), conn1);
comm1.Parameters.Add(new SqlParameter("userId", userId));
It will generate SQL:
SELECT * FROM NY WHERE UserId = #userId
UNION
SELECT * FROM Texas WHERE UserId = #userId
UNION
SELECT * FROM Oregano WHERE UserId = #userId
UNION
SELECT * FROM Alabama WHERE UserId = #userId
If there are some different columns, replace * with column names tables have in common.
BUT, as other suggests, refactoring your DB schema would be the best!
Why doesn't this work? I get an error saying the number cannot be infinity. However, I had to take this away from an insert statement so that it doesn't post entries twice.
Where do I have to incorporate this piece of code to get it to allow my code to loop as a new ID?
cmd = new SqlCommand(#"SELECT CAST(scope_identity() as int)", con);
int aID = Convert.ToInt32(cmd.ExecuteScalar());
In general you can have a stored procedure to do the INSERT and return the last inserted identity with an out parameter, as you can see in an example here: http://www.objectreference.net/post/SCOPE_IDENTITY()-return-the-id-from-the-database-on-insert.aspx
CREATE PROCEDURE [dbo].[Customer_Insert]
#Name VARCHAR(255),
#Email VARCHAR(255),
#Phone VARCHAR(255),
#CustomerID INT OUTPUT
AS
BEGIN
INSERT INTO dbo.Customer ([Name], Email, Phone)
VALUES (#Name,#Email,#Phone)
SET #CustomerID = CAST(SCOPE_IDENTITY() AS INT)
END