Collation conflict using except SQL Server - database

I am trying to execute the following query on two different databases with difent collations
select * from sourcedb.DBO.PKtable
except
select * from destinationdb.DBO.PKtable
It is clear that both the tables have the same columns and the primary key
But when executing, encountered the following exception
Msg 468, Level 16, State 9, Line 17
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_CS_AS" in the EXCEPT operation.
I cannot use COLLATE keyword because.. I will be using the above query at runtime and the table name and columns vary, hence cannot predict the columns of the table.
I have tried to change the collation of the destination database to same as the source database using the following command
ALTER DATABASE destinationDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE destinationDB COLLATE <<sourceCollation>>;
ALTER DATABASE destinationDB SET MULTI_USER
The collation is set to the database and I can see it from the sys tables.
Even then when I execute the query mentioned above, getting the same error
EDIT1: Basically I'm trying to get the records which have the same primary key but difference in the row.

Another possible workaround (apart from dynamic sql) might be to use a temporary table. (This may suit if this is a one off type of operation).
-- create empty #PKTable with required columns and collation.
select * INTO #PKTable from sourcedb.DBO.PKtable where 1=0
-- fill table with data
insert #PKTable select * from destinationdb.DBO.PKtable
-- compare
select * from sourcedb.DBO.PKtable
except
select * from #PKTable
-- remove temp table
drop table #PKTable

Related

There is already an object named 'name' in the database - stored procedure error - SQL Server

I have two tables called timeus and usdpay in my SQL Server database. These two tables get updated every week.
I did small transformation and combined these two tables and created a new table called fluslabor.
I created fluslabor using the stored procedure shown here, and it is working:
CREATE PROCEDURE [dbo].[sp_uslabor]
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID(N'dbo.fluslabor', N'U') IS NOT NULL
TRUNCATE TABLE [dbo].[fluslabor];
SELECT ut.employee_code
,ut.employee_name
,ut.department_desc
,ut.location_desc
,ut.hour
,ut.projects_code
,ut.in_effective_time
,ut.out_effective_time
,ut.date
,ut.id
,p.rate
,(p.rate * ut.hour ) as Labour_Cost
INTO fluslabor
FROM timeus ut
LEFT JOIN usdpay p ON (TRIM(ut.id) = TRIM(p.id) AND ut.date = p.date)
WHERE ut.projects_code NOT LIKE '0%'
END
Today I got new data updated in my two tables timeus and usdpay.
When I execute my stored procedure, SQL Server is throwing this error:
Msg 2714, Level 16, State 6, Procedure SP_uslabor, Line 12 [Batch Start Line 38]
There is already an object named 'fluslabor' in the database.
I need to truncate my table every time and load the new data. I checked the similar post, they said to use drop table option. I don't want to drop the table, just want to truncate and execute the procedure
Can anyone advise what is the issue here please?
The problem here is that the table fluslabor already exists in the database. what you are trying above the insert is checking the object existence and then truncating the same
There are two possible approaches that you can try here.
Instead if the TRUNCATE do a DROP TABLE. But This will also remove the existing user permissions on the table if you have provided specific custom access to the table to any of the users
IF OBJECT_ID(N'dbo.fluslabor', N'U') IS NOT NULL DROP TABLE [dbo].[fluslabor];
The safest approach will be change the SELECT .. INTO statement and convert it into INSERT INTO like this
INSERT INTO fluslabor ( <List your Destination columns> ) SELECT <List your Source columns> FROM <Source Query>
the 2nd approach will have the records loaded along with keeping all the existing permissions
IF EXISTS (SELECT 1 FROM sys.objects WHERE type = 'P' AND name = 'your SP name')
BEGIN
DROP PROCEDURE "your SP name";
END
GO
CREATE PROCEDURE "your SP name"
AS
BEGIN
.
.
.
.
try this one I guess this will help you.

Why DROP TABLE doesn't seem to take effect before a SELECT INTO?

The following tSQL query is puzzling me:
select 1 as FIELD into #TEMP
drop table #TEMP
select 1 as FIELD into #TEMP
When I run it from SQL Server Management Studio session window (pressing F5 to the whole query, as a group), I get the following error:
Msg 2714, Level 16, State 1, Line 3
There is already an object named '#TEMP' in the database.
Note that table #TEMP doesn't exist before the query is executed.
I thought that the code shouldn't produce any errors as line 2 is dropping the temporary table. But it is as if the drop isn't taking effect when line 3 is executed.
My questions:
Why does the error happen?
How do I fix the query so it executes as intended?
PS. The query above is a simplification of a real world query of mine that is showing the same symptoms.
PS2. Regardless of whether this is a sound programming practice or not (as Sean hinted in his comments), this unexpected behavior prompted me to look for information on how these queries are parsed in the hopes that the knowledge will be helpful to me in the future.
As I found the seek of existing tables are different:
select 1 as FIELD into #TEMP
drop table #TEMP
When you use into statement after those commands:
select 1 as FIELD into #TEMP
Error is:
There is already an object named '#TEMP' in the database.
And When you use a select on #TEMP after those commands:
select * from #TEMP
Error is:
Invalid object name '#TEMP'.
So, In first case THERE IS an object with #TEMP name and in the other case THERE IS NOT an object with #TEMP name !.
An important note from technet.microsoft is:
DROP TABLE and CREATE TABLE should not be executed on the same table in the same batch. Otherwise an unexpected error may occur.
In notes of dropping tables by SQL Server Database Engine:
The SQL Server Database Engine defers the actual page deallocations, and their associated locks, until after a transaction commits.
So the second error on using select statement may related to the actual page deallocations and the first error on using into statement may related to duration between lock associated until the transaction commits.
Here try this:
select 1 as FIELD into #TEMP
drop table #TEMP
GO
select 1 as FIELD into #TEMP

