Collation issue while join query in MVC5 and SQL Server 2012 - sql-server

When I join two tables I got a collation issue that is
System.Data.SqlClient.SqlException: Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_CI_AI" in the equal to operation.
Then I set the collation in my db using the following code
ALTER DATABASE [CAM] COLLATE SQL_Latin1_General_CP1_CI_AS;
ALTER TABLE CAM_Users
ALTER COLUMN [EmployeeCode] VARCHAR(50)
COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL;
But I still get the same error.
My join query is this:
List<DTOUserManagement> users = (from CAMuser in _unitOfWorkAsync.RepositoryAsync<CAM_Users>().Queryable()
join QRuser in _unitOfWorkAsync.RepositoryAsync<CAM_V_EmployeeMaster>().Queryable() on CAMuser.EmployeeCode equals QRuser.EmployeeCode into t
from t1 in t.DefaultIfEmpty()
join CAMDomain in _unitOfWorkAsync.RepositoryAsync<CAM_Domain>().Queryable() on CAMuser.DomainID equals CAMDomain.DomainID into t2
from t3 in t2.DefaultIfEmpty()
where CAMuser.IsActive
select new DTOUserManagement
{
TransactUserCode = CAMuser.TransactUserCode,
EmployeeCode = CAMuser.EmployeeCode,
EmployeeName = t1.EmployeeName,
Email = t1.EMail,
DomainID = CAMuser.DomainID,
DomainName = t3.DomainName,
IsActive = CAMuser.IsActive,
AssignedRole = CAMuser.AssignedRoles
}).ToList();
How can I solve this?
Please reply anybody

The problem here is that the COLLATION must match on joining columns.
There are two ways to fix this. First you could change the collation on each column. Or second, you can change the collation at execution time. Here's an example of the second approach:
Sample Data
/* T1 and T2 are identical tables in structre and content, except
* for the collation.
*/
DECLARE #T1 TABLE
(
ID VARCHAR(3) COLLATE SQL_Latin1_General_CP1_CI_AS
)
;
DECLARE #T2 TABLE
(
ID VARCHAR(3) COLLATE Latin1_General_CI_AI
)
;
INSERT INTO #T1
(
ID
)
VALUES
('x'),
('y'),
('z')
;
INSERT INTO #T2
(
ID
)
VALUES
('x'),
('y'),
('z')
;
Anti Pattern - Will not Work
/* This query will failed with the error:
* Cannot resolve the collation conflict between "Latin1_General_CI_AI" and "SQL_Latin1_General_CP1_CI_AS" in the equal to operation.
*/
SELECT
*
FROM
#T1 AS t1
INNER JOIN #T2 AS t2 ON t1.ID = t2.ID
;
Corrected - No Error
/* Success.
*/
SELECT
*
FROM
#T1 AS t1
INNER JOIN #T2 AS t2 ON t1.ID = t2.ID COLLATE Latin1_General_CI_AI
;

You need to check your tables that are involved in these JOINs here to verify that all columns involved have the same collation.
You can do this with this SQL query:
SELECT
TableName = t.Name,
ColumnName = c.name,
Collation = c.collation_name
FROM
sys.columns c
INNER JOIN
sys.tables t ON t.object_id = c.object_id
INNER JOIN
sys.types ty ON c.system_type_id = ty.system_type_id
WHERE
t.name IN ('CAM_Users', 'CAM_Domain') -- add any further tables to check
AND ty.name IN ('char', 'nchar', 'nvarchar', 'varchar')
ORDER BY
t.name, c.name
If there are columns that do not match the database default collation, you need to change those to be the same as all other columns. Once all the string columns in those tables are the same collation, then your joins should work.
Update: use this query to find those tables & columns that do not have the current default database collation:
SELECT
TableName = t.Name,
ColumnName = c.name,
Collation = c.collation_name
FROM
sys.columns c
INNER JOIN
sys.tables t ON t.object_id = c.object_id
INNER JOIN
sys.types ty ON c.system_type_id = ty.system_type_id
WHERE
ty.name IN ('char', 'nchar', 'nvarchar', 'varchar')
AND c.collation_name <> 'SQL_Latin1_General_CP1_CI_AS'
ORDER BY
t.name, c.name

Related

Need to change GUID to varchar or char

