Insert statement with a conditional target column - sql

I am trying to store sensor data recorded to the appropriate target column, depending on what the latest settings are in the settings table (represented as #Unit in the code). The code I have so far gives me a syntax error at line 19 near 'Time', which I am not sure as to why. Any ideas?
CREATE PROCEDURE StoreTemp
#Temperature float,
#Seconds int,
#SessionId int,
#DatasetId int
AS
DECLARE
#Unit varchar(20),
#TempCol varchar(20)
SELECT #Unit = RecordingUnit from ReadLastUnit
SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END)
INSERT INTO DATASET (#TempCol, Time)
VALUES (#Temperature, GETDATE())
GO

You can't use a variable to specify a column name in an insert statement. The column name must be static.
You could use dynamic SQL, however in this case you can just conditionally insert a value into the correct column using a case expression as follows:
CREATE PROCEDURE StoreTemp
(
#Temperature float
, #Seconds int
, #SessionId int
, #DatasetId int
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Unit varchar(20), #TempCol varchar(20);
SELECT #Unit = RecordingUnit from ReadLastUnit;
--SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END);
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN #Unit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN #Unit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE();
END
GO
For better performance and a neater query, you could even simplify the insert to do it all e.g.
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN RecordingUnit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN RecordingUnit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE()
from ReadLastUnit;
Thats assuming you can always guarantee a single row in ReadLastUnit.

Related

Insert query with case or if-else logic possible?

I want to insert values present in SELECT Statement below into table #tab3.
But I want to apply some sort of if-else or case logic to check like this one:
if (ABCList,1) = 'DOB' Then insert it into Dob1 else NULL
if (ABCList,2) = '04MARCH 1999' Then insert it into Dobnum else NULL
if (ABCList,3) = 'Passport' Then insert it into Pass1 else NULL
if (ABCList,4) = 'ABCC123' Then insert it into Passnum else NULL
But I cant figure out how to move data directly from a string into table.
MAIN CODE:
DECLARE #string3 varchar(max) = 'DOB;04MARCH 1999;Passport;ABCC123';
DECLARE #sep3 char(1) = ';'
DECLARE #dot3 char(1) = '.'
DECLARE #tab3 TABLE(
id INT IDENTITY(1,1) PRIMARY KEY,
Dob1 varchar(max),
Dobnum varchar(max),
Pass1 varchar(max),
Passnum varchar(max)
);
SELECT REVERSE(REPLACE(REVERSE(#string3), #sep3, #dot3)) as ABClist
INSERT into #tab3 (Dob1,Dobnum,Pass1,Passnum)
values
(
);
select * from #tab3
Could you not use CASE to achieve what you want?
Something such as
INSERT INTO #tab3 (Dob1,Dobnum,Pass1,Passnum)
SELECT
(
CASE
WHEN //Condition == 'DOB1'
THEN //Whatever
ELSE NULL
END
) AS Dob1,
(
CASE
WHEN //Condition == '04MARCH 1999'
THEN //Whatever
ELSE NULL
END
) AS Dobnum,
and so on.

Insert into table within function

I would like to insert the #OBV's value into a table inside the function. What is the correct way to achieve it?
alter function CalculateOnBalanceVolume (
#operation varchar(3),
#volume money
)
returns char(4) as
begin
declare #prevOBV as money,
#OBV as money
set #prevOBV = (
select top 1 OnBalanceVolume
from OnBalanceVolume
order by EventTime desc
)
if (#operation = 'add') set #OBV = #prevOBV + #volume
if (#operation = 'sub') set #OBV = #prevOBV - #volume
insert into OBVTable values (#OBV) // error
return #OBV
end;
Functions cannot perform any actions known as side-effecting which includes inserting or updating or deleting from tables, so you cannot use a Function for this.
To use a stored procedure you might have:
create procedure CalculateOnBalanceVolume
#operation varchar(3),
#volume decimal(9,2),
#OBV decimal(9,2) output
as
select top (1) #Obv=OnBalanceVolume +
case when #Operation='add' then #volume else -#volume end
from OnBalanceVolume
order by EventTime desc
insert into OBVTable values (#OBV)
go
And then to invoke the procedure and get your output value you would do for example:
declare #OBV decimal(9,2)
exec CalculateOnBalanceVolume 'add', 100, #OBV output
select #OBV as OutputValue

Data not coming when old value is changed from null to other value

Select statement is not displaying data when I change value from 0 to null or vice versa.
But when I change data from 0 to 1 select statement starts displaying data.
Please find my code (query)
declare #tmptable TABLE (Id INT, IsVal BIT)
INSERT Into #tmptable VALUES(1,0)
--SELECT * FROM #tmptable
DECLARE #Id INT
DECLARE #IsVal BIT
SET #Id=1
SET #IsVal=NULL
select #Id as PrimaryKeyValue
,CAST(IsVal as VARCHAR) as OldValue,CAST(ISNULL(#IsVal,'') as VARCHAR) as NewValue
,'IsVal' AS DisplayFieldName,
CASE IsVal
WHEN 1 THEN 'Yes'
WHEN 0 THEN 'No'
END as DisplayOldValue
,CASE #IsVal
WHEN 1 THEN 'Yes'
WHEN 0 THEN 'No'
END as DisplayNewValue
from #tmptable WHERE Id =#Id and ISNULL(IsVal,'')<>ISNULL(#IsVal,'')
There is problem with statement :-
ISNULL(IsVal,'')<>ISNULL(#IsVal,'')
Can't figure out the change I need to make to the above statement so that my query can work as I desired. Please help.
#IsVal variable is used to set value.
Thanks
You are correct, the problem is this expression: ISNULL(IsVal,'')<>ISNULL(#IsVal,'')
When IsVal is 0 and #IsVal is NULL, this becomes:
0 <> ''
which will compare as integers, so the '' becomes 0 and you get:
0 <> 0
So, in your case, 0, '' and NULL will all be treated as equal. You need to choose an invalid int (maybe -1?) or cast the 0 to a varchar to do that comparison.
Thanks for the help,
I was able to resolve the issue:-
declare #tmptable TABLE (Id INT, IsVal BIT)
INSERT Into #tmptable VALUES(1,0)
--SELECT * FROM #tmptable
DECLARE #Id INT
DECLARE #IsVal BIT
declare #oldval VARCHAR(10)
SET #Id=1
SET #IsVal=null
select #oldval=isval from #tmptable WHERE Id=1
print ISNULL(#oldval,'')
print ISNULL(#IsVal,'')
--if(ISNULL(CAST(#oldval AS INT),'')<>ISNULL(CAST(#IsVal AS INT),''))
--BEGIN
PRINT '1'
select #Id as PrimaryKeyValue
,CAST(IsVal as VARCHAR(10)) as OldValue,CAST(ISNULL(#IsVal,'') as VARCHAR(10)) as NewValue
,'IsVal' AS DisplayFieldName,
CASE IsVal
WHEN 1 THEN 'Yes'
WHEN 0 THEN 'No'
END as DisplayOldValue
,CASE #IsVal
WHEN 1 THEN 'Yes'
WHEN 0 THEN 'No'
END as DisplayNewValue
from #tmptable WHERE Id =#Id and ISNULL(CAST(IsVal AS VARCHAR),'null')<>ISNULL(CAST(#IsVal AS VARCHAR),'null')
Why not just use a simple comparison?
(IsVal = #IsVal or IsVal IS NULL AND #IsVal IS NULL)
You may still have problems with type conversion, assuming the types are not compatible.
Why not just use a simple comparison?
(IsVal = #IsVal or IsVal IS NULL AND #IsVal IS NULL)
You may still have problems with type conversion, assuming the types are not compatible.
If that is the case:
(IsVal = CONVERT(VARCHAR(255), #IsVal) or
IsVal IS NULL AND #IsVal IS NULL
)
There is no need to invent special values for isnull() or coalesce().

Stored Function with Multiple Queries and Different Selected Columns

I have series of queries based on a report type. For simplicity here is an example of what i'm trying to do:
If #Reporttype = '1'
Select lcustomerid, lname, fname
from customers
Where dtcreated > #startdate
Else if #Reporttype = '2'
Select barcode, lname, fname
from employees
where dtcreated > #startdate
Else if #reporttype = '3'
Select thetime, lname, name, barcode, lcustomerid
from Customers
where dtcreated > #startdate
You'll notice that I run 3 separate queries, based on the report type being passed. You'll also notice I am returning different columns and the number of columns.
I'd like to make this a stored function, and return the columns I need based on the report type I pass. However, I know that since the number of columns, and the column names are different - that's not going to work as a stored function as I'd like it to.
The major problem here will be reporting this information - I don't want to have separate functions, because i'll have to maintain different reports for each report type.
Is there a way I can make this work?
You can use multi-statement function but you need to specify all columns which will be returned by 3 select statements. It seems it's impossible return multiple result sets.
User-defined functions can not return multiple result sets. Use a
stored procedure if you need to return multiple result sets. https://msdn.microsoft.com/en-us/library/ms191320.aspx
This is one inconvenience but in report you can use only columns you need, others will be nulls.
CREATE FUNCTION MyFun
(
#Reporttype int,
#startdate datetime
)
RETURNS
#Result TABLE
(
lcustomerid int,
lname nvarchar(50),
fname nvarchar(50),
barcode int,
thetime datetime,
name nvarchar(50)
)
AS
BEGIN
If #Reporttype = '1'
insert into #Result (lcustomerid, lname, fname)
select lcustomerid, lname, fname
from customers
Where dtcreated > #startdate
Else if #Reporttype = '2'
insert into #Result (barcode, lname, fname)
Select barcode, lname, fname
from employees
where dtcreated > #startdate
Else if #reporttype = '3'
insert into #Result (thetime, lname, name, barcode, lcustomerid)
Select thetime, lname, name, barcode, lcustomerid
from customers
where dtcreated > #startdate
RETURN
END
So, you can call function in this way
SELECT * FROM dbo.MyFun (1, getdate())
If you cannot use stored procedure and you need to use a function, you can UNPIVOT the data and than in the client side you can PIVOT it.
I need to do something like this when different number of columns are returned to SQL Server Reporting Services report. For example, the following code is always returning three columns - RowID, Column, Value:
DECLARE #Table01 TABLE
(
[ID] INT
,[Value01] INT
,[Value02] NVARCHAR(256)
,[Value03] SMALLINT
);
DECLARE #Table02 TABLE
(
[ID] INT
,[Value01] INT
);
INSERT INTO #Table01 ([ID], [Value01], [Value02], [Value03])
VALUES (1, 111, '1V2', 7)
,(2, 222, '2V2', 8)
,(3, 333, '3V2', 9);
INSERT INTO #Table02 ([ID], [Value01])
VALUES (1, 111)
,(2, 222)
,(3, 333);
-- your function starts here
DECLARE #Mode SYSNAME = 'Table01' -- try with 'Table02', too
DECLARE #ResultSet TABLE
(
[RowID] INT
,[Column] SYSNAME
,[Value] NVARCHAR(128)
);
IF #Mode = 'Table01'
BEGIN;
INSERT INTO #ResultSet ([RowID], [Column], [Value])
SELECT [ID]
,[Column]
,[Value]
FROM
(
SELECT [ID]
,CAST([Value01] AS NVARCHAR(256))
,CAST([Value02] AS NVARCHAR(256))
,CAST([Value03] AS NVARCHAR(256))
FROM #Table01
) DS ([ID], [Value01], [Value02], [Value03])
UNPIVOT
(
[Value] FOR [Column] IN ([Value01], [Value02], [Value03])
) UNPVT
END;
ELSE
BEGIN;
INSERT INTO #ResultSet ([RowID], [Column], [Value])
SELECT [ID]
,[Column]
,[Value]
FROM
(
SELECT [ID]
,CAST([Value01] AS NVARCHAR(256))
FROM #Table02
) DS ([ID], [Value01])
UNPIVOT
(
[Value] FOR [Column] IN ([Value01])
) UNPVT
END;
SELECT *
FROM #ResultSet;
Then in the reporting I need to perform pivot operation again. This is workaround with many limitations:
the unpivot data must be cast to its largest type (usually, string)
unnecessary operations are performed (pivot -> unpivot) instead of just rendering the data;
it does not working well with large amount of data (it is slow)
and others..
For This you may create a scalar value function that return an xml type column and then you can populate that xml tag values to your report screen
CREATE FUNCTION ReportFunc
(
#intReporttype int,
#dtStartdate datetime
)
RETURNS XML
BEGIN
Declare #xmlResult xml
If #intReporttype = '1'
SET #xmlResult = (
select lcustomerid, lname, fname
from customers
Where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
Else if #intReporttype = '2'
SET #xmlResult = (
Select barcode, lname, fname
from employees
where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
Else if #intReporttype = '3'
SET #xmlResult = (
Select thetime, lname, name, barcode, lcustomerid
from customers
where dtcreated > #dtStartdate
FOR XML PATH (''), TYPE
)
RETURN #xmlResult
END
In SQL it is difficult to create something similar so generic or abstract, especially when it has to do with SELECT of colums. If your purpose is to write as less code as you can in order your sql script to be maintained easily and to be able to add new report types in the future with just minor changes I would suggest to use a stored procedure with dynamic sql. You cannot use a function while you wisk your SELECT to be dynamic, its the wrong method. I would write something like that
CREATE PROCEDURE MyProcedure
(
#ReportType int,
#startdate datetime
)
AS
BEGIN
DECLARE #colNames varchar(MAX),#tblName varchar(MAX),#sSQL varchar(MAX);
SELECT #colNames = CASE
WHEN #ReportType = 1 THEN
'lcustomerid, lname, fname' --You can also add alias
WHEN #ReportType = 2 THEN
'barcode, lname, fname'
WHEN #ReportType = 3 THEN
'thetime, lname, name, barcode, lcustomerid'
ELSE
RAISEERROR('Error msg');
END,
#tblName = CASE
WHEN #ReportType = 1 OR #ReportType = 3 THEN
'customers' --You can also add alias
WHEN #ReportType = 2 THEN
'employees'
ELSE
RAISEERROR('Error msg');
END
SET #sSQL =
'Select '+#colNames+'
from '+#tblName +'
where dtcreated > '''+CONVERT(varchar(10), #startdate, 121)+''''
EXEC(#sSQL)
END
And you will call it as
EXEC MyProcedure 1,'20170131'
for example
With this code every time you want a new report type you will need to add just another line in case with the requested column names. I have used this way in working with Crystal reports and I think it is the best possible solution
If you can use Stored Procedures then for maintainability I would look at using a master stored procedure which calls other stored procedures to return different result sets:
CREATE PROCEDURE MyProc_1(#startdate DateTime)
AS
BEGIN
SELECT lcustomerid, lname, fname
FROM customers WHERE dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc_2(#startdate DateTime)
AS
BEGIN
SELECT barcode, lname, fname
FROM employees where dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc_3(#startdate DateTime)
AS
BEGIN
SELECT thetime, lname, name, barcode, lcustomerid
FROM Customers WHERE dtcreated > #startdate
END
GO
CREATE PROCEDURE MyProc(#Reporttype char(1), #startdate DateTime)
AS
BEGIN
IF #Reporttype = '1' EXEC MyProc_1 #startdate
ELSE IF #Reporttype = '2' EXEC MyProc_2 #startdate
ELSE IF #reporttype = '3' EXEC MyProc_3 #startdate
END
GO
And to use:
DECLARE #dt datetime = getdate()
EXEC MyProc 1, #dt
CREATE Proc Emp_det
(
#Reporttype INT,
#startdate DATETIME
)
AS
BEGIN
If #Reporttype = '1' BEGIN
Select lcustomerid, lname, fname
FROM customers
WHERE dtcreated > #startdate
END
ELSE IF #Reporttype = '2' BEGIN
Select barcode, lname, fname
FROM employees
WHERE dtcreated > #startdate
END
ELSE IF #reporttype = '3' BEGIN
Select thetime, lname, name, barcode, lcustomerid
FROM Customers
WHERE dtcreated > #startdate
END
END
GO
Exec Emp_det 1,GETDATE()

How to make this code more professional look?

DECLARE #Coid INT
DECLARE #DTRID INT
DECLARE #EMPID INT
DECLARE #DATE datetime
SELECT TOP 1 #EMPID = tblEmployees.Id, #Coid = tblEmployees.CompanyId, #DATE = tblDailyTimeRecord.TimeIn, #DTRID = tblDailyTimeRecord.Id FROM tblEmployees INNER JOIN tblDailyTimeRecord ON tblEmployees.Id = tblDailyTimeRecord.EmployeeId WHERE tblDailyTimeRecord.Date = CONVERT(date, GETDATE()) AND tblDailyTimeRecord.TimeOut IS NULL ORDER BY tblDailyTimeRecord.ID DESC
IF #Coid IS NULL
BEGIN
DECLARE #IdentityValue TABLE ( ContactID int,EmpId int,DATE datetime)
INSERT INTO [tblDailyTimeRecord]([EmployeeId],[Date],[TimeOut],[IsModified],[CompanyId])
OUTPUT INSERTED.Id,INSERTED.EmployeeId,INSERTED.TimeOut INTO #IdentityValue
Select Id,CONVERT(date, GETDATE()),GETDATE(),0,CompanyId From tblEmployees Where AccessCode = 'GI0056'
INSERT INTO tblTimeLog([EmployeeId],[Time],[Type],[TimeLogSourceId],[CreationDate])
Select EmpId,DATE,2,11,GETDATE() From #IdentityValue
END
ELSE
BEGIN
DECLARE #PAIR int
DECLARE #TIMEOUT datetime
SET #TIMEOUT = GETDATE()
IF #DATE IS NULL
SET #PAIR = 0
ELSE
SET #PAIR = 1
UPDATE [tblDailyTimeRecord] SET TimeOut = #TIMEOUT, PairNo = #PAIR WHERE ID = #DTRID
INSERT INTO tblTimeLog([EmployeeId],[Time],[Type],[TimeLogSourceId],[CreationDate])
VALUES (#EMPID,#TIMEOUT,2,11,#TIMEOUT)
END
Just general formatting rules:
1. All declaration better are in the beginning.
2. Be consistent: all reserved words in capital
3. Do not use reserved words for column names.
4. Comment your code.
5. Use unified intends.
6. Finish statements with semicolon.
7. If you can, use CASE instead of IF SET.
8. Use short aliases for tables and views.
9. Name variables in SELECT statement, then it will be clear what goes where.
Have I forgot something?
Logic/Design.
1. Instead of multiple variables you could use temp table variable or just temp table. It would make code shorter and more readable.
2. First SELECT returns one row, but in the INSERT statement you might insert something completely irrelevant.
3. Having the same data in two tables might be a sign of bad design.
4. Having Date and Datetime for the same event in the same row is very suspicious.
It might be as the code below. Hope that helps.
DECLARE #Coid INT;
DECLARE #DTRID INT;
DECLARE #EMPID INT;
DECLARE #DATE DATETIME;
DECLARE #PAIR INT;
DECLARE #TIMEOUT DATETIME;
DECLARE #IdentityValue TABLE (
ContactID int,
EmpId int,
ContactDate DATETIME
);
-- Select ONLY one row
SELECT TOP 1
#EMPID = e.Id,
#Coid = e.CompanyId,
#DATE = tr.TimeIn,
#DTRID = tr.Id,
#TIMEOUT = GETDATE(),
#PAIR = CASE WHEN tr.TimeIn IS NULL THEN 0 ELSE 1 END
FROM tblEmployees as e
INNER JOIN tblDailyTimeRecord as tr
ON e.Id = tr.EmployeeId
WHERE tr.Date = CONVERT(DATE, GETDATE())
AND tr.TimeOut IS NULL
ORDER BY tr.ID DESC;
IF #Coid IS NULL
BEGIN
-- Insert more than one row or might insert nothing
INSERT INTO [tblDailyTimeRecord] (
[EmployeeId],
[Date],
[TimeOut],
[IsModified],
[CompanyId]
)
OUTPUT INSERTED.Id,
INSERTED.EmployeeId,
INSERTED.TimeOut
INTO #IdentityValue
SELECT Id as [EmployeeId],
CONVERT(DATE, GETDATE()) as [Date],
#TIMEOUT as [TimeOut],
0 as [IsModified],
CompanyId
FROM tblEmployees
WHERE AccessCode = 'GI0056';
INSERT INTO tblTimeLog(
[EmployeeId],
[Time],
[Type],
[TimeLogSourceId],
[CreationDate])
SELECT EmpId as [EmployeeId],
ContactDate as [Time],
2 as [Type],
11 as [TimeLogSourceId],
#TIMEOUT as [CreationDate]
FROM #IdentityValue;
END
ELSE
BEGIN
UPDATE [tblDailyTimeRecord]
SET TimeOut = #TIMEOUT,
PairNo = #PAIR
WHERE ID = #DTRID
INSERT INTO tblTimeLog(
[EmployeeId],
[Time],
[Type],
[TimeLogSourceId],
[CreationDate]
) VALUES (
#EMPID,
#TIMEOUT,
2,
11,
#TIMEOUT
);
END