14

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# ?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Simon Kiely
  • 5,880
  • 28
  • 94
  • 180

8 Answers8

21

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);
  • Nice (though probably unnecessary when only a single action is contemplated) – Damien_The_Unbeliever Mar 10 '12 at 18:32
  • 1
    @Damien_The_Unbeliever - It is worth considering when you have [high concurrency](http://dba.stackexchange.com/a/13384/2103). Probably not the case here but +1 from me. – Mikael Eriksson Mar 10 '12 at 18:38
  • @MikaelEriksson - you indicate it still needs `serializable` to avoid some issues - is there then some clear difference between this and other approaches? – Damien_The_Unbeliever Mar 10 '12 at 18:50
  • @Damien_The_Unbeliever - The difference is that `updlock` hint is not needed with `merge`. I have read that you can use `holdlock` instead of `serializable` with the merge but I have not tested that. – Mikael Eriksson Mar 10 '12 at 18:57
  • 1
    @Mikael Eriksson: At the default isolation level, MERGE is not concurrent: another connection can insert between the match and the insert part of the merge. [Example code here](http://pastebin.com/eALK9GrG) – Andomar Mar 10 '12 at 19:10
  • @Andomar - well yes. Thats kind of what I answers in the linked answer. The code you linked looks almost exactly like the one I used when testing. – Mikael Eriksson Mar 10 '12 at 19:15
  • 2
    @MikaelEriksson: The serializable hint and concurrency don't go together because the effect of serializable is to not be concurrent. After all, serial is an antonym of concurrent. I think the accepted method for high concurrency is to catch the primary key violation in the rare case that a clash occurs. – Andomar Mar 10 '12 at 19:20
  • Because you can save expensive cycles by not invoking the error handling every time (in most cases, you can determine up front that the violation will occur) : http://www.mssqltips.com/sqlservertip/2632/checking-for-potential-constraint-violations-before-entering-sql-server-try-and-catch-logic/ It all depends on what your confidence level is for succeed vs. fail. – Aaron Bertrand Mar 10 '12 at 20:13
  • Is this approach okay if the target table has about 10-15 million rows and username column is indexed ? What if we have more than 1 column to check and they are indexed too ? – Steam Feb 19 '14 at 01:05
12

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.

Phantom
  • 378
  • 3
  • 19
user194076
  • 8,787
  • 23
  • 94
  • 154
  • 4
    On a completely side-note - you've earned over 1K rep so far - is it not time to pick a decent name? – Damien_The_Unbeliever Mar 10 '12 at 18:29
  • I'll know if they are logging in for the first time since their name will be in the database! There is no registering on my website; I use external authentication from another site :). Thanks for the response! – Simon Kiely Mar 10 '12 at 18:32
  • Have you considered using stored procedure for this? – sam yi Mar 14 '12 at 04:34
4
IF NOT EXISTS (select * from users where username = 'username')
BEGIN
    INSERT INTO ...
END
Sascha
  • 10,231
  • 4
  • 41
  • 65
4

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

Jason
  • 3,844
  • 1
  • 21
  • 40
2

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!

levkaster
  • 2,670
  • 2
  • 25
  • 32
1

@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')
Steam
  • 9,368
  • 27
  • 83
  • 122
1

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;
        }
Amen Ayach
  • 4,288
  • 1
  • 23
  • 23
0

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)
ron tornambe
  • 10,452
  • 7
  • 33
  • 60