Sql Server : index already existing on global temp table - sql

In Sql Server 2012 SP3 v.11.0.6020.0 (X64), I have a stored procedure which tests for the existence of a global temporary table (##MyTable , e.g.) and creates it - if not found, of course.
IF OBJECT_ID ( 'tempdb..##MyTable' ) IS NULL
CREATE TABLE ##MyTable
(
Key1 smallint
, Key2 nvarchar(16)
, Value1 char(3)
);
Later in the procedure, it tests if the table has rows and - if necessary - populates it.
IF NOT EXISTS ( SELECT * FROM ##MyTable )
BEGIN
INSERT INTO ##MyTable
SELECT Key1, Key2, Value1
FROM SourceTable
WHERE ...
CREATE NONCLUSTERED INDEX IX_MyTable ON ##MyTable ( [Key1], [Key2] );
END
I am sure that Key1 and Key2 are unique, since they are primary keys on source table.
Then, in either case (the table already existed or not) the stored procedures queries the table. Needlesss to say, the sp logic is much more complex than this.
The table is populated with customers' data coming from 7 different sources; usually, it takes a couple of seconds to insert almost 1 million of rows. Theoretically there is no chance that the all of the INSERT INTO ##MyTable insert 0 (ZERO) rows.
The stored procedure is called by an application: this application is usually launched in the morning and closed at night.
Theoretically, there could be conflicts, a user tries to insert data and create the index while another one was already doing the same. But it's very unlikely that it happens always to the same user; it should be impossible, if that user tries again a few minutes later (table and index already exists).
This works fine for all of the user (near 100), but a specific one, who keeps getting an error: The operation failed because an index or statistics with name 'IX_MyTable' already exists on table ##MyTable.
Aside from the fact that I'm already thinking about making the global temporary table a regular one, could anyone please explain to me this behaviour?
Thanks in advance to anyone who will help!

You would experience this behavior under these circumstances:
You create the table. It is empty.
The insert query is run, but inserts no rows.
The index is created.
On the next run, you will have an empty table and attempt the insert once again.
This is easy enough to work around. Just use a try/catch block or test to see if the index exists before creating it. Or, better yet, create the index when you create the table. Unless you are inserting a lot of data, the overhead shouldn't be too bad.

Better if you move your Create Index statement in Table creation block itself..
IF OBJECT_ID ( 'tempdb..##MyTable' ) IS NULL
BEGIN
CREATE TABLE ##MyTable
(
Key1 smallint
, Key2 nvarchar(16)
, Value1 char(3)
);
CREATE NONCLUSTERED INDEX IX_MyTable ON ##MyTable ( [Key1], [Key2] );
END

The Error explains itself, you must create the index while creating the table, you cant create an index when it already exists
IF OBJECT_ID ( 'tempdb..##MyTable' ) IS NULL
Begin
CREATE TABLE ##MyTable
(
Key1 smallint
, Key2 nvarchar(16)
, Value1 char(3)
);
CREATE NONCLUSTERED INDEX IX_MyTable ON ##MyTable ( [Key1], [Key2] );
END
in your Stored Procedure you are creating index inside condition when the table is empty, so every time you delete all data from table, it is trying to create index.

Related

use INSERT inside definition of VIEW: CREATE VIEW AS INSERT INTO

If I want to do something relatively complicated - something usually done by a stored procedure. Is it possible to make it automatic using a VIEW?
My specific case:
I want output table = input table A + some rows input table B. In a stored procedure, I can make a copy of table A and then INSERT INTO it, but it's not allowed in a view.
Simplified example:
input table is [test_album], and output table = input table + singer Prince.
--create test data
IF OBJECT_ID('[dbo].[test_album]', 'U') IS NOT NULL
DROP TABLE [dbo].[test_album]
CREATE TABLE [test_album] (
id int not null identity(1, 1) primary key,
singer VARCHAR(50) NULL,
album_title VARCHAR(100) NULL
)
INSERT INTO [test_album] (singer, album_title)
VALUES ('Adale', '19'),
('Michael Jaskson', 'Thriller')
--this can be executed as sql code or in stored proc
SELECT *
INTO [result_table]
FROM [test_album]
INSERT INTO [result_table] ([singer])
VALUES ('Prince')
select *
from [result_table]
--id singer album_title
--1 Adale 19
--2 Michael Jaskson Thriller
--3 Prince NULL
----as expected
But I can do this INSERT INTO inside a view.
Real-life case:
additional singers are in a table [extra_singers]
[test_album] may have many other columns (or schema may change) so it's ideal not to type all column names in the code.
--create test data
IF OBJECT_ID('[dbo].[test_album]', 'U') IS NOT NULL
DROP TABLE [dbo].[test_album]
IF OBJECT_ID('[dbo].[extra_singers]', 'U') IS NOT NULL
DROP TABLE [dbo].[extra_singers]
IF OBJECT_ID('[dbo].[result_table]', 'U') IS NOT NULL
DROP TABLE [dbo].[result_table]
CREATE TABLE [test_album] (
id int not null identity(1, 1) primary key,
singer VARCHAR(50) NULL,
album_title VARCHAR(100) NULL,
many_other_columns VARCHAR(100) NULL
)
INSERT INTO [test_album] (singer, album_title)
VALUES ('Adale', '19'),
('Michael Jaskson', 'Thriller')
CREATE TABLE [extra_singers] (
[id] int not null identity(1, 1) primary key,
[name] VARCHAR(50) NULL )
INSERT INTO [extra_singers] ([name])
VALUES ('Prince'),
('Taylor Swift')
--append [extra_singers] to [test_album]
--this can be executed as sql code or in stored proc
SELECT *
INTO [result_table]
FROM [test_album]
INSERT INTO [result_table] ([singer])
SELECT [name]
FROM [extra_singers]
Is there an alternative to this (that is automatic)?
any help's appreciated. Thank u-
a partial solution I can think of:
create view test_view as
select *
from [test_album]
union all
select 3 as id,
'Prince' as singer,
NULL as album_title
but you have to know all the column names in [test_album] and you can't let column [id] do auto-increment
So you may be misunderstanding what a view does, or what an insert is. A view is simply a wrapper around a single select query. It contains exactly one select statement, and nothing else. An insert permanently adds a row of data to a persisted table. The example you gave where you just union the row you want seems valid enough. And certainly if it's the same row you want every time, you would not want to be inserting (or even trying to insert) that row into the underlying table each time
This raises a couple questions though.
If you're always going to be unioning the same single row every time, why not jut add that row to the table?
If, lets say, you don't want that row in the underlying table, cool. But if it's always the same static values, why do you need to include it in the view? Can't it just be assumed it's there?
If it can't be assume to always be the same, you certainly don't want to be changing the VIEW body every time you need it to change. So if it is going to change and you don't want to insert it into the base table, maybe make a second table containing the values you want appended to the base table in the view. Then union the base table and the "extra values" table together instead of a single, hard coded row constructor.

Restoring a Truncated Table from a Backup

I am restoring the data of a truncated table in an Oracle Database from an exported csv file. However, I find that the primary key auto-increments and does not insert the actual values of the primary key constrained column from the backed up file.
I intend to do the following:
1. drop the primary key
2. import the table data
3. add primary key constraints on the required column
Is this a good approach? If not, what is recommended? Thanks.
EDIT: After more investigation, I observed there's a trigger to generate nextval on a sequence to be inserted into the primary key column. This is the source of the predicament. Hence, following the procedure above would not solve the problem. It lies in the trigger (and/or sequence) on the table. This is solved!
easier to use your .csv as an external table and then go
create table your_table_temp as select * from external table
examine the data in the new temp table to ensure you know what range of primary keys is present
do a merge into the new table
samples from here and here
CREATE TABLE countries_ext (
country_code VARCHAR2(5),
country_name VARCHAR2(50),
country_language VARCHAR2(50)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab_data
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY ','
MISSING FIELD VALUES ARE NULL
(
country_code CHAR(5),
country_name CHAR(50),
country_language CHAR(50)
)
)
LOCATION ('Countries1.txt','Countries2.txt')
)
PARALLEL 5
REJECT LIMIT UNLIMITED;
and the merge
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
Edit: after you have merged the data you can drop the temp table and the result is your previous table with the old data and the new data together
Edit you mention " During imports, the primary key column does not insert from the file, but auto-increments". This can only happen when there is a trigger on the table, likely, Before insert on each row. Disable the trigger and then do your import. Re-enable the trigger after committing your inserts.
I used the following procedure to solve it:
drop trigger trigger_name
Imported the table data into target table
drop sequence sequence_name
CREATE SEQUENCE SEQ_NAME INCREMENT BY 1 START WITH start_index_for_next_val MAXVALUE max_val MINVALUE 1 NOCYCLECACHE 20 NOORDER
CREATE OR REPLACE TRIGGER "schema_name"."trigger_name"
before insert on target_table
for each row
begin
select seq_name.nextval
into :new.unique_column_name
from dual;
end;

Derived table with an index

Please see the TSQL below:
DECLARE #TestTable table (reference int identity,
TestField varchar(10),
primary key (reference))
INSERT INTO #TestTable VALUES ('Ian')
select * from #TestTable as TestTable
INNER JOIN LiveTable on LiveTable.Reference=TestTable.Reference
Is it possible to create an index on #Test.TestField? The following webpage suggests it is not. However, I read on another webpage that it is possible.
I know I could create a physical table instead (for #TestTable). However, I want to see if I can do this with a derived table first.
You can create an index on a table variable as described in the top voted answer on this question:
SQL Server : Creating an index on a table variable
Sample syntax from that post:
DECLARE #TEMPTABLE TABLE (
[ID] [INT] NOT NULL PRIMARY KEY,
[Name] [NVARCHAR] (255) COLLATE DATABASE_DEFAULT NULL,
UNIQUE NONCLUSTERED ([Name], [ID])
)
Alternately, you may want to consider using a temp table, which will persist during the scope of the current operation, i.e. during execution of a stored procedure exactly like table variables. Temp tables will be structured and optimized just like regular tables, but they will be stored in tempDb, therefore they can be indexed in the same way as regular table.
Temp tables will generally offer better performance than table variables, but it's worth testing with your dataset.
More in depth details can be found here:
When should I use a table variable vs temporary table in sql server?
You can see a sample of creating a temp table with an index from:
SQL Server Planet - Create Index on Temp Table
One of the most valuable assets of a temp table (#temp) is the ability
to add either a clustered or non clustered index. Additionally, #temp
tables allow for the auto-generated statistics to be created against
them. This can help the optimizer when determining cardinality. Below
is an example of creating both a clustered and non-clustered index on
a temp table.
Sample code from site:
CREATE TABLE #Users
(
ID INT IDENTITY(1,1),
UserID INT,
UserName VARCHAR(50)
)
INSERT INTO #Users
(
UserID,
UserName
)
SELECT
UserID = u.UserID
,UserName = u.UserName
FROM dbo.Users u
CREATE CLUSTERED INDEX IDX_C_Users_UserID ON #Users(UserID)
CREATE INDEX IDX_Users_UserName ON #Users(UserName)

Alter an existing Identity Column's Increment value

I am stumped,
I am trying to alter the increment value of Identity columns in a collection of existing MS SQL tables (which all have data) and have been trying to research if it is possible to do without writing custom scripts per table.
I can't find a solution that doesn't require dropping and recreating the tables which would require a different script for each table as they each have different column lists.
for example i want to change the existing table
CREATE TABLE [dbo].[ActionType](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Action] [varchar](100) NOT NULL,
CONSTRAINT [PK_ActionType] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
To
CREATE TABLE [dbo].[ActionType](
[ID] [int] IDENTITY(1,5) NOT NULL,
[Action] [varchar](100) NOT NULL,
CONSTRAINT [PK_ActionType] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
Via something like
exec sp_AlterIncrement #TABLE_NAME = 'ActionType', #NEW_ICREMENT = 5
While keeping the data.
This would fix a big deployment issue i am facing right now so any help would be appreciated
You can not alter identity increment after you create it.It is possible just to change seed value with DBCC Chekident .
You should drop and recreate the column.
I had to do that before on a small table and it's fairly easy to do, trick is that you have to update it to something that currently doesn't exist as a key, and then back, since you can't increment it by 1 because that key already exists. It takes 2 updates, for a table with IDs smaller than 100 for example:
update my_table set id = id+100;
update my_table set id = id-99;
But anyways , I do not understand why you want to alter the identity value, Because anyhow you will keep the same as primary key or part of the clustered key.
Also, if any change in the column type is being required then i don't think that there is a possibility without altering the table structure.
Alter table ActionType
Alter column ID
You can also revert to the original structure when not required. This can be used for the specified case as well, As if you require this on demand basis.
Please suggest so that i can provide the further feedback.
Couple of things, maybe too much info but helpful when do stuff like this. The following will set the increment to whatever you want:
DBCC CHECKIDENT ([DB.Schema.Table], reseed, 0) --First record will have a 1. You can set it to any value
If you want to insert data into a table that has an identity but you need to force the value to something specific, do this:
SET IDENTITY_INSERT [DB].[schema].[Table] ON
...Add your data here
SET IDENTITY_INSERT [DB].[schema].[Table] OFF
Sometimes this is necessary.this might provide an answer. For example existing table is identity(1,1) [ex below would be A]
It contains value but you would like to change it to increment of to let's say so that it works well with another table [ex below would be B]
So a would have odd ids + whatever it use to contains.while be would now have even number
this script show you how to do it.
create table A(id int identity(1,1),v char)
insert into A
Select 'A'
union select 'B'
union select 'C'
go
create table B(id int identity(1,2),v char)
go
SET IDENTITY_INSERT B ON
GO
insert into B(Id,v)
Select Id,v from A
go
SET IDENTITY_INSERT B OFF
GO
insert into B
Select 'D'
union select 'E'
go
drop table A
go
EXEC sp_RENAME 'B' , 'A'
go
Select * from A
go
Select max(Id)+1 from A
go
create table B(id int identity(8,2),v char)
go
insert into B
Select 'A'
union select 'B'
union select 'C'
go
Select * from B
If you need to reenumerate or compress your Identity field, the easiest way is as follows:
Convert, temporarily, your identity filed into an integer
Replace the values using for example an Excel sheet in other to fill them up
Copy and Paste the column in your Excel file into the Int field.
Save the table
Open it again in design mode and change back the Int field into an Identity
If this Identity field is used in a child table, make sure you have a trigger to also export the new values into the dependant tables .
And that's all.
If you need to control Identity data in your applicaton, just change it to Int and manage the incremental values with code with the Dmax function.
Hope it helps

Constraint for only one record marked as default

How could I set a constraint on a table so that only one of the records has its isDefault bit field set to 1?
The constraint is not table scope, but one default per set of rows, specified by a FormID.
Use a unique filtered index
On SQL Server 2008 or higher you can simply use a unique filtered index
CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
ON TableName(FormID)
WHERE isDefault = 1
Where the table is
CREATE TABLE TableName(
FormID INT NOT NULL,
isDefault BIT NOT NULL
)
For example if you try to insert many rows with the same FormID and isDefault set to 1 you will have this error:
Cannot insert duplicate key row in object 'dbo.TableName' with unique
index 'IX_TableName_FormID_isDefault'. The duplicate key value is (1).
Source: http://technet.microsoft.com/en-us/library/cc280372.aspx
Here's a modification of Damien_The_Unbeliever's solution that allows one default per FormID.
CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO
But the serious relational folks will tell you this information should just be in another table.
CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
From a normalization perspective, this would be an inefficient way of storing a single fact.
I would opt to hold this information at a higher level, by storing (in a different table) a foreign key to the identifier of the row which is considered to be the default.
CREATE TABLE [dbo].[Foo](
[Id] [int] NOT NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DefaultSettings](
[DefaultFoo] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DefaultSettings] WITH CHECK ADD CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO
ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
You could use an insert/update trigger.
Within the trigger after an insert or update, if the count of rows with isDefault = 1 is more than 1, then rollback the transaction.
CREATE VIEW vOnlyOneDefault
AS
SELECT 1 as Lock
FROM <underlying table>
WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO
You'll need to have the right ANSI settings turned on for this.
I don't know about SQLServer.But if it supports Function-Based Indexes like in Oracle, I hope this can be translated, if not, sorry.
You can do an index like this on suposed that default value is 1234, the column is DEFAULT_COLUMN and ID_COLUMN is the primary key:
CREATE
UNIQUE
INDEX only_one_default
ON my_table
( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )
This DDL creates an unique index indexing -1 if the value of DEFAULT_COLUMN is 1234 and ID_COLUMN in any other case. Then, if two columns have DEFAULT_COLUMN value, it raises an exception.
The question implies to me that you have a primary table that has some child records and one of those child records will be the default record. Using address and a separate default table here is an example of how to make that happen using third normal form. Of course I don't know if it's valuable to answer something that is so old but it struck my fancy.
--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;
CREATE TABLE [dev].[people](
[Id] [int] identity primary key,
name char(20)
)
GO
CREATE TABLE [dev].[Addresses](
id int identity primary key,
peopleId int foreign key references dev.people(id),
address varchar(100)
) ON [PRIMARY]
GO
CREATE TABLE [dev].[defaultAddress](
id int identity primary key,
peopleId int foreign key references dev.people(id),
addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
ADD CONSTRAINT FK_Def_People_Address
FOREIGN KEY(peopleID, addressesID)
REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name)
select 'Bill' union
select 'John' union
select 'Harry'
insert into dev.Addresses (peopleid, address)
select 1, '123 someplace' union
select 1,'work place' union
select 2,'home address' union
select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
select 1,1 union
select 2,3
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people
join dev.addresses on people.id = addresses.peopleid
left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id
insert into dev.defaultaddress (peopleId, addressesId)
select 1,2
GO
You could do it through an instead of trigger, or if you want it as a constraint create a constraint that references a function that checks for a row that has the default set to 1
EDIT oops, needs to be <=
Create table mytable(id1 int, defaultX bit not null default(0))
go
create Function dbo.fx_DefaultExists()
returns int as
Begin
Declare #Ret int
Set #ret = 0
Select #ret = count(1) from mytable
Where defaultX = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_DEFAULT_SET] CHECK
(([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)
Insert into mytable (id1, defaultX) values (2,1)
This is a fairly complex process that cannot be handled through a simple constraint.
We do this through a trigger. However before you write the trigger you need to be able to answer several things:
do we want to fail the insert if a default exists, change it to 0 instead of 1 or change the existing default to 0 and leave this one as 1?
what do we want to do if the default record is deleted and other non default records are still there? Do we make one the default, if so how do we determine which one?
You will also need to be very, very careful to make the trigger handle multiple row processing. For instance a client might decide that all of the records of a particular type should be the default. You wouldn't change a million records one at a time, so this trigger needs to be able to handle that. It also needs to handle that without looping or the use of a cursor (you really don't want the type of transaction discussed above to take hours locking up the table the whole time).
You also need a very extensive tesing scenario for this trigger before it goes live. You need to test:
adding a record with no default and it is the first record for that customer
adding a record with a default and it is the first record for that customer
adding a record with no default and it is the not the first record for that customer
adding a record with a default and it is the not the first record for that customer
Updating a record to have the default when no other record has it (assuming you don't require one record to always be set as the deafault)
Updating a record to remove the default
Deleting the record with the deafult
Deleting a record without the default
Performing a mass insert with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record inserts
Performing a mass update with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record updates
Performing a mass delete with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record deletes
#Andy Jones gave an answer above closest to mine, but bearing in mind the Rule of Three, I placed the logic directly in the stored proc that updates this table. This was my simple solution. If I need to update the table from elsewhere, I will move the logic to a trigger. The one default rule applies to each set of records specified by a FormID and a ConfigID:
ALTER proc [dbo].[cpForm_UpdateLinkedReport]
#reportLinkId int,
#defaultYN bit,
#linkName nvarchar(150)
as
if #defaultYN = 1
begin
declare #formId int, #configId int
select #formId = FormID, #configId = ConfigID from csReportLink where ReportLinkID = #reportLinkId
update csReportLink set DefaultYN = 0 where isnull(ConfigID, #configId) = #configId and FormID = #formId
end
update
csReportLink
set
DefaultYN = #defaultYN,
LinkName = #linkName
where
ReportLinkID = #reportLinkId