In my stored procedure, i have a select statement which will return more than one row of results. And i would want to insert those results into another table.
When i run the stored procedure, there are two rows of result returned from select statement, but it only insert one row of result instead of two into sendemail table.
Can anyone help on this? thanks
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#newid int,
#Name nvarchar (50),
#Email nvarchar(50),
#ExpiryDate varchar(50)
SET #newid = 0
--Get results
SELECT #newid = b.[Id]
, #Name = c.[Name]
, #Email = b.[Email]
, #ExpiryDate = a.AccountExpiryDate
FROM Account a
LEFT JOIN Customer b ON c.[Id] = a.[CustomerId]
LEFT JOIN VIP c on c.[Id] = b.[VIPId]
WHERE a.[AccountExpiryDate] Between Cast(GETDATE() AS DATE) AND CAST(DATEADD(d, 30, GETDATE()) AS DATE)
--Insert into send email table if return results
IF(#newid > 0)
BEGIN
INSERT INTO [dbo].[SendEmail]
VALUES (
5
,'test#hotmail.com'
,'Test'
,#Email
,#Name
,NULL
,NULL
,''
,NULL
,'Test Email'
,'<p>
Hi ' + #Name + ',<br /><br />
Your account will be expired on ' + #ExpiryDate + '.
</p>'
,NULL
,NULL
,0
,GETUTCDATE()
,NULL
,1
,NULL
,1)
END
END
You should use insert . . . select:
INSERT INTO [dbo].[SendEmail] ( column list goes here )
SELECT 5, 'test#hotmail.com', 'Test', c.[Email], v.[Name], '',
'Test Email',
'<p>
Hi ' + v.[Name] + ',<br /><br />
Your account will be expired on ' + cast(a.AccountExpiryDate as varchar(255)) + '.
</p>',
0, GETUTCDATE(), 1, 1)
FROM Account a LEFT JOIN
Customer c
ON c.[Id] = a.[CustomerId] LEFT JOIN
VIP v
ON v.[Id] = c.[VIPId]
WHERE a.AccountExpiryDate Between Cast(GETDATE() AS DATE) AND CAST(DATEADD(day, 30, GETDATE()) AS DATE);
Notes:
Always explicitly list the columns you are inserting. It is very easy to make a mistake if you do not do so.
Only include the columns that actually have values; the rest will be defaulted to NULL (presumably).
Give your tables meaningful table aliases such as c for Customers, not VIP.
When using DATEADD(), explicitly write out the date part (day rather than d).
Variables are not needed for this query.
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last year.
Improve this question
Can anyone recommend how I can speed up this code and primarily the cursor? The code is an SQL Server db query that creates a trigger on INSERT, UPDATE, or DELETE. It writes a record to a changlog table identifying the type of change (I, U, or D) and then saves the old value and new value of each affected column for each row in a details table.
I want this to be generic so I can easily reuse it for any table I throw at it that has a unique column I can filter on. Writing the whole row of changes to a cloned structure audit table is not an option unfortunately.
Any help is greatly appreciated, I am not the greatest at query optimization and welcome any feedback or rewrites.. Thanks!
ALTER TRIGGER [dbo].[tbl_Address_ChangeTracking] ON [dbo].[tbl_Address]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
--SET XACT_ABORT ON
-- Get the table name of the current process
DECLARE #TableName VARCHAR(25)
SET #TableName = COALESCE(
(
SELECT SCHEMA_NAME(schema_id) + '.' + OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(##PROCID) AND
SCHEMA_NAME(sys.objects.schema_id) = OBJECT_SCHEMA_NAME(##PROCID)
), 'Unknown')
--Declare our cursor to navigate the records in inserted and deleted
DECLARE #cursorSQL AS NVARCHAR(MAX) = ''
DECLARE #PrimaryID AS VARCHAR(MAX) = ''
DROP TABLE IF EXISTS #inserted1TableTemp
DROP TABLE IF EXISTS #inserted2TableTemp
DROP TABLE IF EXISTS #deletedTableTemp
DECLARE #ourLogCursor CURSOR
--If we have a record in inserted and deleted this is an update record and we should pull from the inserted table and assume
--this is one update or many update statements
IF EXISTS
(
SELECT 1
FROM inserted
) AND
EXISTS
(
SELECT 1
FROM deleted
)
BEGIN
SELECT *
INTO #inserted1TableTemp
FROM inserted
SET #cursorSQL = 'SET #ourLogCursor = CURSOR FOR SELECT AddressID FROM #inserted1TableTemp; OPEN #ourLogCursor;'
END
--If we have an inserted record and no deleted record this is an insert and we pull from the inserted table
IF EXISTS
(
SELECT 1
FROM inserted
) AND
NOT EXISTS
(
SELECT 1
FROM deleted
)
BEGIN
DROP TABLE IF EXISTS #inserted2TableTemp
DROP TABLE IF EXISTS #inserted1TableTemp
DROP TABLE IF EXISTS #deletedTableTemp
SELECT *
INTO #inserted2TableTemp
FROM inserted
SET #cursorSQL = 'SET #ourLogCursor = CURSOR FOR SELECT AddressID FROM #inserted2TableTemp; OPEN #ourLogCursor;'
END
--If we have a deleted record and no insert record this is a deletion and we pull from the deleted table
IF NOT EXISTS
(
SELECT 1
FROM inserted
) AND
EXISTS
(
SELECT 1
FROM deleted
)
BEGIN
DROP TABLE IF EXISTS #inserted1TableTemp
DROP TABLE IF EXISTS #inserted2TableTemp
DROP TABLE IF EXISTS #deletedTableTemp
SELECT *
INTO #deletedTableTemp
FROM deleted
SET #cursorSQL = 'SET #ourLogCursor = CURSOR FOR SELECT AddressID FROM #deletedTableTemp; OPEN #ourLogCursor;'
END
--If we have a deleted record and no insert record this is a deletion and we pull from the deleted table
IF NOT EXISTS
(
SELECT 1
FROM inserted
) AND
NOT EXISTS
(
SELECT 1
FROM deleted
)
BEGIN
RETURN;
END
--Execute our dynamic SQL that sets the correct FOR SELECT statment for the cursor. Pass #ourCursorLog as an input param, and then grab the output
--so the results are available outside the scope of the executesql call
EXEC sys.sp_executesql #cursorSQL, N'#ourLogCursor CURSOR OUTPUT', #ourLogCursor OUTPUT;
FETCH NEXT FROM #ourLogCursor INTO #PrimaryID
DECLARE #xmlOld XML
DECLARE #xmlNew XML
DECLARE #SummaryID INT
SET #TableName = COALESCE(
(
SELECT SCHEMA_NAME(schema_id) + '.' + OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(##PROCID) AND
SCHEMA_NAME(sys.objects.schema_id) = OBJECT_SCHEMA_NAME(##PROCID)
), 'Unknown')
--Navigate all our rows
WHILE ##FETCH_STATUS = 0
BEGIN
DROP TABLE IF EXISTS #tmp_AddressesChangelogTrigger
DROP TABLE IF EXISTS #tmp_AddressesChangelogTriggerXML1
DROP TABLE IF EXISTS #tmp_AddressesChangelogTriggerXML2
DROP TABLE IF EXISTS #tmp_AddressesChangelogTriggerXMLsWithDifferences
--Get the deleted and inserted records as xml for comparison against each other
SET #xmlNew =
(
SELECT *
FROM deleted AS [TABLE]
WHERE AddressID = #PrimaryID
ORDER BY AddressID FOR XML AUTO, ELEMENTS
)
SET #xmlOld =
(
SELECT *
FROM inserted AS [TABLE]
WHERE AddressID = #PrimaryID
ORDER BY AddressID FOR XML AUTO, ELEMENTS
)
CREATE TABLE #tmp_AddressesChangelogTriggerXML1
(
NodeName VARCHAR(MAX), Value VARCHAR(MAX)
)
CREATE TABLE #tmp_AddressesChangelogTriggerXML2
(
NodeName VARCHAR(MAX), Value VARCHAR(MAX)
)
--Extract the values and column names
INSERT INTO #tmp_AddressesChangelogTriggerXML2( NodeName, Value )
--Throw the XML into temp tables with the column name and value
SELECT N.value( 'local-name(.)', 'nvarchar(MAX)' ) AS NodeName, N.value( 'text()[1]', 'nvarchar(MAX)' ) AS VALUE
FROM #xmlNew.nodes( '/TABLE/*' ) AS T(N)
INSERT INTO #tmp_AddressesChangelogTriggerXML1( NodeName, Value )
SELECT N.value( 'local-name(.)', 'nvarchar(MAX)' ) AS NodeName, N.value( 'text()[1]', 'nvarchar(MAX)' ) AS VALUE
FROM #xmlOld.nodes( '/TABLE/*' ) AS T(N)
--Get the differences into a temp table
SELECT *
INTO #tmp_AddressesChangelogTriggerXMLsWithDifferences
FROM
(
SELECT COALESCE(A.NodeName, B.NodeName) AS NodeName, B.Value AS OldValue, A.Value AS NewValue
FROM #tmp_AddressesChangelogTriggerXML1 AS A
FULL OUTER JOIN #tmp_AddressesChangelogTriggerXML2 AS B ON A.NodeName = B.NodeName
WHERE A.Value <> B.Value
) AS tmp
--If anything changed thhen start our write statments
IF
(
SELECT COUNT(*)
FROM #tmp_AddressesChangelogTriggerXMLsWithDifferences
) > 0
BEGIN
BEGIN TRY
-- Now create the Summary record
--BEGIN TRANSACTION WRITECHANGELOGRECORDS
INSERT INTO TableChangeLogSummary( ID, ModifiedDate, ChangeType, TableName )
--Get either insert, or if no insert value, get the delete value
--Set the update type, I, D, U
--Compare values with a full outer join
--Filter on the ID we are on in the CURSOR
SELECT COALESCE(I.AddressID, D.AddressID), GETDATE(),
CASE
WHEN D.AddressID IS NULL THEN 'I'
WHEN I.AddressID IS NULL THEN 'D'
ELSE 'U'
END, #TableName
FROM inserted AS I
FULL OUTER JOIN deleted AS D ON I.AddressID = D.AddressID
WHERE( I.AddressID = #PrimaryID OR
I.AcesAddressID IS NULL
) AND
( D.AddressID = #PrimaryID OR
D.AcesAddressID IS NULL
)
--Get the last summary id that was inserted so we can use it in the detail record
SET #SummaryID = (SELECT IDENT_CURRENT('TableChangeLogSummary'))
--Insert our
INSERT INTO TableChangeLogDetail( SummaryID, ColumnName, OldValue, NewValue )
SELECT #SummaryID, T.NodeName, T.OldValue, T.NewValue
FROM #tmp_AddressesChangelogTriggerXMLsWithDifferences AS T
--COMMIT TRANSACTION WRITECHANGELOGRECORDS
--PRINT 'RECORD WRITTEN'
END TRY
BEGIN CATCH
DECLARE #errorXML XML
SET #errorXML = (SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_STATE() AS ErrorState, ERROR_SEVERITY() AS ErrorSeverity, ERROR_PROCEDURE() AS ErrorProcedure, ERROR_LINE() AS ErrorLine, ERROR_MESSAGE() AS ErrorMessage FOR XML RAW)
DECLARE #errorXMLText NVARCHAR(MAX) = ''
SET #errorXMLText = (SELECT CAST(#errorXML AS NVARCHAR(MAX)))
RAISERROR(#errorXMLText, 16, 1) WITH NOWAIT
END CATCH
END
--Go to the next record and process
FETCH NEXT FROM #ourLogCursor INTO #PrimaryID
END
CLOSE #ourLogCursor
DEALLOCATE #ourLogCursor
END
Acknowledging the recommendation for using change data tracking and caution against putting too much logic into triggers, the following is a refactoring (and some outright rewriting) of your change capture logic.
The updated logic makes a single pass through the data, handing all affected records at once. Given the requirements, I think it is pretty close to optimal, but there may still be room for improvements. The conversion to and from XML likely adds a significant bit of overhead. The alternative would be to dynamically generate and apply custom triggers for each table that explicitly reference all of the data columns individually to get the details and UNION them together.
I also refined the value comparison to better handle nulls, case sensitivity, and potential trailing space changes.
The code below is not in the form of a trigger, but in a form suitable for stand-alone testing. I figured you (and any others who may be interested) would want to test Once checked out, you should be able to retrofit it back into your trigger.
Note that this is not a 100% generalized solution. Some column types may not be supported. The logic currently assumes a single column primary key of type integer. Changes would be required to handle deviations from these (and possibly some currently unidentified) constraints.
-- Simulated change log tables
DECLARE #TableChangeLogSummary TABLE (ID INT IDENTITY(1,1), KeyValue INT NOT NULL, ModifiedDate DATETIME NOT NULL, ChangeType CHAR(1) NOT NULL, TableName NVARCHAR(1000) NOT NULL )
DECLARE #TableChangeLogDetails TABLE (ID INT IDENTITY(1,1), SummaryID int NOT NULl, ColumnName NVARCHAR(1000) NOT NULL, OldValue NVARCHAR(MAX), NewValue NVARCHAR(MAX))
-- Simulated system defined inserted/deleted tables
DECLARE #inserted TABLE (ID INTEGER, Value1 NVARCHAR(100), Value2 BIT, Value3 FLOAT)
DECLARE #deleted TABLE (ID INTEGER, Value1 NVARCHAR(100), Value2 BIT, Value3 FLOAT)
-- Test data
INSERT #inserted
VALUES
(1, 'AAA', 0, 3.14159), -- Insert
(2, 'BBB', 1, null), -- Mixed updates including null to non-null and non-null to null
(3, 'CCC', 0, 0), -- Trailing space change
(4, 'DDD', null, 1.68), -- No changes
(5, '', 0, null), -- No changes with blanks and nulls
(6, null, null, null), -- No changes all nulls
(7, null, null, null) -- Insert all nulls (summary with key, but no details will be logged)
INSERT #deleted
VALUES
(2, 'bbb', null, 2.73),
(3, 'CCC ', 0, 0),
(4, 'DDD', null, 1.68),
(5, '', 0, null),
(6, null, null, null),
(8, null, null, null), -- Delete all null values (summary with key, but no details will be logged)
(9, 'ZZZ', 999, 999.9) -- Delete non-nulls
--- Now the real work begins...
-- Set table and information. Assumes table has exactly one PK column. Later logic assumes an INT.
DECLARE #TableName NVARCHAR(1000) = 'MyTable' -- To be extracted from the parent object of the trigger
DECLARE #KeyColumnName SYSNAME = 'ID' -- This can be fixed if known or derived on the fly from the primary key definition
-- Extract inserted and/or deleted data
DECLARE #InsertedXml XML = (
SELECT *
FROM #inserted
FOR XML PATH('inserted'), TYPE
)
DECLARE #DeletedXml XML = (
SELECT *
FROM #deleted
FOR XML PATH('deleted'), TYPE
)
-- Parse and reassange the captured key and data values
DECLARE #TempDetails TABLE(
KeyValue INT NOT NULL,
ChangeType CHAR(1) NOT NULL,
ColumnName VARCHAR(1000) NOT NULL,
IsKeyColumn BIT NOT NULL,
NewValue NVARCHAR(MAX),
OldValue NVARCHAR(MAX))
INSERT #TempDetails
SELECT
KeyValue = COALESCE(I.KeyValue, D.KeyValue),
ChangeType = CASE WHEN D.KeyValue IS NULL THEN 'I' WHEN I.KeyValue IS NULL THEN 'D' ELSE 'U' END,
ColumnName = COALESCE(I.ColumnName, D.ColumnName),
IsKeyColumn = K.IsKeyColumn,
NewValue = I.Value,
OldValue = D.Value
FROM (
SELECT K.KeyValue, C.ColumnName, C.Value
FROM #InsertedXml.nodes( '/inserted' ) R(Row)
CROSS APPLY (
SELECT KeyValue = C.Col.value('text()[1]', 'int')
FROM R.Row.nodes( './*' ) C(Col)
WHERE C.Col.value( 'local-name(.)', 'nvarchar(MAX)' ) = #KeyColumnName
) K
CROSS APPLY (
SELECT ColumnName = C.Col.value('local-name(.)', 'nvarchar(MAX)'), Value = C.Col.value('text()[1]', 'nvarchar(MAX)')
FROM R.Row.nodes( './*' ) C(Col)
) C
) I
FULL OUTER JOIN (
SELECT K.KeyValue, C.ColumnName, C.Value
FROM #DeletedXml.nodes( '/deleted' ) R(Row)
CROSS APPLY (
SELECT KeyValue = C.Col.value('text()[1]', 'int')
FROM R.Row.nodes( './*' ) C(Col)
WHERE C.Col.value( 'local-name(.)', 'nvarchar(MAX)' ) = #KeyColumnName
) K
CROSS APPLY (
SELECT ColumnName = C.Col.value('local-name(.)', 'nvarchar(MAX)'), Value = C.Col.value('text()[1]', 'nvarchar(MAX)')
FROM R.Row.nodes( './*' ) C(Col)
) C
) D
ON D.KeyValue = I.KeyValue
AND D.ColumnName = I.ColumnName
CROSS APPLY (
SELECT IsKeyColumn = CASE WHEN COALESCE(I.ColumnName, D.ColumnName) = #KeyColumnName THEN 1 ELSE 0 END
) K
WHERE ( -- We need to be careful about edge cases here
(I.Value IS NULL AND D.Value IS NOT NULL)
OR (I.Value IS NOT NULL AND D.Value IS NULL)
OR I.Value <> D.Value COLLATE Latin1_General_Bin -- Precise compare (case and accent sensitive)
OR DATALENGTH(I.Value) <> DATALENGTH(D.Value) -- Catch trailing space cases
OR K.IsKeyColumn = 1
)
-- Get rid of updates with no changes, but keep key-only inserts or deletes
DELETE T
FROM #TempDetails T
WHERE T.IsKeyColumn = 1
AND T.ChangeType = 'U'
AND NOT EXISTS (
SELECT *
FROM #TempDetails T2
WHERE T2.KeyValue = T.KeyValue
AND T2.IsKeyColumn = 0
)
-- Local table to capture and link SummaryID between the summary and details tables
DECLARE #CaptureSummaryID TABLE (SummaryID int, KeyValue INT NOT NULL)
-- Insert change summary and capture the assigned Summary ID via the OUTPUT clause
INSERT INTO #TableChangeLogSummary (KeyValue, ModifiedDate, ChangeType, TableName)
OUTPUT INSERTED.id, INSERTED.KeyValue INTO #CaptureSummaryID
SELECT T.KeyValue, ModifiedDate = GETDATE(), T.ChangeType, TableName = #TableName
FROM #TempDetails T
WHERE T.IsKeyColumn = 1
ORDER BY T.KeyValue -- Optional, but adds consistancy
-- Insert change details
INSERT INTO #TableChangeLogDetails (SummaryID, ColumnName, OldValue, NewValue)
SELECT S.SummaryID, T.ColumnName, T.OldValue, T.NewValue
FROM #CaptureSummaryID S
JOIN #TempDetails T ON T.KeyValue = S.KeyValue
WHERE T.IsKeyColumn = 0
ORDER BY T.ColumnName -- Optional, but adds consistancy
-- View test results
SELECT 'Change Log:', *
FROM #TableChangeLogSummary S
LEFT JOIN #TableChangeLogDetails D ON D.SummaryID = S.ID
ORDER BY S.ID, D.ID
please forgive my inexperience, I hope this isn't too dumb of a question, I'm stuck and have no where else to turn. I'll keep it to the point:
I'm trying to gather payroll data with the results like so:
The issue I have is the variable number of columns. I will be given a date range and are required to return an attendance record for each day in the given range, or a null value if no data is present. I'm using WebAPI as middle tier so I have the ability to perform further data manipulation to achieve this result.
My tables are as follows:
I can't be the first person who needs this done, any articles/posts or anything that would help me accomplish this? Even pseudo code would help; anything!
Thanks a million in advnace!
This is what I've been able to come up with but I'm not even sure if its doable:
-- convert date range into days of month
-- to ensure null values are included in data??
DECLARE #intFlag INT = 0;
DECLARE #numberOfDays INT = DATEDIFF(DAY, #startDate, #endDate);
DECLARE #TMP TABLE (DaysOfMonth date)
WHILE (#intFlag <= #numberOfDays)
BEGIN
INSERT INTO #TMP VALUES (DATEADD(DAY, #intFlag, #startDate));
SET #intFlag = #intFlag + 1
END
-- select days in given data range so c# app can build header row
-- would it help if I pivot this data?
SELECT
DaysOfMonth
FROM
#TMP
ORDER BY
DaysOfMonth
-- get a count for number of people
DECLARE #count INT = 0;
DECLARE #TMPPPL TABLE (Id int identity(1,0), PId Int)
INSERT INTO
#TMPPPL
SELECT
p.PersonId
FROM
dbo.People p
JOIN
dbo.UserTypes ut on p.UserType_UserTypeId = ut.UserTypeId and (ut.Code = 'caregiver' or ut.Code = 'director')
DECLARE #numberOfPeople INT = (SELECT COUNT(1) FROM #TMPPPL)
-- create and execute sproc to return row of data for each person
WHILE (#count <= #numberOfPeople)
BEGIN
-- STUCK HERE, This obviously won't work but what else can I do?
EXEC GetPersonAttendanceHours #personId, #startDate, #endDate;
SET #count = #count + 1
END
This was interesting. I think this will do what you're looking for. First test data:
CREATE TABLE people (PersonID int, Name varchar(30))
INSERT INTO people (PersonID, Name)
SELECT 1, 'Kelly'
UNION ALL SELECT 2, 'Dave'
UNION ALL SELECT 3, 'Mike'
CREATE TABLE attendances (PersonID int, SignIn datetime, SignOut datetime)
INSERT INTO attendances (PersonID, SignIn, SignOut)
SELECT 1, '1-Feb-2015 08:00', '1-Feb-2015 09:00'
UNION ALL SELECT 1, '1-Feb-2015 12:00', '1-Feb-2015 12:30'
UNION ALL SELECT 2, '2-Feb-2015 08:00', '2-Feb-2015 08:15'
UNION ALL SELECT 1, '3-Feb-2015 08:00', '3-Feb-2015 09:00'
UNION ALL SELECT 1, '4-Feb-2015 08:00', '4-Feb-2015 08:30'
UNION ALL SELECT 2, '4-Feb-2015 08:00', '4-Feb-2015 10:00'
UNION ALL SELECT 2, '6-Feb-2015 12:00', '6-Feb-2015 15:00'
UNION ALL SELECT 3, '6-Feb-2015 15:00', '6-Feb-2015 17:00'
UNION ALL SELECT 3, '8-Feb-2015 10:00', '8-Feb-2015 12:00'
Then a dynamic query:
DECLARE #startDate DATETIME='1-Feb-2015'
DECLARE #endDate DATETIME='9-Feb-2015'
DECLARE #numberOfDays INT = DATEDIFF(DAY, #startDate, #endDate)
declare #dayColumns TABLE (delta int, colName varchar(12))
-- Produce 1 row for each day in the report. Note that this is limited by the
-- number of objects in sysobjects (which is about 2000 so it's a high limit)
-- Each row contains a delta date offset, #startDate+delta gives each date to report
-- which is converted to a valid SQL column name in the format colYYYYMMDD
INSERT INTO #dayColumns (delta, colName)
SELECT delta, 'col'+CONVERT(varchar(12),DATEADD(day,delta,#startDate),112) as colName from (
select (ROW_NUMBER() OVER (ORDER BY sysobjects.id))-1 as delta FROM sysobjects
) daysAhead
WHERE delta<=#numberOfDays
-- Create a comma seperated list of columns to report
DECLARE #cols AS NVARCHAR(MAX)= ''
SELECT #cols=CASE WHEN #cols='' THEN #cols ELSE #cols+',' END + colName FROM #dayColumns ORDER BY delta
DECLARE #totalHours AS NVARCHAR(MAX)= ''
SELECT #totalHours=CASE WHEN #totalHours='' THEN '' ELSE #totalHours+' + ' END + 'ISNULL(' + colName +',0)' FROM #dayColumns ORDER BY delta
-- Produce a SQL statement which outputs a variable number of pivoted columns
DECLARE #query AS NVARCHAR(MAX)
SELECT #query=
'declare #days TABLE (reportDay date, colName varchar(12))
INSERT INTO #days (reportDay, colName)
SELECT DATEADD(day,Delta,'''+CONVERT(varchar(22),#startDate,121)+'''), ''col''+CONVERT(varchar(12),DATEADD(day,delta,'''+CONVERT(varchar(22),#startDate,121)+'''),112) as colName from (
select (ROW_NUMBER() OVER (ORDER BY sysobjects.id))-1 as Delta FROM sysobjects
) daysAhead
WHERE Delta<='+CAST(#numberOfDays as varchar(10))+'
SELECT p.Name, pivotedAttendance.*,'+#totalHours+' as totalHours FROM (
SELECT * FROM (
select p.PersonID, d.colName, CAST(DATEDIFF(MINUTE, a.SignIn, a.SignOut)/60.0 as decimal(5,1)) as hrsAttendance
from #days d
CROSS JOIN people p
LEFT OUTER JOIN attendances a ON a.PersonID=p.PersonID AND CAST(a.SignOut as DATE)=d.reportDay
) as s
PIVOT (
SUM(hrsAttendance) FOR colName in ('+#cols+')
) as pa
) as pivotedAttendance
INNER JOIN people p on p.PersonID=pivotedAttendance.PersonID'
-- Run the query
EXEC (#query)
Which produces data in a similar format to your example, with all of the days in the report range and a row for each person. From the above I see:
For presentation purposes you should be able to convert the column name to a display-able date (just parse the YYYYMMDD out of the column name). The date can't be used as the column name directly as it produces an invalid column name.
SQL Fiddle example here.
This is a variation on a theme that I've done in order to display schedules or attendance. I expect something similar should work with your report. Here is the beginning of your stored procedure:
DECLARE #iDay INT = 0;
DECLARE #countDays INT = DATEDIFF(DAY, #startDate, #endDate);
DECLARE #tempDates TABLE ([tempDate] DATE);
DECLARE #filterDates NVARCHAR;
WHILE (#iDay <= #countDays)
BEGIN
INSERT INTO #tempDates VALUES (DATEADD(DAY, #iDay, #startDate));
SET #iDay = #iDay + 1;
END;
SELECT #filterDates = STUFF(
(SELECT N''',''' + CONVERT(NVARCHAR, [tempDate], 103) FROM #tempDates FOR XML PATH('')),
1,
2,
''
);
You were on the right track with your suggestion. The next query gets your data before you PIVOT it.
SELECT [People].[Person_PersonID], [tempDates].[tempDate], [Attendances].[SignIn], [Attendances].[SignOut],
MIN([Attendances].[SignOut], DATEADD(DAY, 1, [tempDates].[tempDate]))
- MAX([Attendances].[SignIn], [tempDates].[tempDate]) * 24 AS [numHours]
FROM [People]
CROSS JOIN #tempDates [tempDates]
LEFT JOIN [Attendances]
ON (
([Attendances].[SignIn] < DATEADD(DAY, 1, [tempDates].[tempDate]))
AND ([Attendances].[SignOut] > [tempDates].[tempDate])
);
Once we're satisfied with the results of the previous query, we substitute it with a query using PIVOT, which should look something like this.
SELECT *
FROM (
SELECT [People].[PersonID], [tempDates].[tempDate], [Attendances].[SignIn], [Attendances].[SignOut],
MIN([Attendances].[SignOut], DATEADD(DAY, 1, [tempDates].[tempDate]))
- MAX([Attendances].[SignIn], [tempDates].[tempDate]) * 24 AS [numHours]
FROM [People]
CROSS JOIN #tempDates [tempDates]
LEFT JOIN [Attendances]
ON (
([Attendances].[SignIn] < DATEADD(DAY, 1, [tempDates].[tempDate]))
AND ([Attendances].[SignOut] > [tempDates].[tempDate])
)
) AS [DatedAttendance]
PIVOT (
SUM([numHours]) FOR ([tempDate] IN (#filterDates))
) AS [PivotAttendance]
ORDER BY [PersonID]
I need to evaluate a formula in sql server 2008
Table 1 contains
Entity Value
A 2424053.500000
B 1151425.412500
C 484810.700000
Table 2 contains
Entity Formula
A (2100*(1-0.0668)*24*mday*10)
B (1000*(1-0.0575)*24*mday*10)
C (1260*(1-0.09)*24*mday*10)
Where mday is number of days taken from user. Data type of Formula is a string.
I need to calculate the output of value/formula for each entity
can you provide me the query for the same
Example solution for SQL Server 2008, adjust as required...
IF EXISTS (SELECT * FROM sys.tables WHERE object_id = object_id('EntityValue'))
BEGIN
DROP TABLE EntityValue;
END;
CREATE TABLE EntityValue
(
Id CHAR(1),
mdayValue DECIMAL(13, 6)
)
INSERT INTO EntityValue
VALUES ('1', 2424053.500000)
, ('2', 1151425.412500)
, ('3', 484810.700000)
IF EXISTS (SELECT * FROM sys.tables WHERE object_id = object_id('EntityFormula'))
BEGIN
DROP TABLE EntityFormula;
END;
CREATE TABLE EntityFormula
(
Id CHAR(1),
Formula NVARCHAR(MAX)
)
INSERT INTO EntityFormula
VALUES ('1', '(2100*(1-0.0668)*24*mday*10)')
, ('2', '(1000*(1-0.0575)*24*mday*10)')
, ('3', '(1260*(1-0.09)*24*mday*10)')
DECLARE #FormulaTable AS TABLE
(
RowId INT IDENTITY(1,1)
,Formula NVarchar(max)
);
INSERT INTO #FormulaTable (Formula)
SELECT Formula = REPLACE(eFormula.Formula, 'mday', CAST(eValue.mdayValue AS NVARCHAR(MAX)))
FROM EntityFormula AS eFormula
INNER JOIN EntityValue AS eValue ON eValue.ID = eFormula.ID;
DECLARE #TSql NVarchar(max), #CurrentRowId INT;
SET #CurrentRowId = 1;
WHILE(1=1)
BEGIN
SELECT #TSql = 'SELECT ' + Formula
FROM #FormulaTable
WHERE RowID = #CurrentRowId
IF(##ROWCOUNT = 0)
BEGIN
BREAK;
END
EXEC sp_executesql #Tsql
SET #CurrentRowId = #CurrentRowId + 1;
END
I have a SQL table where I need to update Date field of multiple Users. The primary key (userId) field is int. Now I am sending the Date values in a comma separated string (like "10/06/2013,12/05/2013,16/07/2013") and corresponding userId values also in a comma separated string (like "1001,1002,1005").
How can I update all relevant Users in my stored procedure? Or should I send the userIds and Dates in any other way?
try this
DECLARE #dates VARCHAR(8000) = '10/06/2013,12/05/2013,16/07/2013'
DECLARE #userid VARCHAR(8000) = '1001,1002,1005'
DECLARE #t1 TABLE
(
dates VARCHAR(50) ,
userid VARCHAR(50)
)
WHILE CHARINDEX(',', #dates) > 0
BEGIN
INSERT INTO #t1
( dates ,
userid
)
VALUES ( SUBSTRING(#dates, 1, ( CHARINDEX(',', #dates) - 1 )) ,
SUBSTRING(#userid, 1, ( CHARINDEX(',', #userid) - 1 ))
)
SET #dates = SUBSTRING(#dates, CHARINDEX(',', #dates) + 1,
LEN(#dates))
SET #userid = SUBSTRING(#userid, CHARINDEX(',', #userid) + 1,
LEN(#userid))
END
INSERT INTO #t1
( dates, userid )
VALUES ( #dates, #userid )
SELECT *
FROM #t1 AS t
UPDATE LMS.dbo.Employee
SET JoiningDate = ( SELECT dates
FROM #t1 AS t
WHERE LMS.dbo.Employee.Code = t.userid
)
It would be good if you send a XML including date and user id. That is comparatively faster as well.
That is what prepared statements are for. Create a prepared UPDATE statement. And then execute it with different values as many times as you need.
I would like to create a trigger based on a column but only for those records that end in _ess. How can I set up an audit trigger to do this?
Here is the current trigger but it just checks for all changes to username, whereas I just want it to check when username is updated to or from a username ending in _ess.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[AUDIT_UPD_HRPERSONS_USERNAME] ON [dbo].[HRPersons] FOR UPDATE NOT FOR REPLICATION As
BEGIN
DECLARE
#OperationNum int,
#DBMSTransaction VARCHAR(255),
#OSUSER VARCHAR(50),
#DBMSUSER VARCHAR(50),
#HostPhysicalAddress VARCHAR(17),
#contexto varchar(128),
#ApplicationModifierUser varchar(50),
#SessionInfo_OSUser varchar(50),
#HostLogicalAddress varchar(30)
Set NOCOUNT On
IF ##trancount>0
BEGIN
EXECUTE sp_getbindtoken #DBMSTransaction OUTPUT
END
ELSE BEGIN
SET #DBMSTransaction = NULL
END
IF PatIndex( '%\%',SUSER_SNAME()) > 0
BEGIN
set #OSUSER = SUSER_SNAME()
set #DBMSUSER = NULL
END
ELSE BEGIN
SET #OSUSER = NULL
SET #DBMSUSER = SUSER_SNAME()
END
set #HostPhysicalAddress = (SELECT net_address FROM master..sysprocesses where spid=##spid )
set #HostPhysicalAddress = substring (#HostPhysicalAddress,1,2) + '-' + substring (#HostPhysicalAddress,3,2) + '-' + substring (#HostPhysicalAddress,5,2) + '-' + substring (#HostPhysicalAddress,7,2) + '-' + substring (#HostPhysicalAddress,9,2) + '-' + substring (#HostPhysicalAddress,11,2)
SELECT #contexto=CAST(context_info AS varchar(128)) FROM master..sysprocesses WHERE spid=##SPID
IF (PatIndex( '%APPLICATION_USER=%',#contexto) is not null) and (PatIndex( '%APPLICATION_USER=%',#contexto) > 0)
set #ApplicationModifierUser=substring(ltrim(substring(#contexto,PatIndex( '%APPLICATION_USER=%',#contexto)+17,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%APPLICATION_USER=%',#contexto)+17,128) ) ) - 1 )
ELSE
set #ApplicationModifierUser=NULL
IF (PatIndex( '%OS_USER=%',#contexto) is not null) and ( PatIndex( '%OS_USER=%',#contexto)>0 )
set #SessionInfo_OSUser=substring(ltrim(substring(#contexto,PatIndex( '%OS_USER=%',#contexto)+8,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%OS_USER=%',#contexto)+8,128) ) ) - 1 )
ELSE
set #SessionInfo_OSUser=NULL
IF (PatIndex( '%LOGICAL_ADDRESS=%',#contexto) is not null) and (PatIndex( '%LOGICAL_ADDRESS=%',#contexto)>0)
set #HostLogicalAddress=substring(ltrim(substring(#contexto,PatIndex( '%LOGICAL_ADDRESS=%',#contexto)+16,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%LOGICAL_ADDRESS=%',#contexto)+16,128) ) ) - 1 )
ELSE
set #HostLogicalAddress=NULL
INSERT INTO AuditedOperations ( Application, Object, OperationType, ModifiedDate, ApplicationModifierUser, OSModifierUser, DBMSModifierUser, Host, HostLogicalAddress, HostPhysicalAddress, DBMSTransaction)
VALUES (APP_NAME(), 'HRPERSONS', 'U', GETDATE(), #ApplicationModifierUser, #OSUSER, #DBMSUSER, HOST_NAME(), #HostLogicalAddress, #HostPhysicalAddress, #DBMSTransaction)
Set #OperationNum = ##IDENTITY
INSERT INTO AuditedRows (OperationNum, RowPK)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar))
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
INSERT INTO AuditedRowsColumns (OperationNum, RowPK, ColumnName, ColumnAudReg, OldValue, NewValue)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar)), 'USERNAME','A', CONVERT( VARCHAR(3500),DELETED.USERNAME), CONVERT( VARCHAR(3500),INSERTED.USERNAME)
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
END
GO
Just add this:
INSERT INTO AuditedRows (OperationNum, RowPK)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar))
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
-- Restrict it to only those where the username is changing from or to %_ess
WHERE (deleted.username like '%_ess' or inserted.username like '%_ess')
INSERT INTO AuditedRowsColumns (OperationNum, RowPK, ColumnName, ColumnAudReg, OldValue, NewValue)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar)), 'USERNAME','A', CONVERT( VARCHAR(3500),DELETED.USERNAME), CONVERT( VARCHAR(3500),INSERTED.USERNAME)
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
-- Restrict it to only those where the username is changing from or to %_ess
WHERE (deleted.username like '%_ess' or inserted.username like '%_ess')