How to update a table if a column exists in SQL Server? - sql-server

I have a table MyTable created by
CREATE TABLE MyTable
(
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Type] [int] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
)
I want to check if a column exists in my table, and if it does, I want to copy the data to a different column, then drop the old column, like this:
IF (SELECT COLUMNPROPERTY(OBJECT_ID('MyTable'), 'Timestamp', 'Precision')) IS NOT NULL
BEGIN
UPDATE [dbo].[MyTable]
SET [CreatedDate] = [Timestamp]
ALTER TABLE [dbo].[MyTable]
DROP COLUMN [Timestamp]
END
GO
However, when I try to run this I get an error:
Invalid column name 'Timestamp'
How can I accomplish what I'm trying to do?

This is a compilation issue.
If the table doesn't exist when you compile the batch all works fine as the statements referencing the table are subject to deferred compile. However for a preexisting table you will hit this problem as it tries to compile all statements and balks at the non existent column.
You can push the code into a child batch so it is only compiled if that branch is hit.
IF (SELECT COLUMNPROPERTY(OBJECT_ID('MyTable'), 'Timestamp', 'Precision')) IS NOT NULL
BEGIN
EXEC('
UPDATE [dbo].[MyTable]
SET [CreatedDate] = [Timestamp]
ALTER TABLE [dbo].[MyTable]
DROP COLUMN [Timestamp]
')
END
GO
If you are just trying to rename the column
EXEC sys.sp_rename 'dbo.MyTable.[TimeStamp]' , 'CreatedDate', 'COLUMN'
Would be easier though (from a position where the CreatedDate column doesn't exist).

You have to first create the [Timestamp] column with an ALTER TABLE statement.
Then the rest should run.
EDIT based on comment (I know this info is duplicated elsewhere on SO, but I couldn't find it):
Ok, the IF condition in SQL Server unfortunately does not allow you to ignore code that does not parse. What is happening is that SQL Server is looking at your command, and parsing every statement to make sure it is valid.
When it does this, SQL Server isn't smart enough to figure out that the invalid statement (the UPDATE that requires the presence of [TimeStamp]) will not be reached if there is no [TimeStamp].
In other words, you can't write a SQL command that expects a column that doesn't exist EVEN IF you nest that command in an IF condition that won't be reached. SQL Server will parse the entire statement and not allow it to run BEFORE it tests the IF condition.
A commonly used Work arounds for this is Dynamic SQL, which SQL Server can't pre-parse, so it won't try.

Related

Recursive OR statement SQL Server 2016

I am looking at creating a relatively simple insert statement that inserts a new record if there are any changes to a table. Issue i have is there are over 600 columns that would need to be checked.
More details: the main reporting table is updated every 15 minutes from the front end application using a SQL process to push the changes, however it over-writes the data and doesn't maintain a change log. I have no control over any of this.
Second table (my table) is a DWH table, which will create an audit of changes. So I use an inner join where t1.AccountNo = t2.AccountNo and t1.Field1 <> t.2Field1 then add an OR and add the next field t1.AccountNo = t2.AccountNo and t1.Field2 <> t.2Field2 .
Is there a better way to get the desired result given the number of columns?
You could try a different approach.
Create a trigger on the main table for update and delete.
This trigger copies the data which is already in the table to your dwh table before the data has changed.
create Trigger [nameupdate] on [yourtable] after update
as
insert into [dwh]
select
getdate() as [ChangeDate]
,'update' as [Action]
,SYSTEM_USER as [User]
,d.[ID]
,d.[...]
from deleted d
GO
same for delete
create Trigger [namedelete] on [yourtable] after delete
[...]
my dwh table has 3 additional columns for tracking and contains all columns from main table.
CREATE TABLE [dwh](
[ID] [int] IDENTITY(1,1) NOT NULL Primary key,
[ChangeDate] [datetime] NOT NULL,
[Action] [varchar](50) NOT NULL,
[User] [nvarchar](128) NOT NULL,
[...]

Trying to access previous record in SQL Server

I'm trying to do a balance column in a accounting app with c# winforms, i'm doing it just for using by myself, being the formula of that column like this one:
balance[i] = debit[i] - credit[i] + balance[i-1]
So, I thought a calculated column would be the best solution. I'm using Visual Studio 2013 Community and SQL Server, I tried to do it in the "table view" in the CREATE TABLE script:
CREATE TABLE [dbo].[CONTAT1]
(
[NASIENTO] INT IDENTITY (1, 1) NOT NULL,
[FECHA] DATE NOT NULL,
[CONCEPTO] NVARCHAR (MAX) NOT NULL,
[DEBIT] INT DEFAULT ((0)) NOT NULL,
[CREDIT] INT DEFAULT ((0)) NOT NULL,
[BALANCE] AS ([DEBIT]-[CREDIT] + lag([BALANCE], 1, 0)),
[FACTURA] INT NULL,
[RECIBO] INT NULL,
PRIMARY KEY CLUSTERED ([NASIENTO] ASC)
);
I specified the default lag's parameter so in the first record the function lag just add 0 (oh, I've translated the so-called rows names so anyone can follow the question). When i update the table it don't work and gives the following message:
Dropping unnamed constraint on [dbo].[CONTAT1]...
Dropping unnamed constraint on [dbo].[CONTAT1]...
Starting rebuilding table [dbo].[CONTAT1]...
(116,1): SQL72014: .Net SqlClient Data Provider: Msg 10753, Level 15, State 1, Line 13 The function 'lag' must have an OVER clause.
(104,0): SQL72045: Script execution error. The executed script:
BEGIN TRANSACTION;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET XACT_ABORT ON;
CREATE TABLE [dbo].[tmp_ms_xx_CONTAT1] (
[NASIENTO] INT IDENTITY (1, 1) NOT NULL,
[FECHA] DATE NOT NULL,
[CONCEPTO] NVARCHAR (MAX) NOT NULL,
[DEBIT] INT DEFAULT ((0)) NOT NULL,
[CREDIT] INT DEFAULT ((0)) NOT NULL,
[BALANCE] AS ([DEBIT] - [CREDIT] + lag([BALANCE], 1, 0)),
[FACTURA] INT NULL,
[RECIBO] INT NULL,
PRIMARY KEY CLUSTERED ([NASIENTO] ASC)
);
IF EXISTS (SELECT TOP 1 1
FROM [dbo].[CONTAT1])
BEGIN
SET IDENTITY_INSERT [dbo].[tmp_ms_xx_CONTAT1] ON;
INSERT INTO [dbo].[tmp_ms_xx_CONTAT1] ([NASIENTO], [FECHA], [CONCEPTO], [DEBIT], [CREDIT], [FACTURA], [RECIBO])
SELECT [NASIENTO],
[FECHA],
[CONCEPTO],
[DEBIT],
[CREDIT],
[FACTURA],
[RECIB
An error occurred while the batch was being executed.
The last time I used SQL was like in the early 2000's, so I'm not sure why don't work... honestly, the batch thing is killing me.
I have searched the lag function and seems correct to me, and since the table have a primary key AND if I just try it without the lag function (using just [BALANCE] AS ([DEBIT]-[CREDIT]) ) it works perfectly, I suppose I have no need to specify the order or something else, so I suppose again the problem is the lag function.
The questions are:
The lag function can be used with CREATE TABLE, right? And if not, should i just make a query in the form OnLoad event to create that column?
Are there any other way to access the previous record? Yes, i could do it via DataSet with a very simple foreach, but i don't want to create... how do you call it in english? Gaps? Just different information in the database and the app, and duplicate queries and what not... so i thought it would be better that the database manage it automagically :P , right?
There is no previous record in SQL - tables have no order.
Standard for accounting - and in most jurisdictions even legally quite required - is to record the change AND THE NEW VALUE in the table, together with a running number (per account).

Update DateUsed by trigger

I'm trying to update a table with a trigger from another table. I thought this would be a very simple query but the query I first came up with does not work and I don't understand why.
CREATE TABLE [dbo].[Vehicle](
[id] [int] IDENTITY(1,1) NOT NULL,
[plate] [nvarchar](50) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[dateUsed] [datetime] NULL
)
CREATE TABLE [dbo].[Transaction](
[id] [int] IDENTITY(1,1) NOT NULL,
[vehicleId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateTransaction] [datetime] NOT NULL,
)
When a transaction is added, I wish to update the Vehicle table. If the added dateTransaction is later then dateUsed it should be updated so the dateUsed field always contains the latest date of that specific vehicle.
I would think that this trigger should do the trick.. but it does not:
UPDATE [Vehicle]
SET [dateUsed] =
CASE
WHEN [dateUsed] < [Transaction].[dateTransaction]
OR [dateUsed] IS NULL
THEN [Transaction].[dateTransaction]
ELSE [dateUsed]
END
FROM [Transaction]
WHERE [Vehicle].[id]=[Transaction].[vehicleId]
It looks good to me... It should go over all newly inserted records and update the dateUsed field. If the dateTransaction is newer, use that one.. if not.. use the current. But I seem to missing something because it's not updating to the latest date. It does match one of the transactions of that specific vehicle but not the latest one.
A query that does work:
UPDATE [Vehicle]
SET [dateUsed] = InsertedPartitioned.[dateTransaction]
FROM [Vehicle]
LEFT JOIN (
SELECT
[vehicleId],
[dateTransaction],
ROW_NUMBER() OVER(PARTITION BY [VehicleId] ORDER BY [dateTransaction] DESC) AS RC
FROM [Inserted]) AS InsertedPartitioned
ON InsertedPartitioned.RC=1
AND InsertedPartitioned.[vehicleId]=[Vehicle].[id]
WHERE InsertedPartitioned.[vehicleId] IS NOT NULL
AND ([Vehicle].[dateUsed] IS NULL
OR InsertedPartitioned.[dateTransaction] > [Vehicle].[dateUsed]);
So I have a working solution and it may even be for the better (haven't timed it with a large insert) but it bugs the hell out of my not knowing why the first it not working!
Can anyone 'enlighten me'?
why the first it not working
Because of a wonderful aspect of the Microsoft extension to UPDATE that uses a FROM clause:
Use caution when specifying the FROM clause to provide the criteria for the update operation. The results of an UPDATE statement are undefined if the statement includes a FROM clause that is not specified in such a way that only one value is available for each column occurrence that is updated, that is if the UPDATE statement is not deterministic.
(My emphasis).
That is, if more than one row from inserted matches the same row in Vehicle then it's undefined which row will be used to apply the update - and all computations within the SET clause are computed "as if" they're all evaluated in parallel - so it's not as if a second attempt to update the same row will observe the results on the first attempt - the current value of the DateUsed column that can be observed is always the original value.
In ANSI standard SQL, you'd have to write the UPDATE without using the FROM extension and would thus have to write a correlated subquery, something like:
UPDATE [Vehicle]
SET [dateUsed] = COALESCE((SELECT dateUsed FROM inserted i
WHERE i.VehicleId = Vehicle.Id and
(i.dateUsed > Vehicle.DateUsed or
Vehicle.DateUsed IS NULL),
dateUsed)
WHERE [id] IN (select [vehicleId] FROM inserted)
Which, under the same circumstances, would nicely give you an error about a subquery returning more than one value (for the one inside the COALESCE, not the one in the IN) and thus give you a clue to why it's not working.
But, undeniably, the FROM extension is useful - I just wish it triggered a warning for this kind of situation.

Cannot insert explicit value for identity column

I am migrating my application form one database to other with keeping table structure as it is. I am creating same tables in new table and inserted value using db link.
I am getting error message like "Cannot insert explicit value for identity column in table 'XYZ' when IDENTITY_INSERT is set to OFF." because table XYZ have ScreenConfigSettingAccessId as an identity column
Below is the script I am using for creating table and inserting value
CREATE TABLE [dbo].[XYZ](
[ScreenConfigSettingAccessId] [int] IDENTITY(1,1) NOT NULL,
[APP_ID] [int] NOT NULL,
[ScreenConfigSettingId] [int] NOT NULL,
[RSRC_ID] [char](20) NOT NULL)
)
INSERT INTO [dbo].[XYX]
(
[ScreenConfigSettingAccessId] ,
[APP_ID] ,
[ScreenConfigSettingId] ,
[RSRC_ID]
)
SELECT
[ScreenConfigSettingAccessId] ,
[APP_ID] ,
[ScreenConfigSettingId] ,
[RSRC_ID]
FROM [olddatabase].[database name].[dbo].[XYX]
in old table the value of ScreenConfigSettingAccessId is 3 and 4.
I want to inset the same data which old table have so set IDENTITY_INSERT to ON and tried but it still not allowing to insert.
Looking for you suggestions
You need to specify the table. Check out the command syntax in SQL Books Online: SQL 2000 or SQL 2012 (the syntax hasn't changed).

Problem with Indexed View in SQL Server, Error 8646

I was just prototyping a new system for deferring certain operations until out of hours on one of our databases. I've come up with (what I think) a pretty simple schema. I was first prototyping on SQL Server 2005 Express, but have confirmed the same problem on 2008 Developer. The error I'm getting is:
Msg 8646, Level 21, State 1, Procedure
Cancel, Line 6 Unable to find index
entry in index ID 1, of table
277576027, in database 'xxxxxx'. The
indicated index is corrupt or there is
a problem with the current update
plan. Run DBCC CHECKDB or DBCC
CHECKTABLE. If the problem persists,
contact product support.
The schema I'm using is:
create schema Writeback authorization dbo
create table Deferrals (
ClientID uniqueidentifier not null,
RequestedAt datetime not null,
CompletedAt datetime null,
CancelledAt datetime null,
ResolvedAt as ISNULL(CompletedAt,CancelledAt) persisted,
constraint PK_Writeback_Deferrals PRIMARY KEY (ClientID,RequestedAt) on [PRIMARY],
constraint CK_Writeback_Deferrals_NoTimeTravel CHECK ((RequestedAt <= CompletedAt) AND (RequestedAt <= CancelledAt)),
constraint CK_Writeback_Deferrals_NoSchrodinger CHECK ((CompletedAt is null) or (CancelledAt is null))
/* TODO:FOREIGN KEY */
)
create view Pending with schemabinding as
select
ClientID
from
Writeback.Deferrals
where
ResolvedAt is null
go
alter table Writeback.Deferrals add constraint
DF_Writeback_Deferrals_RequestedAt DEFAULT CURRENT_TIMESTAMP for RequestedAt
go
create unique clustered index PK_Writeback_Pending on Writeback.Pending (ClientID)
go
create procedure Writeback.Defer
#ClientID uniqueidentifier
as
set nocount on
insert into Writeback.Deferrals (ClientID)
select #ClientID
where not exists(select * from Writeback.Pending where ClientID = #ClientID)
go
create procedure Writeback.Cancel
#ClientID uniqueidentifier
as
set nocount on
update
Writeback.Deferrals
set
CancelledAt = CURRENT_TIMESTAMP
where
ClientID = #ClientID and
CompletedAt is null and
CancelledAt is null
go
create procedure Writeback.Complete
#ClientID uniqueidentifier
as
set nocount on
update
Writeback.Deferrals
set
CompletedAt = CURRENT_TIMESTAMP
where
ClientID = #ClientID and
CompletedAt is null and
CancelledAt is null
go
And the code that provokes the error is as follows:
declare #ClientA uniqueidentifier
declare #ClientB uniqueidentifier
select #ClientA = newid(),#ClientB = newid()
select * from Writeback.Pending
exec Writeback.Defer #ClientA
select * from Writeback.Pending
exec Writeback.Defer #ClientB
select * from Writeback.Pending
exec Writeback.Cancel #ClientB --<-- Error being raised here
select * from Writeback.Pending
exec Writeback.Complete #ClientA
select * from Writeback.Pending
select * from Writeback.Deferrals
I've seen a few others encountering such problems, but they seem to either have aggregates in their views (and a message back from MS saying they'd remove the ability to create such indexed views in 2005 SP 1), or they resolved it by applying a merge join in their join clause (but I don't have one).
Initially there was no computed column in the Deferrals table, and the where clause in the view was testing the CompletedAt and CancelledAt columns for NULL separately. But I changed to the above just to see if I could provoke different behaviour.
All of my SET options look right for using indexed views, and if they weren't, I'd expect a less violent error to be thrown.
Any ideas?
you have index corruption. run checkdb and see what errors it gives you. the easiest thing you could do is to rebuild your indexes.
also take a look at this KB article if it applies to your sitution.
Also note that putting a primary key on a GUID column will create a clustered index on it which is the worst thing performance wise you could do.
I managed to work out what's causing this error, by trying to build up this script, from scratch, adding in pieces as I went.
It's some kind of bug that's produced if the view is created as part of a CREATE SCHEMA statement. If I separate the CREATE SCHEMA into it's own batch, and then create the table and view in separate batches, everything works fine.
Long overdue edit - I raised this on Connect here. It was confirmed as being an issue in SQL Server 2008.
Internal builds (in 2010) indicated it was no longer an issue, and I have (just now, 2016) confirmed that the script in the question does not generate the same error in SQL Server 2012. The fix was not back-ported to SQL Server 2008.

Resources