Changing the collation of a SQL Server 2012 database

Alter Collation
I need to change the collation of one of our databases on a particular server from Latin1_General_CI_AS to SQL_Latin1_General_CP1_CI_AI so that it matches the rest of our databases.
The Problem
However, when I attempt to do this, I get the following error:
ALTER DATABASE failed. The default collation of database 'XxxxxXxxxxx' cannot be set to SQL_Latin1_General_CP1_CI_AI. (Microsoft SQL Server, Error: 5075)
My Research
My googling on the topic has revealed a number of articles which indicate that I need to export all the data, drop the database, re-create it with the correct collation, then re-import the data.
For example: Problem with database collation change (SQL Server 2008)
Obviously this is a significant task, especially since primary-foreign key relationships must be preserved, and our database is quite large (over ten million data rows).
My Question
Is there a way to change the collation of an existing SQL Server 2012 database which does not require exporting and re-importing all the data?
Alternatively, is there some tool or script(s) which can automate this process in a reliable manner?
The following works for me on SQL Server 2012:
ALTER DATABASE CURRENT COLLATE SQL_Latin1_General_CP1_CI_AI;
The accepted answer in the linked question is not entirely correct, at least not for SQL Server 2012. It says:
Ahh, this is one of the worst problems in SQL Server: you cannot change the collation once an object is created (this is true both for tables and databases...).
But I was just able to change the default collation and I have tables that are populated. The MSDN page for ALTER DATABASE states in the "Remarks" section, under "Changing the Database Collation":
Before you apply a different collation to a database, make sure that the following conditions are in place:
You are the only one currently using the database.
No schema-bound object depends on the collation of the database.
If the following objects, which depend on the database collation, exist in the database, the ALTER DATABASE database_name COLLATE statement will fail. SQL Server will return an error message for each object blocking the ALTER action:
User-defined functions and views created with SCHEMABINDING.
Computed columns.
CHECK constraints.
Table-valued functions that return tables with character columns with collations inherited from the default database collation.
So, I would suggest making sure that the database is in Single-User mode, and that if you have any of those four items, that you:
drop them
change the collation
and then re-add them
BUT, at that point all that has been changed is the Database's default Collation. The Collation of any existing columns in user tables (i.e. non-system tables) will still have the original Collation. If you want existing string columns -- CHAR, VARCHAR, NCHAR, NVARCHAR, and the deprecated TEXT and NTEXT -- to take on the new Collation, you need to change each of those columns individually. And, if there are any indexes defined on those columns, then those indexes will need to be dropped first (disabling is not enough) and created again after the ALTER COLUMN (other dependencies that would prevent the ALTER COLUMN would have already been dropped in order to get the ALTER DATABASE to work). The example below illustrates this behavior:
Test Setup
USE [tempdb];
SET NOCOUNT ON;
CREATE TABLE dbo.ChangeCollationParent
(
[ChangeCollationParentID] INT NOT NULL IDENTITY(1, 1)
CONSTRAINT [PK_ChangeCollationParent] PRIMARY KEY,
ExtendedASCIIString VARCHAR(50) COLLATE Latin1_General_CI_AS NULL,
UnicodeString NVARCHAR(50) COLLATE Latin1_General_CI_AS NULL
);
CREATE TABLE dbo.ChangeCollationChild
(
[ChangeCollationChildID] INT NOT NULL IDENTITY(1, 1)
CONSTRAINT [PK_ChangeCollationChild] PRIMARY KEY,
[ChangeCollationParentID] INT NULL
CONSTRAINT [FK_ChangeCollationChild_ChangeCollationParent] FOREIGN KEY
REFERENCES dbo.ChangeCollationParent([ChangeCollationParentID]),
ExtendedASCIIString VARCHAR(50) COLLATE Latin1_General_CI_AS NULL,
UnicodeString NVARCHAR(50) COLLATE Latin1_General_CI_AS NULL
);
INSERT INTO dbo.ChangeCollationParent ([ExtendedASCIIString], [UnicodeString])
VALUES ('test1' + CHAR(200), N'test1' + NCHAR(200));
INSERT INTO dbo.ChangeCollationParent ([ExtendedASCIIString], [UnicodeString])
VALUES ('test2' + CHAR(170), N'test2' + NCHAR(170));
INSERT INTO dbo.ChangeCollationChild
([ChangeCollationParentID], [ExtendedASCIIString], [UnicodeString])
VALUES (1, 'testA ' + CHAR(200), N'testA ' + NCHAR(200));
INSERT INTO dbo.ChangeCollationChild
([ChangeCollationParentID], [ExtendedASCIIString], [UnicodeString])
VALUES (1, 'testB ' + CHAR(170), N'testB ' + NCHAR(170));
SELECT * FROM dbo.ChangeCollationParent;
SELECT * FROM dbo.ChangeCollationChild;
Test 1: Change column Collation with no dependencies
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [UnicodeString] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationChild
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationChild
ALTER COLUMN [UnicodeString] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
SELECT * FROM dbo.ChangeCollationParent;
SELECT * FROM dbo.ChangeCollationChild;
The ALTER COLUMN statements above complete successfully.
Test 2: Change column Collation with dependencies
-- First, create an index:
CREATE NONCLUSTERED INDEX [IX_ChangeCollationParent_ExtendedASCIIString]
ON dbo.ChangeCollationParent ([ExtendedASCIIString] ASC);
-- Next, change the Collation back to the original setting:
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE Latin1_General_CI_AS NULL;
This time, the ALTER COLUMN statement received the following error:
Msg 5074, Level 16, State 1, Line 60
The index 'IX_ChangeCollationParent_ExtendedASCIIString' is dependent on column 'ExtendedASCIIString'.
Msg 4922, Level 16, State 9, Line 60
ALTER TABLE ALTER COLUMN ExtendedASCIIString failed because one or more objects access this column.
ALSO, please be aware that the Collation of some string columns in database-scoped system catalog views (e.g. sys.objects, sys.columns, sys.indexes, etc) will change to the new Collation. If your code has JOINs to any of these string columns (i.e. name), then you might start getting Collation mismatch errors until you change the Collation on the joining columns in your user tables.
UPDATE:
If changing the Collation for the entire Instance is the desire, or an option, then there is an easier method that bypasses all of these restrictions. It is undocumented and hence unsupported (so if it doesn't work, Microsoft isn't going to help). However, it changes the Collation at all levels: Instance, all Database's, and all string columns in all User Tables. It does this, and avoids all of the typical restrictions, by simply updating the meta-data of the tables, etc to have the new Collation. It then drops and recreates all indexes that have string columns. There are also a few nuances to this method that might have impact, but are fixable. This method is the -q command-line switch of sqlservr.exe. I have documented all of the behaviors, including listing all of the potentially affected areas by doing such a wide-sweeping Collation change, in the following post:
Changing the Collation of the Instance, the Databases, and All Columns in All User Databases: What Could Possibly Go Wrong?
For anyone else stumbling to this problem, the solution is to set DB in single_user mode before change the collation and then set again the multi_user mode after it.
Make sure to not close the connection before setting the multi_user mode!
/* block all other users from connecting to the db */
ALTER DATABASE YorDbName SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
/* modify your db collate */
ALTER DATABASE CURRENT COLLATE SQL_Latin1_General_CP1_CI_AI;
/* allow again all other users to connect to the db */
ALTER DATABASE YorDbName SET MULTI_USER;

