Computed column at creation of table gets evaluated every time - sql

I have the following script that creates a table:
CREATE TABLE [dbo].[Persons]
(
[Id] INT IDENTITY(1,1) NOT NULL,
[Name] NVARCHAR(250) NOT NULL,
[Surname] NVARCHAR(250) NOT NULL,
[NumberOfNotes] INT NOT NULL,
[TotalCash] FLOAT NOT NULL,
[Result] AS ([NumberOfNotes] * [TotalCash] * ROUND(RAND() * 2, 0)),
CONSTRAINT [PK_Persons] PRIMARY KEY ([Id] ASC)
);
The table gets created correctly and whenever I insert a new person the Result gets calculated. Problem is that it gets re-evaluated every time I do a select. I would like the computed value to stay the same for that record. How do I achieve this? Thanks in advance!

I simple trick is to seed rand():
[Result] AS ([NumberOfNotes] * [TotalCash] * ROUND(RAND(id) * 2, 0)),
Basically, this is using a "deterministic" random number generator. You can do that in other ways as well.
Alternatively, you could just assign it a value when you insert new rows into the table.

Related

SQL Server identity number is not consecutive

I am creating a database table with an index number (CustRef) for each row.
The index starts from 1 and increases 1 with each new row. The query language as followed:
CREATE TABLE [dbo].[CustDetails]
(
[CustRef] [int] IDENTITY(1,1) NOT NULL,
[LName] [nchar](25) NOT NULL,
[FName] [nchar](25) NOT NULL,
[Address] [nchar](80) NULL,
[Suburb] [nchar](25) NULL,
[State] [nchar](5) NULL,
[PCode] [nchar](5) NULL,
CONSTRAINT [PK_CustDetails]
PRIMARY KEY CLUSTERED ([CustRef] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
The table is created successfully and I was testing it by inserting some sample data. At one stage, I deleted a row, which the index number (CustRef) was 6. After I deleted index 6 row and continue inserting sample data.
Unfortunately, the index number is not consecutive. In other word, I was expecting the new data content will use the index 6 as its row index number. However, new entries skip index 6, it starts from index 7.
As you can see from the above screenshot, between index 5 and 7, index 6 is missing.
How to resolve this issue? Thanks in advance.
1. Create the table
CREATE TABLE [dbo].[CustDetails]
(
[CustRef] [int] IDENTITY(1,1) NOT NULL,
[LName] [nchar](25) NOT NULL,
[FName] [nchar](25) NOT NULL,
[Address] [nchar](80) NULL,
[Suburb] [nchar](25) NULL,
[State] [nchar](5) NULL,
[PCode] [nchar](5) NULL,
)
GO
Insert records
deleted the last record. in my case it was 4th record.
Set identity insert on
SET IDENTITY_INSERT [dbo].[CustDetails] ON;
Inserted the record with identity column included.
insert into [dbo].[CustDetails] ([CustRef] , [LName], [FName] , [Address],
[Suburb], [State] , [PCode])values (4, 'test4',
'test','test','test','test','test')
this approach is good for testing. But normally, unless you truncate the table, your index will not reset in identity columns in SQL server as it is already assigned. So this is kind of a solution. using Identity insert you get the row count and by based on max(row count) you can set identity column value when inserting a record.
In your table CustDetails column CustRef is identity column. It auto increments at 1,2,3,4,5,6 now you deleted last row i.e. delete from CustDetails where CustRef=6. Technically next row will be inserted is 7 because SQL has already assigned value 6 to previous row. In other scenario if you had entered upto 1,2,3,4,5,6,7,8,9 and now deleting 6th row. what do you expect next value according to your requirement? 10 or 6?
I had implemented logic when client was asking to provide facility to delete last row only and no number to be skipped, I had write a logic of maxvalue + 1 for next row. you can write as remove identity and max(CustRef)+1 at time of insert. Alternatively you can use dbcc reseed that after each delete you write
declare #id int = 0
SELECT #id = MAX(CustRef) FROM CustDetails
DBCC CHECKIDENT ('dbo.CustDetails', reseed, #id)
this will tell sql to reset value to last max. which is not at all recommended way to do so. this is just an option but yeah you can choose remove identity and write logic for get max(CustRef) before insert statement and increment it and insert into table CustDetails.
Well, if it didn't work that way, then you would run into this:
insert custRef ...; // id = 1
insert custRef ...; // id = 2
insert custRef (fName, lName)
values ('Jane', 'Goodall');
// id = 3
Jane Goodall! She deserves a prize.
declare #giveAPrizeTo int = (
select id
from custRef
where fname = 'Jane'
and lname = 'Goodall'
);
We'll actually deliver it in a bit.
But first, another task. Customers that can't be contacted aren't really useful. The boss asked that we keep the table pure.
delete custRef where address is null;
alter table custRef alter column address nchar(80) not null;
Okay, well, moving on, add the next person.
insert custRef (fName, lName, address)
values ('Jeffrey', 'Dahmer', '1234 W Somewhere; Milwaukee, Wisconsin 12345');
In this hypothetical dialect where id's get recycled, Jeffrey Dahmer now has id = 3.
Hmm, that's interesting. I should be careful of the newest customer. Well, I got distracted, what was I doing? Oh yeah, that prize! I'd better deliver it.
declare #message nvarchar(max) = (
select 'Congratulations ' + fName + ' ' + lName ', ' +
'we support your good works. Have a prize!'
from custRef
where id = #giveAPrizeTo;
);
print (#message);
Congratulations Jeffrey Dahmer, we support your good works. Have a Prize!
Oops!

Subqueries are not allowed in this context. Only scalar expressions are allowed in CREATE TABLE with AS syntax

I am trying to run the below script but I keep getting the error messages
"Subqueries are not allowed in this context. Only scalar expressions are allowed." on lines 16 and 34.
I know where it's failing - it is failing on the AS clauses, but I don't know how to correct it with different code to stop the errors from appearing.
I have tried looking around at other existing questions but none helped me that I can find. As the issue I have here is using data from columns in different tables along with columns in the current table.
Could I get some help with getting this working and advise what code will be better please?
Thanks for your help in advance!
Dan
This is the code for my database::
CREATE DATABASE [LEARNING]
GO
CREATE TABLE Trainees
(
Trainee_ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Name varchar(50) NOT NULL,
[Assigned Tutor_ID] int NOT NULL,
)
GO
CREATE TABLE Tutors
(
Tutor_ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Name varchar(50) NOT NULL,
[Assigned Trainee_ID] AS (Select Trainee_ID from Trainees where Tutors.[Assigned Trainee_ID] = Trainees.Trainee_ID) NOT NULL
)
GO
CREATE TABLE [Rooms]
(
Room_ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Room Name] varchar(50) NOT NULL,
[Cost per hour] money NOT NULL
)
GO
CREATE TABLE [Rooms Rented]
(
Rented_ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Room_ID int NOT NULL,
Tutor_ID int NOT NULL,
[Length of time in hours] int NOT NULL,
[Total Cost] AS (select ([Rooms Rented].[Length of time in hours])*([Rooms].[Cost per hour]) from [Rooms]) NOT NULL
)
GO
INSERT INTO Tutors values ('Nikki Smith',1)
GO
INSERT INTO Trainees Values ('Tyler Hatherall')
GO
INSERT INTO Rooms values ('Training Room 1',6.50)
GO
INSERT INTO [Rooms Rented] values (1,1,2)
GO
Computed columns are used to ensure columns persisted property within the table itself.
In your case, you need to have another update after you created table, populate the column by the query similar to below query, also, you need to create Foreign Key in the Total Cost column based on what you try to achieve.
UPDATE A
SET A.[Total Cost] = A.[Length of time in hours] * B.[Cost per hour] --add ISNULL to treat NULL if needed
FROM [Rooms Rented] as A
INNER JOIN [Rooms] as B
ON B.Room_ID = A.Room_ID
Your AS statements are computed columns. When your computed columns refer to other tables, you cannot implement this directly. You will have to create scalar functions first.
For example, after creating Rooms, create this function that takes a room id and returns cost per hour:
create function f_get_Rooms_CostPerHour (#Room_ID int)
returns money
as
return (select [Cost per hour] from [Rooms] where [Rooms].Room_ID=#Room_ID)
Now you can use this in your computed column formula. Note that a computed column formula never has a SELECT in it. It also does not have a null/not null specification.
CREATE TABLE [Rooms Rented]
(
Rented_ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Room_ID int NOT NULL,
Tutor_ID int NOT NULL,
[Length of time in hours] int NOT NULL,
[Total Cost] AS ([Length of time in hours]*f_get_Rooms_CostPerHour([Room_ID]))
)

Insert only modified values and column names into a table

I have a sql server 2012 database. In which i have a changeLog table that contains
TableName, ColumnName, FromValue and ToValue columns. Which will be used to keep track of modified columns and data.
So if any update occur through application then only modified columns should insert into this table with its new and old value.
Can anyone help me in this.
For Example:
If the procedure updates all columns of property table (propertyName, address)
then if user update propertyName (but update also contains address column but with no data change) then only propertyName and its data will be inserted into ChangeLog table not address column and its data because address data does not contains any data change.
IF there is no other auditing requirement at all - you would not be thinking about Auditing in any way without this - then OK, go for it. However this is a very limited use of Auditing: User X changed this field at time Y. Generally this is interesting as part of a wider question: what did user X do? What happened to that customer data in the database to end up the way it is now?
Questions like that are harder to answer if you have the data structure you propose and would be quite onerous to reconstruct. My usual approach would be as follows. Starting from a base table like so (this from one of my current projects):
CREATE TABLE [de].[Generation](
[Id] [int] IDENTITY(1,1) NOT NULL,
[LocalTime] [datetime] NOT NULL,
[EntityId] [int] NOT NULL,
[Generation] [decimal](18, 4) NOT NULL,
[UpdatedAt] [datetime] NOT NULL CONSTRAINT [DF_Generation_UpdatedAt] DEFAULT (getdate()),
CONSTRAINT [PK_Generation] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
(I've excluded FK definitions as they aren't relevant here.)
First create an Audit table for this table:
CREATE TABLE [de].[GenerationAudit](
[AuditId] int identity(1, 1) not null,
[Id] [int] NOT NULL,
[LocalTimeOld] [datetime] NULL,
[EntityIdOld] [int] NULL,
[GenerationOld] [decimal](18, 4) null,
[UpdatedAtOld] [datetime] null,
[LocalTimeNew] [datetime] null,
[EntityIdNew] [int] null,
[GenerationNew] [decimal](18, 4) null,
[UpdatedAtNew] [datetime] NOT NULL CONSTRAINT [DF_GenerationAudit_UpdatedAt] DEFAULT (getdate()),
[UpdatedBy] varchar(60) not null
CONSTRAINT [PK_GenerationAudit] PRIMARY KEY CLUSTERED
(
[AuditId] ASC
)
This table has an *Old and a *New version of each column that can't change. The Id, being an IDENTITY PK, can't change so no need for an old/new. I've also added an UpdatedBy column. It also has a new AuditId IDENTITY PK.
Next create three triggers on the base table: one for INSERT, one for UPDATE and one for DELETE. In the Insert trigger, insert a row into the Audit table with the New columns selected from the inserted table and the Old values as null. In the UPDATE one, the Oldvalues come from the deleted and the new from the inserted. In the DELETE trigger, old from from deleted and the new are all null.
The UPDATE trigger would look like this:
CREATE TRIGGER GenerationAuditUpdate
ON de.Generation
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
insert into de.GenerationAudit (Id, LocalTimeOld, EntityIdOld, GenerationOld, UpdatedAtOld,
LocalTimeNew, EntityIdNew, GenerationNew, UpdatedAtNew,
UpdatedBy)
select isnull(i.Id, d.Id), d.LocalTime, d.EntityId, d.Generation, d.UpdatedAt,
i.LocalTime, i.EntityId, d.Generation, getdate(),
SYSTEM_USER)
from inserted i
full outer join deleted d on d.Id = i.Id;
END
GO
You then have a full before/after picture of each change (and it'll be faster than seperating out diffs column by column). You can create views over the Audit table to get entries where the Old value is different to the new, and include the base table Id (which you will also need in your structures!), the user who did it, and the time they did it (UpdatedAtNew).
That's my version of Auditing and it's mine!

Table cell to count rows in other table

I'm not entirely sure if I'm even going about this in the right manner.
MVC+EF site so i could do this in the controller but I would prefer it in the DB if possible.
I have two tables. One contains entries and one contains a list of members. I want the list of members to have a column that contains the count of how many times the member name appears in the list of entries. Can I do this in the definition of the table itself?
I know this query works:
select count(*)
from dbo.Entries
where dbo.Entries.AssignedTo = 'Bob Smith'
But is there any way of doing this? What is the correct syntax?
CREATE TABLE [dbo].[Members] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (100) NOT NULL,
[Email] NVARCHAR (500) NOT NULL,
[Count] INT = select count(*) from dbo.Entries where dbo.Entries.AssignedTo = [Name]
PRIMARY KEY CLUSTERED ([ID] ASC)
I've done some searching and have tried a few different syntax's but I'm completely lost at this point so if anyone can get me headed in the correct direction I would really appreciate it.
Thanks in advance.
You could create Members as a view combining both the Entries data and another table. (Warning: Syntax not tested)
CREATE TABLE [_member_data] (
[ID] INT IDENTITY(1, 1) NOT NULL,
[Name] NVARCHAR (100) NOT NULL,
[Email] NVARCHAR (500) NOT NULL,
PRIMARY KEY CLUSTERED ([ID] ASC)
);
CREATE VIEW [dbo].[Members] AS
SELECT ID, Name, Email, COUNT(*)
FROM _member_data JOIN dbo.Entries ON [Name] = [AssignedTo]
GROUP BY 1, 2, 3;
It is possible to take this even further with triggers/rules that rewrite attempted inserts into Members as inserts to the appropriate backing table. But to get the kind of expressive information that you are looking for, you really want to explore using a view.

How to update 2nd table with identity value of inserted rows into 1st table

I have the following table structures
CREATE TABLE [dbo].[WorkItem](
[WorkItemId] [int] IDENTITY(1,1) NOT NULL,
[WorkItemTypeId] [int] NOT NULL,
[ActionDate] [datetime] NOT NULL,
[WorkItemStatusId] [int] NOT NULL,
[ClosedDate] [datetime] NULL,
)
CREATE TABLE [dbo].[RequirementWorkItem](
[WorkItemId] [int] NOT NULL,
[RequirementId] [int] NOT NULL,
)
CREATE TABLE #RequirmentWorkItems
(
RequirementId int,
WorkItemTypeId int,
WorkItemStatusId int,
ActionDate datetime
)
I use the #RequirmentWorkItems table to create workitems for requirements. I then need to INSERT the workitems into the WorkItem table and use the identity values from the WorkItem table to create the cross-reference rows in the RequirementWorkItem table.
Is there a way to do this without cursoring thru each row? And I can't put the RequirementId into the WorkItem table because depending on the WorkItemTypeId the WorkItem could be linked to a Requirement or a Notice or an Event.
So there are really 3 xref tables for WorkItems. Or would it be better to put a RequirementId, NoticeId and EventId in the WorkItem table and 1 of the columns would have a value and other 2 would be null? Hopefully all this makes sense. Thanks.
You should read MERGE and OUTPUT – the swiss army knife of T-SQL for more information about this.
Today I stumbled upon a different use for it, returning values using an OUTPUT clause from a table used as the source of data for an insertion. In other words, if I’m inserting from [tableA] into [tableB] then I may want some values from [tableA] after the fact, particularly if [tableB] has an identity. Observe my first attempt using a straight insertion where I am trying to get a field from #source.[id] that is not used in the insertion: