Make column to always contain calculated value without triggers - sql

I was doing practice tests before 70-461 exam and one of questions was to create table that:
Has 4 columns, saleid, unitprice, amount, and price.
Price is calculated by multiplication of unitprice and amount
You can't use triggers.
The last one killed me. How can I do it without triggers? Here is my solutions using instead of triggers.
CREATE TABLE [HR].[Stuff](
[saleid] [int] IDENTITY(1,1) NOT NULL,
[unitprice] [float] NOT NULL,
[amount] [float] NOT NULL,
[price] [float] NULL,
)
GO
CREATE TRIGGER [calculate_price_insert]
ON [HR].[Stuff]
INSTEAD OF INSERT
AS
DECLARE #unitprice float = (SELECT TOP 1 unitprice from inserted )
DECLARE #amount float = (SELECT TOP 1 amount from inserted)
INSERT INTO HR.[Stuff]
VALUES(#unitprice,#amount, #unitprice*#amount)
GO
CREATE TRIGGER [calculate_price_update]
ON [HR].[Stuff]
INSTEAD OF UPDATE
AS
DECLARE #unitprice float = (SELECT TOP 1 unitprice from inserted )
DECLARE #amount float = (SELECT TOP 1 amount from inserted)
UPDATE HR.[Stuff]
SET unitprice = #unitprice, amount = #amount, price = #unitprice*#amount
WHERE unitprice = (SELECT TOP 1 saleid from inserted)
GO

You need to use computed column:
CREATE TABLE [HR].[Stuff](
[saleid] [int] IDENTITY(1,1) NOT NULL,
[unitprice] [float] NOT NULL,
[amount] [float] NOT NULL,
[price] AS ([unitprice] * [amount])
)
LiveDemo
Also storing unitprice and amount as FLOAT may be dangerous because FLOAT isn't accurate datatype. Use DECIMAL(12,4) instead.
Your original trigger solution will fail, because trigger is executed per statement, not per row. Try with:
INSERT INTO [HR].[Stuff](unitprice, amount) VALUES (10, 20), (30, 50), (100,1);
You will loose records with INSERT and get false results with multiple UPDATE.
SqlFiddleDemo

Related

Insert data from user-defined table on some fields, other fields must use scalar variable

How would I make use of my user-defined table that comes loaded with data, insert into a table but 1 of the table fields won't be using the data from the User Defined Table. I want a scalar variable to take its place.
#dataTableType is the following user-defined table:
CREATE TYPE [dbo].[NewSequenceType] AS TABLE (
[FK_Sequence] [int] NULL,
[FK_Status] [int] NULL,
[FK_Shift] [int] NULL,
[PK_SequenceType] [int] NULL,
[TypeName] [varchar](30) NULL,
[SequenceName] [varchar](100) NULL,
[SequenceDetails] [varchar](400) NULL,
[SequenceStatus] [varchar](15) NULL,
[SequenceShift] [varchar](15) NULL,
[EmployeeId] [varchar](8) NULL,
[EquipmentId] [varchar](25) NULL,
[Comments] [varchar](512) NULL,
[EnteredDate] [datetime] NULL
)
My stored procedure:
ALTER PROCEDURE [dbo].[spNewInspectionEntry] (#dataTableType NewSequenceType READONLY)
INSERT INTO dbo.PIT_Inspection (FK_Sequence, FK_Status, FK_Shift, FK_EmployeeName, FK_EquipmentName, Comments, EnteredDate)
SELECT
FK_Sequence, FK_Status, FK_Shift, EmployeeId,
EquipmentId, Comments, EnteredDate
FROM
#dataTableType;
What I'm looking to do:
DECLARE #EmployeeIDExists varchar(8);
--Note that I only care about one row, this is Ok.
SELECT TOP 1 #EmployeeIDExists = EmployeeId FROM #dataTableType;
--Retrieve PKEmployeeId
SELECT #EmployeeIdPK = PK_EmployeeName
FROM dbo.PIT_EmployeeName
WHERE EmployeeId = #EmployeeIDExists
--Now I need to insert the value of #EmployeeIdPK? Along with the rest, it needs to replace EmployeeID and EquipmentId, Not sure how to...
INSERT INTO dbo.PIT_Inspection (FK_Sequence, FK_Status, FK_Shift, FK_EmployeeName, FK_EquipmentName, Comments, EnteredDate)
SELECT
FK_Sequence, FK_Status, FK_Shift, EmployeeId,
EquipmentId, Comments, EnteredDate
FROM
#dataTableType;
How can I get #EmployeeIdPK in Along with the rest, it needs to replace EmployeeID, I don't want the EmployeeId from the user-defined table to go there, I want the variable #EmployeeIdPK instead.
You can put any expression, variable, and literal value in the SELECT list. Just keep in mind that those values will be repeated for all rows.
Meaning:
SELECT Field1,
Field2,
'hello' AS [LiteralText],
5 AS [LiteralNumber],
#Variable AS [ValueFromVariable]
FROM TableName;
will repeated those literal and variable values for all rows.
Hence:
INSERT INTO dbo.PIT_Inspection (FK_Sequence, FK_Status, FK_Shift, FK_EmployeeName,
FK_EquipmentName, Comments, EnteredDate)
SELECT
FK_Sequence, FK_Status, FK_Shift, #EmployeeIdPK,
EquipmentId, Comments, EnteredDate
FROM
#dataTableType;

SQL loop executes but new old values are over written

As my question title says, my program loops but all of my values I updated are being overwritten. Here's the code posted below. Say minRownum is 1 and max is 12, I see the loop execute 12 times correctly and min gets updated +1 each time. But in the end result, only the final row in my column whose RowNum is 12 have any values
I'm not exactly sure why overwriting is occurring since I'm saying "Update it where the rownumber = minrownumber" then I increment minrownum.
Can anyone point to what I am doing wrong? Thanks
WHILE (#MinRownum <= #MaxRownum)
BEGIN
print ' here'
UPDATE #usp_sec
set amount=(
SELECT sum(amount) as amount
FROM dbo.coverage
inner join dbo.owner
on coverage.api=owner.api
where RowNum=#MinRownum
);
SET #MinRownum = #MinRownum + 1
END
PS: I edited this line to say (below) and now every value has the same wrong number (its not distinct but duplicated to all.
set amount = (SELECT sum(amount) as amount
FROM dbo.coverage
INNER JOIN dbo.owner ON coverage.api = owner.api
where RowNum=#MinRownum
) WHERE RowNum = #MinRownum;
Tables:
CREATE TABLE dbo. #usp_sec
(
RowNum int,
amount numeric(20,2),
discount numeric(3,2)
)
CREATE TABLE [dbo].[handler](
[recordid] [int] IDENTITY(1,1) NOT NULL,
[covid] [varchar](25) NULL,
[ownerid] [char](10) NULL
)
CREATE TABLE [dbo].[coverage](
[covid] [varchar](25) NULL,
[api] [char](12) NULL,
[owncovid] [numeric](12, 0) NULL,
[amount] [numeric](14, 2) NULL,
[knote] [char](10) NULL
)
CREATE TABLE [dbo].[owner](
[api] [char](12) NOT NULL,
[owncovid] [numeric](12, 0) NULL,
[ownerid] [char](10) NOT NULL,
[officer] [char](20) NOT NULL,
[appldate] [date] NOT NULL
)
Your UPDATE statement needs its own WHERE clause. Otherwise, each UPDATE will update every row in the table.
And the way you have this written, your subquery still needs its WHERE clause too. In fact, you need to definitively correlate the subquery to your table's (#usp_sec) rows. We cannot tell you how that should be done without more information such as your table definitions.

Inserted clause returns 0 when used with triggers

I'm trying to get the last inserted rows Id from an inserts statement on the following table using SQL server 2012
[dbo].[Table](
[TableId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[CreatedBy] [nvarchar](50) NULL,
[CreatedDate] [datetime2](7) NOT NULL,
[ModifiedBy] [nvarchar](50) NULL,
[ModifiedDate] [datetime2](7) NULL,
CONSTRAINT [pk_Table] PRIMARY KEY CLUSTERED
(
[TableId] ASC
)
I'm also using an audit triggers on that table that are as follows:
trigger [dbo].[trigger_Table_auditColumnAutoInsert]
on [dbo].[Table]
instead of insert
/**************************************************************
* INSTEAD OF trigger on table [dbo].[Table] responsible
for automatically inserting audit column data
**************************************************************/
as
begin
set nocount on
declare #currentTime datetime2
set #currentTime = GETUTCDATE()
insert into [dbo].[Table]
(
Name,
CreatedBy,
CreatedDate,
ModifiedBy,
ModifiedDate
)
select
Name,
ISNULL(CreatedBy, system_user),
#currentTime,
NULL,
NULL
from inserted
select SCOPE_IDENTITY() as [TableId]
goto EOP -- end of procedure
ErrorHandler:
if (##trancount <> 0) rollback tran
EOP:
end
I used different approaches, but nothing 'SAFE' seems to work.
Using scope identity returns null
insert into dbo.[Table](Name) Values('foo')
select SCOPE_IDENTITY()
Using OUTPUT INSERTED always returns 0 for the identity coloumns; although it returns the other inserted values:
declare #tmpTable table
(
TableId int,
Name nvarchar (50)
)
INSERT INTO [dbo].[Table]([Name])
output inserted.TableId, inserted.Name into #tmpTable
VALUES('foo')
select * from #tmpTable
TableId Name
0 foo
I know of another solution to get the inserted Id from the triggers itself, by executing a dynamic sql command as follows:
declare #tmpTable table (id int)
insert #tmpTable (id )
exec sp_executesql N'insert into dbo.[Table](Name) Values(''foo'')'
select id from #tmpTable
I couldn't figure out why in the first 2 cases it is not working; why the SCOPE_IDENTITY() does not work although the triggers execute in the same transaction? And also why the INSERTED clause returns 0 for the identity column.
It appears that the following requirements apply to your audit column data:
Use the insert value supplied for CreatedBy, or use SYSTEM_USER by default.
Always use GETUTCDATE() for CreatedDate.
If the INSTEAD OF trigger (rather than an AFTER trigger) is not essential to your requirements, then you can use DEFAULT constraints on your audit columns and an AFTER INSERT trigger to enforce requirement #2.
CREATE TABLE [dbo].[Table]
(
[TableId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[CreatedBy] [nvarchar](50) NOT NULL CONSTRAINT [DF_Table_CreatedBy] DEFAULT SYSTEM_USER,
[CreatedDate] [datetime2](7) NOT NULL CONSTRAINT [DF_Table_CreatedDate] DEFAULT GETUTCDATE(),
[ModifiedBy] [nvarchar](50) NULL,
[ModifiedDate] [datetime2](7) NULL,
CONSTRAINT [pk_Table] PRIMARY KEY CLUSTERED ([TableId] ASC)
)
GO
CREATE TRIGGER Trigger_Table_AfterInsert ON [dbo].[Table]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
UPDATE [dbo].[Table] SET [CreatedDate]=GETUTCDATE()
FROM [dbo].[Table] AS T
INNER JOIN INSERTED AS I ON I.[TableId]=T.[TableId]
END
GO
Then, both SCOPE_IDENTITY() and OUTPUT INSERTED techniques to get the new TableId value work as expected.
If the INSTEAD OF trigger is essential to your implementation, then SELECT ##IDENTITY is an alternative to SCOPE_IDENTITY.

Create table and get data from another table

I have a table Cost category:
CREATE TABLE [dbo].[CostCategory](
[ID_CostCategory] [int] NOT NULL,
[Name] [varchar](150) NOT NULL,
[Plan] [money] NOT NULL,
[Realization] [money] NULL,
)
go
and I have another table with defined Costs:
CREATE TABLE [dbo].[Cost](
[ID_Cost] [int] NOT NULL,
[Name] [varchar](50) NULL,
[ID_CostCategory] [int] NULL,
[ID_Department] [int] NULL,
[ID_Project] [int] NULL,
[Value] [money] NULL,
)
go
What I want to do is to sum values from Cost table (according to ID_CostCategory) and put into Cost Category table, Realization column. So each ID_CostCategory presents automaticaly sum of Costs from Cost table (per ID_CostCategory).
How can I modify the script of Cost Category table to achieve it? Probably it's not a rocket science but I am really new to SQL.
OK. One more thing I forgot to add...
the structure looks in a way:
Cost>Cost Category>Department>Project
Right now I can easily take data from cost table and present it in Cost Category table. But Cost table includes such data as:
ID_Cost Name ID_CostCategory ID_Department ID_Project Value
1 fv 001 1 1 1 100
2 fv 002 2 1 1 500
3 fv 003 2 2 1 300
4 fv 004 3 2 2 150
5 fv 005 3 3 2 30
6 fv 006 4 3 2 15
I have also table Department which includes colums: ID_Department, Name, Plan,
So now, I want to do is to sum values from Cost table (according to ID_CostCategory and ID_Department) and put into Department table as Realization column. So each ID_Department presents automaticaly sum of Costs from Cost table (per ID_CostCategory and ID_Department).
Hope it is clear (later on will have to do it with table Project, but once I got it, it will be easy)
Try this
--create table without realization column
CREATE TABLE [dbo].[CostCategory](
[ID_CostCategory] [int] NOT NULL,
[Name] [varchar](150) NOT NULL,
[Plan] [money] NOT NULL
) go
CREATE TABLE [dbo].[Cost](
[ID_Cost] [int] NOT NULL,
[Name] [varchar](50) NULL,
[ID_CostCategory] [int] NULL,
[ID_Department] [int] NULL,
[ID_Project] [int] NULL,
[Value] [money] NULL,
) go
Create a UDF to calculate sum of the cost column:
CREATE FUNCTION [dbo].[CalculateRealization](#Id INT)
RETURNS money
AS
BEGIN
DECLARE #cost money
SELECT #cost = SUM(Value)
FROM [dbo].[Cost]
WHERE [ID_CostCategory] = #ID
return #cost
END
Now Alter your CostCategory table to add computed column:
ALTER TABLE [dbo].[CostCategory]
ADD [Realization] AS dbo.CalculateRealization(ID_CostCategory);
Now you can select Realization from Costcategory
SELECT ID_CostCategory, Realization
FROM [dbo].[CostCategory]
Answer to your comment below:
Create Another UDF
CREATE FUNCTION [dbo].[CheckValue](#Id INT, #value Money)
RETURNS INT
AS
BEGIN
DECLARE #flg INT
SELECT #flg = CASE WHEN [Plan] >= #value THEN 1 ELSE 0 END
FROM [dbo].[CostCategory]
WHERE [ID_CostCategory] = #ID
return #flg;
END
Now add Constraint on Cost Table:
ALTER TABLE ALTER TABLE [dbo].[Cost]
ADD CONSTRAINT CHK_VAL_PLAN_COSTCATG
CHECK(dbo.CheckValue(ID_CostCategory, Value) = 1)
You do not need to have a Realization column as part of the CostCategory table. Rather, you will want to use a join.
Select A.ID_CostCategory, A.Name, SUM(B.Value) As Realization from CostCategory A
JOIN Cost B ON A.ID_CostCategory = B.ID_CostCategory
Group By A.ID_CostCategory, A.Name

Problem with SQL In Line Table Function... Query

Hi I have the following query running a function to get the Standard Deviation for a set of Tickers in the following table...
GO
CREATE TABLE [dbo].[Tickers](
[ticker] [varchar](10) NULL,
[date] [datetime] NULL,
[high] [float] NULL,
[low] [float] NULL,
[open] [float] NULL,
[close] [float] NULL,
[volume] [float] NULL,
[time] [datetime] NULL,
[change] [float] NULL
) ON [PRIMARY]
THE PROBLEM: THE FOLLOWING IN LINE TABLE FUNCTION RETURNS STDDEV CALC which is in turn used by a SPROC To calculate Bollinger bands ... (mov average + 2 * STDEV) etc...
The results that I get for some Tickers has weird data ... this is the result set for the ticker 'ATE' or just a sample of the result set.
dayno ticker stddev
484 11/13/2009 0.544772123613694
485 11/16/2009 0.323959874058724
486 11/17/2009 0.287909129182731
487 11/18/2009 0.225018517756637
488 11/19/2009 4.94974746848848E-02
489 11/20/2009 4.94974746848848E-02
As you can see the last two values for some of the tickers results in 'weird data' and the actual ticker table is within very normal ranges.
As you can see from the following in line table function there was some funny stuff going on at the end because it is using a 20 day period and the last value always came back as NULL, so I asked experts to adjust and this is what Peter came up with... it usually works find but as you can see above sometimes does not - does anyone have a suggestion on how I may fix this dilemma??
ALTER FUNCTION dbo.GetStdDev3 (#TKR VARCHAR(10))
RETURNS #results TABLE (
dayno SMALLINT IDENTITY(1,1) PRIMARY KEY
, [date] DATETIME
, [stddev] FLOAT
)
AS BEGIN
DECLARE #min_sysdate DATETIME, #min_tkrdate DATETIME, #rowcount SMALLINT
SET #min_sysdate = DATEADD(DAY,-731,GETDATE())
SET #min_tkrdate = DATEADD(DAY,20,(
SELECT MIN(DATE) FROM TICKERS WHERE TICKER = #TKR))
INSERT #results ([date],[stddev])
SELECT x.[date], ISNULL(STDEV(y.[Close]),0) AS stdev
FROM Tickers x
JOIN Tickers y ON x.[DATE] BETWEEN DATEADD(DAY,-20,y.[DATE]) AND y.[DATE]
WHERE x.[DATE] > #min_tkrdate
AND x.[DATE] > #min_sysdate
AND x.TICKER = #TKR
AND y.TICKER = #TKR
GROUP BY x.[DATE]
SET #rowcount = ##ROWCOUNT
UPDATE #results SET [stddev] = (
SELECT [stddev] FROM #results WHERE dayno = #rowcount-1)
WHERE dayno = #rowcount
RETURN
4.94974746848848E-02 is actually the same thing as 0.0494974746848848
Are you sure this is in error? Seems to me it could just be a low deviation.
Ditto what "d." said. The standard deviation for the last two dates is low, but it was decreasing over time anyway. Also, all that the last update statement does is to set the last row (latest date) in the set equal to the second-to-last value. (Perhaps "adjsut" might have been "delete"?)