Temporary table from stored procedure result in SQL Server

Can we create a temporary table from the stored procedure results dynamically?
I do not want to declare the temporary table columns manually. It shud take the schema of table from the stored procedure results.
use one of the Rowset Functions:
SELECT *
INTO #Temp
FROM OPENQUERY(SERVERNAME, 'EXEC pr_StorProcName')
Use following syntax template to create temp table on basis of result set.
Select * into #temptable from mytable
Select column1,column2,..columnn into #temptable from mytable
Notes:
The SELECT INTO statement is very fast, for one reason: the command isn't logged for backup purposes. More precisely, the command can be inside a transaction and any rollback command will correctly undo its effects. However, the new values aren't permanently stored in the log file, therefore after this command you can only perform a complete database backup (incremental backup raise errors). This explains why you have to explicitly enable this functionality for non-temporary tables (temporary tables are never included in backup, so you don't need to use the sp_dboption command before using SELECT INTO with a temporary table).

SQL Server parallels to Oracle Alter Table Set Column Unused?

Is there a parallel in Microsoft SQL Server (2005, preferably) for the Oracle functionality of setting a column to be unused? For example:
ALTER TABLE Person SET UNUSED Nickname;
Nothing turns up in searches, so my thought is this feature must be Oracle specific.
Don't think there's anything like that in SQL server.
You could create a 1:1 relation to a new table containing the hidden columns:
insert into NewTable
select (keycol, Nickname) from ExistingTable
alter table ExistingTable drop column Nickname
That way you still have the data, but the column is in a table nobody knows about.
Alternatively, you could use column level permissions:
DENY SELECT (Nickname) ON ExistingTable TO domain\user
DENY SELECT (Nickname) ON ExistingTable TO public
...
This will return an error when someone tries to read the column. The big disadvantage of this method is that select * will also fail.
There is no equivalent statement, but depending on your need you could probably write a trigger to roll back any changes if made.

Resources