Unique constraint on table column - sql

I'm having a table (an existing table with data in it) and that table has a column UserName.
I want this UserName to be unique.
So I add a constraint like this:
ALTER TABLE Users
ADD CONSTRAINT [IX_UniqueUserUserName] UNIQUE NONCLUSTERED ([UserName])
Now I keep getting the Error that duplicate users exist in this table.
But I have checked the database using the following query:
SELECT COUNT(UserId) as NumberOfUsers, UserName
FROM Users
GROUP BY UserName, UserId
ORDER BY UserName
This results in a list of users all having 1 as a NumberOfUsers. So no duplicates there.
But when I'm checking the username he fails I see the following result:
beluga
béluga
So apperently he fails to compare an "e" and "é" or "è" ... It's like he ignores these, is there any way that sql doesn't ignore these accents when adding the unique key contraint.
SOLUTION:
THX to you guys I've found the solution.
This fixed the problem:
ALTER TABLE Users
ALTER COLUMN UserName nvarchar(250) COLLATE SQL_Latin1_General_CP1_CI_AS

The collation you are using most likely ignores case and accents when comparing. You'll need to change the collation.
Latin1_General_CI_AI Ignores case and accents
Latin1_General_CI_AS will not ignore accents
List of SQL server collation names here.

Your query groups by UserID too - you don't want to be doing that.
Use:
SELECT COUNT(*) as NumberOfUsers, UserName
FROM Users
GROUP BY UserName
ORDER BY UserName
Your query would only show up users with the same name and same user ID. Or, maybe, order the data by COUNT(*) so the last row that shows up is most likely the troublemaker?
You could also have problems with collation as others have suggested, but normally, GROUP BY would be self-consistent.

Presumably UserId is your primary key. Since it's part of what you are grouping by, you are guaranteed to get a single row per group. Take the "userId" column out of your group by.

As Andrew Barrett says, the default collation in MySQL doesn not recognize accents correctly.
Change the collation of your fields to UTF8_unicode_ci and it should see accents properly.
ci means case insensitive, and you can use a different collation if case is important.
You can create a new table with the new collation, then copy * from the existing table into the new one.

Also note that you can also create just the table you are interested in the relevent collation (instead of server wide) .So you could also do something like :
CREATE TABLE Users (c1 varchar (10), .., COLLATE Latin1_General_CI_AS NULL )

Related

SQL Concatenate column values and store in an extra column

I am using SQL Server 2019 (v15.0.2080.9) and I've got a simple task but somehow I am not able to solve it...
I have a little database with one table containing a first name and a last name column
CREATE TABLE [dbo].[person]
(
[first_name] [nchar](200) NULL,
[last_name] [nchar](200) NULL,
[display_name] [nchar](400) NULL
) ON [PRIMARY]
GO
and I want to store the combination of first name with an extra whitespace in between in the third column (yes I really have to do that...).
So I thought I might use the CONCAT function
UPDATE [dbo].[person]
SET display_name = CONCAT(first_name, ' ', last_name)
But my display_name column is only showing me the first name... so what's wrong with my SQL?
Kind regards
Sven
Your method should work and does work. The issue, though is that the data types are nchar() instead of nvarchar(). That means they are padded with spaces and the last name is starting at position 201 in the string.
Just fix the data type.
In addition, I would suggest that you use a computed column:
alter table person add display_name as (concat(first_name, ' ', last_name));
This ensures that the display_name is always up-to-date -- even when the names change.
Here is a db<>fiddle.
As a note: char() and nchar() are almost never appropriate. The one exception is when you have fixed length strings, such as state or country abbreviations or account codes. In almost all cases, you want varchar() or nvarchar().

How to determine if SQLite column created with COLLATE NOCASE

A column in a SQLite db must be COLLATE NOCASE. I assume there is no way to add that capability to an existing table, so I'm prepare to recreate the table with it. How can I determine if the existing column is COLLATE NOCASE in order to avoid recreating the table every time it is opened?
How can I determine if the existing column is COLLATE NOCASE
The query
SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name='my_table'
will give you the CREATE TABLE statement for that table. You could inspect the DDL to determine if the column is already defined as COLLATE NOCASE.
You might not need to do that at all if it is sufficient to change the collations in the query. I mean you can just overwrite it in the query. It won't affect constraints or index, but depending on your use case, it might be good enough.
To be clear: the collate clause in the table definition is just a default for the queries. You can overwrite this in the queries.
e.g.
WHERE column = 'term' COLLATE NOCASE
or
ORDER BY column COLLATE NOCASE
However, not that SQLite's LIKE doesn't honor collate clause (use pragma case_sensitive_like instead).
The easiest and most general way is store a version number somewhere (in another table, or with PRAGMA user_version).
If you want to check the column itself, use a query with a comparison that is affected by the column's collation:
SELECT Col = upper(Col)
FROM (SELECT Col
FROM MyTable
WHERE 0 -- don't actually return any row from MyTable
UNION ALL
SELECT 'x' -- lowercase; same collation as Col
);