I am trying to use a reference table to update a code, but I am getting the error:
Conversion failed when converting from a character string to
uniqueidentifier
The ID is a guid: 086B9FE7-3980-47D7-BB05-003708F1D564 and the reference code I want to use is 4 characters, like H100.
I received the initial error when I tried to alter the datatype in the table and then update the file with the values from the reference table. I have tried converting and casting based on other articles, but even if I cast or convert successfully, I still get the same message.
RefTable:
Id | ReportCode
6340FCEA-161C-42F4-8D7F-46B4C2E6C4E2 | H100
DataTable:
CauseId
6340FCEA-161C-42F4-8D7F-46B4C2E6C4E2
Code I am using to try and update. The first works, and the second bring the error msg:
select cast(nvarchar(36), ID) as ID
from [dbo].[reftable]
UPDATE dbo.datatable
SET causeid = L.reportcode
FROM dbo.datatable S
join dbo.reftable L on S.causeid = L.id
Uniqueidentifier is a particular column type that holds 36 bytes, with some hyphen characters in the middle. Asigning a string value that doesn't match it's format will always fail when trying to convert it.
-- Conversion failed when converting from a character string to uniqueidentifier.
SELECT CONVERT(UNIQUEIDENTIFIER, 'H100')
-- OK
SELECT CONVERT(UNIQUEIDENTIFIER, 'c029f8be-29dc-41c1-8b38-737b4cc5a4df')
If you want to use a common VARCHAR as your new causeid value, you need to first convert the column type to VARCHAR (or NVARCHAR).
ALTER TABLE dbo.datatable ALTER COLUMN causeid VARCHAR(200) -- NOT NULL if you need
If you can't alter the data type it's probably because there is an INDEX or a CONSTRAINT linked to it. When you try to alter, the SQL engine will tell you which object is linked to it. You will have to drop them, alter the column type and then create them again.
I use these queries to check indexes and constraints. I updated the search values for your table.
DECLARE #table_name VARCHAR(200) = 'datatable'
DECLARE #column_name VARCHAR(200) = 'causeid'
-- Indexes
SELECT
SchemaName = SCHEMA_NAME(t.schema_id),
TableName = t.name,
IndexName = ind.name,
IndexType = CASE ind.index_id WHEN 0 THEN 'Heap' WHEN 1 THEN 'Clustered' ELSE 'Nonclustered' END,
Disabled = ind.is_disabled,
ColumnOrder = ic.index_column_id,
ColumnName = col.name,
ColumnType = y.name,
ColumnLength = y.max_length,
ColumnIncluded = ic.is_included_column
FROM
sys.indexes ind
INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id
INNER JOIN sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id
INNER JOIN sys.tables t ON ind.object_id = t.object_id
INNER JOIN sys.types y ON y.user_type_id = col.user_type_id
WHERE
t.is_ms_shipped = 0 AND
t.name = #table_name AND
col.name = #column_name
ORDER BY
SchemaName,
t.name,
ind.name,
ic.index_column_id
-- Constraints
SELECT
TableName = t.Name,
ColumnName = c.Name,
dc.Name,
dc.definition
FROM
sys.tables t
INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id
INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id
WHERE
t.name = #table_name AND
c.name = #column_name
ORDER BY
t.Name

How to get the name of the Parent Table with the help of Column name in Sys.Columns