Select query to retrieve the value of primary key for a specific row in a table

I am struggling to retrieve the value of primary key for a table. We are using MS SQL Server 2005. The database was designed years back by somebody else (he didn't follow the normalization rules at all). He used Key (which is a keyword in sql server) as the column name for primary key of a table. So I cannot use query like this: select key from table_name where column2 = ?
Could anyone help to write a query to get the value of the primary key for a specific row something like this: select primary_key from tbale_name where column2 = ?
Yes you can, simply wrap column names in backticks:
select `key` from `table_name` where `column2` = ?
Alternatively, depending on your DB, you might use square brackets:
select table_name.[key] from table_name where table_name.[column2] = ?
Edit: I see you said "MS SQL". I think that one works with the square brackets. MySQL accepts the backtick syntax.

Keep strings case-insensitively unique in a database

I'd like to ensure that when a user wants to register a username in my system (web application), the user name is unique even if case is not regarded. So if a user named "SuperMan" is already registered, no other user must be allowed to register as "superman" or "SUPERman". This must be checked at database level.
In my current implementation, I do the following:
select count(*) from user where lower(name) = lower(?) for update;
-- If the count is greater than 0, abort with an error
-- Determine a new ID for the user
insert into user (id, name, …) values (?, ?, …);
I'm not sure whether the "for update" will lock the database far enough so that other users won't be able to register with an invalid name between the two SQL statements above. Probably it's no 100% safe solution. Unfortunately I cannot use unique keys in SQL because they will only compare case-sensitively.
Is there another solution to this? How about the following, to add more safety?
select count(*) from user where lower(name) = lower(?) for update;
-- If the count is greater than 0, abort with an error
-- Determine a new ID for the user
insert into user (id, name, …) values (?, ?, …);
-- Now count again
select count(*) from user where lower(name) = lower(?);
-- If the count is now greater than 1, do the following:
delete from user where id = ?;
-- Or alternatively roll back the transaction
The way I've done that is make a second column for the username that's either converted to all caps or all lower case. That I put a unique index on that. I use a trigger to generate the value in this other column so the code doesn't have to worry about it.
Come to think of it, you could use a function based index to get the same result:
CREATE UNIQUE INDEX user_name_ui1 ON user (lower(name))
Your assumption that an unique constraint will compare case sensitively is incorrect. They will compare according to the collation of the column. All commercial databases worth talking about support collations. Simply choose a case insensitive collation for your column, and declare a unique constraint on it. See:
Character Sets and Collations That MySQL Supports
Using SQL Server Collations
Oracle Linguistic Sorting and String Searching
SQLite collating sequences
...
Doing the enforcement by a lookup and before insert is not only extremely inefficient, is also incorrect under concurrency.
You can also change the way unique constraints work by changing the collation for that column: Unique constraint on table column

Storing a pre-processed varchar column in the database along with the original one

I have a big table with names and surnames. People search this database via a Web interface. PHP code queries the table with LOWER(#name) = LOWER(name). In order to make the search faster, I want to make a derived column in the table named lower_name and always LOWER() the names before storing. But when it comes to send the results to web interface, I want to show the original ones. However, I don't want to change the table structure. Is there some kind of Index that automatically does the trick or an option to create an "invisible column" in SQL Server or something like that?
You can create a presisted computed column with an index on it:
ALTER TABLE YourTable ADD lower_name AS LOWER(name) PERSISTED
go
CREATE NONCLUSTERED INDEX IX_YourTable_lower_name
ON YourTable (lower_name)
go
You do not INSERT or UPDATE this column, the DB will do it for you and always keep it in sync with your "name" column.
If you don't want to use a computed column you could create a view, that has the LOWER(name) AS lower_name column in it and put an index on that column:
http://msdn.microsoft.com/en-us/library/cc917715.aspx
You have various options:
declare the column Name case-insensitive using the COLLATE clause
compare Name and #Name using the COLLATE clause
create a computed column LowerName AS LOWER(Name) PERSISTED and index this computed column
PHP code queries the table with
LOWER(#name) = LOWER(name)
Thanks in a world of pain. LOWER(name) triggers a table scan. This is not making it faster, it is - brutally - a beginner mistake that kills performance. You should never compare against a calculation of the table fields, unless you ahve the result in a precaculated index (as you can do in SQL Server 2008, for example).
What about
WHERE name LIKE #name
which should ignore case... plus an Index on the name field.