I wanted to get the table name. I have the column name and when I try to look up at the Sys.Columns table I get the matching name of the column. How will I get the table name to which the required column is associated
SELECT OBJECT_SCHEMA_NAME(object_id) AS TableSchemaName,
OBJECT_NAME(object_id) AS TableName
FROM sys.columns
WHERE name = 'YourColumnName'
I hope this helps:
select t.name from sys.columns c
inner join sys.tables t
on c.object_id = t.object_id
where c.name = 'insert column name here'
select OBJECT_NAME(object_id) as TableName from sys.Columns where name='columnNamehere'
Try this
declare #columnName As varchar(50) = 'ParentColumnName'
select t.name from sys.tables t
join sys.columns c
on c.object_id = t.object_id
and c.name = #columnName
select name as 'TableName' from sys.tables where object_id=
(select object_id from sys.columns where name='UserName')
I know that this is an old question, but the answers listed to date do not get at what the parent table name is for a view's columns, nor if a column is aliased to have a new name with respect to the column name in the parent table.
Unfortunately, (at least in 2008R2) it seems that even with registering your Views to a Schema, the referencing_minor_id of sys.dm_sql_referenced_entities (or the equivalent column from sys.SQL_Modules) is always set to zero. However, you can retrieve all referred-to tables (and parent views), along with which fields of those tables are queried with sys.dm_sql_referenced_entities (or sys.SQL_Modules). However, it does not capture the oorder of those bindings, so the following won't quite work to link view columns directly to table columns, but it'll provide an approximation:
DECLARE #obj_name nvarchar(50) = 'Table_or_View_name_here';
with obj_id as (
select object_id, name, OBJECT_SCHEMA_NAME(object_id)+'.'+name as qualified_name from sys.all_objects as o
where o.name = #obj_name
),
tv_columns as ( -- table or view
select o.name as Obj_Name, c.* from sys.columns as c join
obj_id as o on
c.object_id=o.object_id
),
sql_referenced_entities as (
SELECT
o.name as referencing_name,
o.object_id,
COALESCE(NULLIF(referencing_minor_id,0),ROW_NUMBER() OVER (ORDER BY (select 'NO_SORT'))) as referencing_minor_id,
referenced_server_name,
referenced_database_name,
referenced_schema_name,
referenced_entity_name,
referenced_minor_name,
referenced_id,
referenced_minor_id,
referenced_class,
referenced_class_desc,
is_caller_dependent,
is_ambiguous
FROM obj_id as o, sys.dm_sql_referenced_entities((select qualified_name from obj_id), 'OBJECT') where referenced_minor_id<>0
)
select
c.object_id as object_id,
o.name as object_name,
c.column_id,
c.name as column_name,
c2.object_id as parent_table_object_id,
o2.name as parent_table_name,
c2.column_id as parent_column_id,
c2.name as parent_column_name
-- ,c.*,
-- ,c2.*
from sys.columns as c join
obj_id as o on
c.object_id=o.object_id left outer join
(sql_referenced_entities as s join
sys.all_objects as o2 on
s.referenced_id=o2.object_id and s.referenced_class=1 join
sys.columns as c2 on
s.referenced_id=c2.object_id and s.referenced_minor_id=c2.column_id
) on
c.object_id=s.object_id and c.column_id=s.referencing_minor_id
To get the true aliases used, as well as any calculations involving the combinations of multiple fields, you would have to parse the output of either OBJECT_DEFINITION(OBJECT_ID('schema.view')) (or potentially exec sp_helptext 'schema.view'), as follows
Starting with OBJECT_DEFINITION(OBJECT_ID('schema.view'))
Mark what is enclosed in single quotes as superseeding the removal and other rules to follow
Remove blocks between /* */ comments
Remove any text after --, up to the next linebreak sequence (see EXEC SP_HELPTEXT 'sp_helptext' for their end-of-line code)
Look up table/subselect aliases from the FROM clause
Parse out the SELECT clause and break on commas, rather than end-of-line.
Reduce any contiguous whitespace to a single space character. Force whitespace before [ and after ] when a dot/period (or whitespace) don't already appear.
We'll put the above into a stored procedure that we'll call usp_helptext_for_view
See which table_alias.field_name are aliased or simply appear as field_name. See the below code snippet to see how to seperate the field alias from the definition.
Link the view to the table as appropriate.
drop table #s
create table #s (id bigint identity(1,1) primary key, text nvarchar(max))
insert into #s (text) exec usp_helptext_for_view #qualified_viewname
with s as (select
id,
text,
az=(select
MIN(x*(case x when 0 then null else 1 end)) FROM (VALUES
(charindex(#viewfieldname,text COLLATE Latin1_General_CI_AS)),
(charindex('['+#viewfieldname+']',text COLLATE Latin1_General_CI_AS)),
(charindex('AS ['+#viewfieldname+']',text COLLATE Latin1_General_CI_AS)),
(charindex('as '+#viewfieldname,text COLLATE Latin1_General_CI_AS))
) AS value(x)
),
NULLIF(charindex('=',text),0)) as eq --oh, the irony of how the two different styles are applied
FROM #s
)
SELECT
#viewfieldname as ViewField,
CASE eq WHEN NULL
THEN IIF(az IS NULL, NULL, LEFT(text, az-2))
ELSE RIGHT(text,LENGTH(text)-eq) -- alternately ELSE CASE az WHEN NULL THEN NULL WHEN <eq THEN RIGHT(text,LENGTH(text)-eq) ELSE NULL END
END as ViewFieldDefinition,
id as sortPosition
FROM s
WHERE text like '%'+#viewfieldname+'%' -- you should be able to eliminate this clause without affecting the results.
ORDER BY id, #viewfieldname

Implicit conversion of varchar value to varchar - collation conflict

I'm getting the following error whilst running this script. I've tried using the following:
COLLATE Latin1_General_CI_AS. Please can it be sorted? Thanks
Msg 457, Level 16, State 1, Line 8
Implicit conversion of varchar value to varchar cannot be performed because the collation of the value is unresolved due to a collation conflict
DECLARE #AccountID INT
SET #AccountID = 12
SELECT TOP 1 ac.AccountID,
co.Email,
ao.AccountOptionID
FROM CRM.acc.Account ac
INNER JOIN CRM.[profile].[Profile] pr
ON pr.ProfileID = ac.ProfileFK
INNER JOIN CRM.[profile].Contact co
ON pr.ProfileID = co.ProfileFK
LEFT JOIN CRM.acc.[AccountOption] ao
ON ao.AccountFK = ac.AccountID
LEFT JOIN (
SELECT OptionID
FROM CRM.acc.[Option]
WHERE [Name] = 'SMS messages') op
ON op.OptionID = ao.OptionFK
WHERE ac.AccountID = #AccountID
UNION ALL
SELECT u.UnsubscribeID,
u.EmailAddress,
u.SentEmailFK
FROM Email.dbo.Unsubscribe u
INNER JOIN (
SELECT CASE
WHEN AccountTypeFK = 2 THEN OnlineBillingEmail
ELSE EmailBillingEmail
END [EmailAddress]
FROM CRM.acc.Account
WHERE AccountID = #AccountID
) ace
ON ace.EmailAddress COLLATE DATABASE_DEFAULT = u.EmailAddress COLLATE DATABASE_DEFAULT
WHERE ISNULL(ace.EmailAddress, '') != ''
Seems that you have different collation types on your database. Then you can have issues when joining tables or to tables in other databases, as in this case. To get around this you can specify the collation of columns or force a collation using the COLLATE clause when joining two columns. Check more info about collation in MSDN
Also you need to specify the COLLATE clause where you are using the problematic column(s). Check this answer, it answer a very similar question
It is better to stick to a single collation globally. Otherwise you will have problems.
See a detailed explanation of the different collation styles
Edited: to check column collation use this snippet
SELECT name, collation_name
FROM sys.columns
WHERE OBJECT_ID IN (SELECT OBJECT_ID
FROM sys.objects
WHERE type = 'U'
AND name = 'your_table_name'
)
AND name = 'your_column_name'
Edited: added snippet to get all columns with different collation on a database
SELECT [TABLE_NAME] = OBJECT_NAME([id]),
[COLUMN_NAME] = [name],
[COLLATION_NAME] = collation
FROM syscolumns
WHERE collation <> 'your_database_collation_type'
AND collation IS NOT NULL
AND OBJECTPROPERTY([id], N'IsUserTable')=1
try this
ace.EmailAddress COLLATE Latin1_General_CI_AS = u.EmailAddress COLLATE Latin1_General_CI_AS
Because you are doing a union all to the CRM table which has a different collation. You need to explicitly define the collation of your columns going into your union. In your case that will be UnsubscribeID,EmailAddress and SentEmailFK all should be defined with collate database_default
Running the below should work. Let me know in the comments if this works
SELECT TOP 1 ac.AccountID,
co.Email,
ao.AccountOptionID
FROM CRM.acc.Account ac
INNER JOIN CRM.[profile].[Profile] pr
ON pr.ProfileID = ac.ProfileFK
INNER JOIN CRM.[profile].Contact co
ON pr.ProfileID = co.ProfileFK
LEFT JOIN CRM.acc.[AccountOption] ao
ON ao.AccountFK = ac.AccountID
LEFT JOIN (
SELECT OptionID
FROM CRM.acc.[Option]
WHERE [Name] = 'SMS messages') op
ON op.OptionID = ao.OptionFK
WHERE ac.AccountID = #AccountID
UNION ALL
SELECT u.UnsubscribeID COLLATE DATABASE_DEFAULT AS UnsubscribeID,
u.EmailAddress COLLATE DATABASE_DEFAULT AS EmailAddress ,
u.SentEmailFK COLLATE DATABASE_DEFAULT AS SentEmailFK
FROM Email.dbo.Unsubscribe u
INNER JOIN (
SELECT CASE
WHEN AccountTypeFK = 2 THEN OnlineBillingEmail
ELSE EmailBillingEmail
END [EmailAddress]
FROM CRM.acc.Account
WHERE AccountID = #AccountID
) ace
ON ace.EmailAddress COLLATE DATABASE_DEFAULT = u.EmailAddress COLLATE
DATABASE_DEFAULT
WHERE ISNULL(ace.EmailAddress, '') != ''

How to get list of child tables for a database table?

I have to write a delete script to delete rows form a database table. However the table has a lot of children tables (foreign keys) and those children tables have children tables too.
There are foreign keys for all relationships and I'd like to use this info to get the list of tables where I'll have to deletes, in the correct order (leaf tables first and then up the dependency graph).
How can I get the list of child tables for a given table in the correct order?
try this on your database, this script will only give you the graph for one table at a time. I assume you have an Employee table but you would have to change line 2 to check a specific table of your database:
DECLARE #masterTableName varchar(1000)
SET #masterTableName = 'Employee'
DECLARE #ScannedTables TABLE( Level int, Name varchar(1000) collate Latin1_General_CI_AS )
DECLARE #currentTableCount INT
DECLARE #previousTableCount INT
DECLARE #level INT
SET #currentTableCount = 0
SET #previousTableCount = -1
SET #level = 0
INSERT INTO #ScannedTables VALUES ( #level, #masterTableName )
WHILE #previousTableCount <> #currentTableCount
BEGIN
SET #previousTableCount = #currentTableCount
INSERT INTO #ScannedTables
SELECT DISTINCT
#level + 1, TC.Table_Name COLLATE Latin1_General_CI_AS
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC ON TC.Constraint_Name = RC.Constraint_Name
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FTC ON RC.Unique_Constraint_Name = FTC.Constraint_Name
WHERE TC.CONSTRAINT_TYPE = 'FOREIGN KEY'
AND FTC.TABLE_NAME COLLATE Latin1_General_CI_AS IN ( SELECT Name FROM #ScannedTables WHERE Level = #level )
AND TC.Table_Name COLLATE Latin1_General_CI_AS NOT IN ( SELECT Name FROM #ScannedTables )
SET #level = #level + 1
SELECT #currentTableCount = COUNT(*) FROM #ScannedTables
END
SELECT * FROM #ScannedTables
There is no simple generic answer for this, since tables can recursively depend on other tables including self relationships, etc. Your result could be more than simple tree.
Your best way should depend on your db model: if you have tree tables connected, then delete your data from third table first, than second, than third.
...or disable constraints, delete data, enable constraints.
...or change foreign keys to DELETE CASCADE.
It depends on your data model.
This article gives a good idea of how to do what you're asking.
EDIT: I've modified the original query given in the link to:
Make the script schema aware
Correct the bug noted in the
comments below
Not sure why the editor is doing such a poor job of formatting the code block.
with Fkeys as (
select distinct
OnTable = onTableSchema.name + '.' + OnTable.name
,AgainstTable = againstTableSchema.name + '.' + AgainstTable.name
from
sysforeignkeys fk
inner join sys.objects onTable
on fk.fkeyid = onTable.object_id
inner join sys.objects againstTable
on fk.rkeyid = againstTable.object_id
inner join sys.schemas onTableSchema
on onTable.schema_id = onTableSchema.schema_id
inner join sys.schemas againstTableSchema
on againstTable.schema_id = againstTableSchema.schema_id
where 1=1
AND AgainstTable.TYPE = 'U'
AND OnTable.TYPE = 'U'
-- ignore self joins; they cause an infinite recursion
and onTableSchema.name + '.' + OnTable.name <> againstTableSchema.name + '.' + AgainstTable.name
)
,MyData as (
select
OnTable = s.name + '.' + o.name
,AgainstTable = FKeys.againstTable
from
sys.objects o
inner join sys.schemas s
on o.schema_id = s.schema_id
left join FKeys
on s.name + '.' + o.name = FKeys.onTable
left join Fkeys fk2
on s.name + '.' + o.name = fk2.AgainstTable
and fk2.OnTable = Fkeys.AgainstTable
where 1=1
and o.type = 'U'
and o.name not like 'sys%'
and fk2.OnTable is null
)
,MyRecursion as (
-- base case
select
TableName = OnTable
,Lvl = 1
from
MyData
where 1=1
and AgainstTable is null
-- recursive case
union all select
TableName = OnTable
,Lvl = r.Lvl + 1
from
MyData d
inner join MyRecursion r
on d.AgainstTable = r.TableName
)
select
Lvl = max(Lvl)
,TableName
,strSql = 'delete from [' + tablename + ']'
from
MyRecursion
group by
TableName
order by
1 desc
,2 desc

Check for constraint on table

If I check for a column's existance as follows, before adding it, how would I do the equivalent for a UNIQUE constraint?
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[Table]') AND [Name]='Column')
ALTER TABLE [Table]
ADD
[Column] varchar(500)
select * from sys.objects where type = 'uq' and parent_object_id = OBJECT_ID('[Table]')
Something like this for a constraint FK_myTable (SQL 2000):
if not exists ( select *
from sysconstraints sc
inner join sysobjects tbl on sc.id = tbl.id
inner join sysobjects con on sc.constid = con.id
where tbl.name = 'myTable' and con.name = 'FK_myTable' )

Resources