change data capture - monitor single column - sql

Afternoon all,
I've enabled CDC on a particular table (code below) however I'm wondering if it's possible to track changes on LocationTypeID column only, yet record the remaining columns of the row within the system table?
EXEC sys.sp_cdc_enable_table
#source_schema = N'LocationManagementOperational',
#source_name = N'FunctionalLocation',
#role_name = N'Public',
#captured_column_list = N'[ID], [ParentID], [LocationTypeID], [Path], [Elevation], [InclinationPositionX], [InclinationPositionY], [InclinationPercentage], [InclinationAzimuth], [DispatchSignId], [MaterialGeometry], [DynamicDigLimitInclination], [LastUpdate], [RecordStatus]',
#index_name = N'UQ_FunctionalLocation',
#supports_net_changes = 1

Related

MERGE statement for SCD type 2 is creating duplicate rows?

I have the below MERGE statement that I built to implement SCD type 2 on my SQL Server DB however if you look at the attached image, this sometimes inserts multiple duplicate rows which is incorrect - this should be one latest row per meterkey/meterserialnumber.
Is there anything obvious in the code I need to amend? It's weird because if I add a test row of data and run the merge, this works fine.. its just the occasional rows where it seems to be doing this.
DECLARE #DateNow int = CONVERT(INT, CONVERT(VARCHAR(8), GETDATE(), 112))
IF Object_id('tempdb..#meterkeysinsert') IS NOT NULL
DROP TABLE #meterkeysinsert;
CREATE TABLE #meterkeysinsert
(
MeterKey int,
change VARCHAR(10)
);
MERGE INTO [DIM].[MeterDetails] AS Target
using dbo.test AS Source
ON Target.meterkey = source.meterkey
and target.MeterSerialNumber = Source.MeterSerialNumber
and target.islatest = 1
WHEN matched THEN
UPDATE SET Target.islatest = 0,
Target.todatekey = #Datenow
WHEN NOT matched BY target THEN
INSERT ( meterkey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,[FromDatekey]
,[ToDatekey]
,[IsLatest])
VALUES (Source.meterkey
,Source.[MeterSerialNumber]
,Source.[lguf]
,Source.[electricityMetertype]
,Source.[profileType]
,Source.[timeSwitchCode]
,Source.[lineLossFactorId]
,Source.[standardSettlementConfiguration]
,Source.[energisationStatus]
,Source.[DateSpecifiedKey]
,Source.[distributorId]
,Source.[gspid]
,#Datenow
,NULL
,1 --IsRowCurrent
)
output Source.meterkey,
$action
INTO #meterkeysinsert;
INSERT INTO [DIM].[MeterDetails]
(MeterKey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,[FromDateKey]
,[ToDateKey]
,[IsLatest])
SELECT A.MeterKey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,#Datenow
,null
,1
FROM dbo.test a
INNER JOIN #meterkeysinsert CID
ON a.MeterKey = CID.Meterkey
AND CID.change = 'UPDATE'
`

SQL, How to take into account previously populated records when populating a sequential number ID for a field in a nightly script

I am trying to include a clause in a script that runs nightly that will populate a three digit number into a field if that record meets a set of conditions. I will include the script that I have written for this below but I do not know how to account for the numbers that will have been populated on previous nights and keep the new numbers to be populated in sequential order. The numbers must start at 100 and go up by 1 each time a new record is found that meets the conditions.
All help is appreciated.
My current script:
DECLARE #myVar NVarchar(50)
SET #myVar = 99
UPDATE Database1..Thing
SET #myVar = Thing_Number_NEW = #myVar + 1
WHERE (Thing_Number = '' OR Thing_Number IS NULL)
AND Thing_Number_Needed = 'Yes'
AND Symbology IN (2, 3, 55, 66)
AND Thing_Number_New IS NULL
AND Last_edited_user is not null
Is this what you want?
UPDATE Database1..Thing
SET Thing_Number_NEW = COALESCE(n.max_Thing_Number_NEW + 1, 100)
FROM (SELECT MAX(Thing_Number_NEW) as max_Thing_Number_NEW FROM Database1..Thing) n
WHERE (Thing_Number = '' OR Thing_Number IS NULL)
Thing_Number_Needed = 'Yes' AND
Symbology IN (2, 3, 55, 66) AND
Thing_Number_New IS NULL AND
Last_edited_user is not null;

SQL Where with Binary(n) column

I have a stored procedure:
ALTER PROCEDURE [dbo].[spUpdateOrInsertNotification]
#ContentJsonHash BINARY(32)
AS
DECLARE #NotificationId INT;
SET #NotificationId = (SELECT #NotificationId
FROM dbo.tblNotifications n
WHERE n.ContentJsonHash = #ContentJsonHash);
IF #NotificationId IS NOT NULL
BEGIN
-- Increment Count
END
ELSE
BEGIN
-- Insert new row.
END
It's supposed to check if the Hash already exists and if it does, increment the count for the row, otherwise insert the row. However, it never finds the Hash and the corresponding NotificationId. NotificationId is always null.
If I run it twice, passing it the same data (a C# array byte[32]). It never finds the same NotificationId and I end up with duplicate entries being put in.
e.g.
NotificationId | ContentJsonHash
9 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
10 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
Can I not do comparisons on Binary(n) fields like this WHERE n.ContentJsonHash = #ContentJsonhash ?
The C# code:
using (var conn = new SqlConnection(Sql.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = new SqlCommand(Sql.SqlUpdateOrInsertNotification, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Source", notificationMessage.Source);
cmd.Parameters.AddWithValue("#Sender", notificationMessage.Sender);
cmd.Parameters.AddWithValue("#NotificationType", notificationMessage.NotificationType);
cmd.Parameters.AddWithValue("#ReceivedTimestamp", notificationMessage.Timestamp);
cmd.Parameters.AddWithValue("#ContentJSon", notificationMessage.NotificationContent);
cmd.Parameters.AddWithValue("#ContentJsonHash", notificationMessage.ContentHashBytes);
await cmd.ExecuteNonQueryAsync();
}
}
I've also tried calling the stored procedure from SQL like this:
exec dbo.spUpdateOrInsertNotification 'foo', 'bar', 0,
'2017-12-05 15:23:41.207', '{}',
0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
Calling this twice returns 2 rows :(
I can do this, which works, hard coding the binary field I want to check
select *
from dbo.tblNotifications
where ContentJsonhash = 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
Binary comparisons can be tricky. If you are using a true binary column, I believe length also comes into play. So even if those bytes are the same, and the lengths differ, the comparison would be false. An easy way is to convert these to strings:
alter procedure [dbo].[spUpdateOrInsertNotification]
#ContentJsonHash BINARY(32)
AS
DECLARE #NotificationId INT;
SET #NotificationId = (SELECT NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
IF #NotificationId IS NOT NULL
BEGIN
-- Increment Count
END
ELSE
BEGIN
-- Insert new row.
END
I had an # where I shouldn't have had an ampersand.
SET #NotificationId = (SELECT #NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
Should be
SET #NotificationId = (SELECT NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
I feel so stupid for not noticing this sooner :(

"Insert Into Select" writing to table but contains sub-query reading from same table

I am adding records into my table "SampleTestLimits" using an "Insert Into Select", but which also has a sub-query reading from the same table to perform a count for me.
I don't think the sub-query is seeing the earlier records added by my "Insert Into Select". It's the same for Oracle and SQL Server. The code for SQL Server is shown below (my sub-query begins with "SELECT COALESCE...").
I have another stored procedure which does work in a similar situation.
Would appreciate it if anybody could tell if what I'm doing is a no no.
ALTER PROCEDURE [dbo].[CreateSampleTestLimits]
#SampleCode as NVARCHAR(80),
#TestPosition as smallint,
#TestCode NVARCHAR(20),
#TestVersion smallint,
#EnterDate as integer,
#EnterTime as smallint,
#EnterUser as NVARCHAR(50)
AS
BEGIN
INSERT INTO SampleTestLimits
([AuditNumber]
,[LimitNumber]
,[ComponentRow]
,[ComponentColumn]
,[ComponentName]
,[TestPosition]
,[SampleCode]
,[AuditFlag]
,[LimitSource]
,[LimitType]
,[UpperLimitEntered]
,[UpperLimitValue]
,[LowerLimitEntered]
,[LowerLimitValue]
,[LimitTextColour]
,[LimitPattern]
,[LimitForeColour]
,[LimitBackColour]
,[CreatedDate]
,[CreatedTime]
,[CreatedUser]
,[LimitText]
,[FilterName]
,[deleted]
,IsRuleBased)
SELECT 1 --starting auditnumber
,(SELECT COALESCE(MAX(LimitNumber), 0) + 1 AS NextLimitNumber FROM SampleTestLimits WHERE SampleCode=#SampleCode AND TestPosition=#TestPosition AND ComponentRow=1 AND ComponentColumn=1 AND AuditFlag=0) -- TFS bug# 3952: Calculate next limit number.
,ComponentRow
,ComponentColumn
,(select ComponentName from TestComponents TC where TC.TestCode=#TestCode and TC.ComponentColumn=TestLimits.ComponentColumn and TC.ComponentRow = TestLimits.ComponentRow and TC.AuditNumber=TestLimits.AuditNumber)
,#TestPosition
,#SampleCode
,0 --auditflag
,1 --limitsource = test
,[LimitType]
,[UpperLimitEntered]
,[UpperLimitValue]
,[LowerLimitEntered]
,[LowerLimitValue]
,[LimitTextColour]
,[LimitPattern]
,[LimitForeColour]
,[LimitBackColour]
,#EnterDate
,#EnterTime
,#EnterUser
,[LimitText]
,[FilterName]
,0 --deleted
,0 --rule based
FROM TestLimits join Tests on Tests.TestCode=TestLimits.TestCode and Tests.AuditNumber= TestLimits.AuditNumber WHERE Tests.TestCode=#TestCode and Tests.auditnumber=#TestVersion and ([TestLimits].FilterString is null or DATALENGTH([TestLimits].FilterString)=0)
END
Assuming that I understand your logic correctly (ie. that you want the nextlimitnumber to increase by 1 for each row being added), in Oracle, I'd do it by using the analytic function row_number() to work out what number to add to the previous max value, something like:
INSERT INTO sampletestlimits (auditnumber,
limitnumber,
componentrow,
componentcolumn,
componentname,
testposition,
samplecode,
auditflag,
limitsource,
limittype,
upperlimitentered,
upperlimitvalue,
lowerlimitentered,
lowerlimitvalue,
limittextcolour,
limitpattern,
limitforecolour,
limitbackcolour,
createddate,
createdtime,
createduser,
limittext,
filtername,
deleted,
isrulebased)
SELECT 1, --starting auditnumber
(SELECT COALESCE (MAX (limitnumber), 0) + 1 AS nextlimitnumber
FROM sampletestlimits
WHERE samplecode = p_samplecode
AND testposition = p_testposition
AND componentrow = 1
AND componentcolumn = 1
AND auditflag = 0)
+ row_number() over (partition by testposition, componentrow, componentcolumn, auditflag) as nextlimitnumber, -- TFS bug# 3952: Calculate next limit number.
componentrow,
componentcolumn,
(SELECT componentname
FROM testcomponents tc
WHERE tc.testcode = p_testcode
AND tc.componentcolumn = testlimits.componentcolumn
AND tc.componentrow = testlimits.componentrow
AND tc.auditnumber = testlimits.auditnumber),
p_testposition,
p_samplecode,
0, --auditflag
1, --limitsource = test
limittype,
upperlimitentered,
upperlimitvalue,
lowerlimitentered,
lowerlimitvalue,
limittextcolour,
limitpattern,
limitforecolour,
limitbackcolour,
p_enterdate,
p_entertime,
p_enteruser,
limittext,
filtername,
0, --deleted
0 --rule based
FROM testlimits
JOIN tests
ON tests.testcode = testlimits.testcode
AND tests.auditnumber = testlimits.auditnumber
WHERE tests.testcode = p_testcode
AND tests.auditnumber = p_testversion
AND ( testlimits.filterstring IS NULL
OR datalength (testlimits.filterstring) = 0);
I had to guess at what the partition by clause would need to contain - adjust that as necessary for your requirements.

Need help joining 3rd table to Stored Proc

I have a stored Procedure that works fine joining 2 tables together. I needed to add a new field from a new table that was not included in the original SP. What I am trying to do is sum a field from the new table for each record that is a child record of the Parent table which is in the original SP.
I tested the Sum based on th parent table in a test query and it works fine:
select totaldollars from TTS_EmpTime where emptimedaytotal_id='32878'
so then the next step would be to integrate into the SP. I did so and have set the new portions of the SP to be bold so you can see what was added. IF the bold portions are removed the SP works fine if not I get this error:
*Msg 8120, Level 16, State 1, Procedure TTS_RptTest2, Line 11
Column 'TTS_EmpTimeDayTotal.EmployeeID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause*.
Here is my Stored Proc:
USE [TTSTimeClock]
GO
/****** Object: StoredProcedure [dbo].[TTS_RptTest2] Script Date: 03/04/2011 12:29:59 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[TTS_RptTest2]
#BureauID nvarchar(36),
#CompanyID nvarchar(36),
#DivisionID nvarchar(10) ,
#punchDate smalldatetime,
#PeriodDays integer,
#EmployeeID nvarchar(20) = null
As
--with DayTotals as(
select
DayTotal.DivisionID,
DayTotal.EmployeeID,
EmpData.EmployeeFirstName AS First,
EmpData.EmployeeLastName AS Last,
EmpData.employeetypeid AS EmpId,
DayTotal.ID as DayTotalID,
-- Format the Date as MM/DD DOW or 2Digit Month & 2Digit Day and the 3Char Day of the week Uppercase
convert(varchar(5),DayTotal.PunchDate,101) + ' ' + upper(left(datename(dw,DayTotal.Punchdate),3))as PunchDate,
-- Format the in and out time as non military time with AM or PM No Dates
substring(convert(varchar(20), DayTotal.FirstDayPunch, 9), 13, 5) + ' ' + substring(convert(varchar(30), DayTotal.FirstDayPunch, 9), 25, 2)as TimeIn,
substring(convert(varchar(20), DayTotal.LastDayPunch, 9), 13, 5) + ' ' + substring(convert(varchar(30), DayTotal.LastDayPunch, 9), 25, 2) as TimeOut,
DayTotal.RegularHours,
DayTotal.NonOvertimeHours,
DayTotal.OvertimeHours,
DayTotal.TotalDayHRS,
DayTotal.PeriodRegular,
DayTotal.PeriodOtherTime,
DayTotal.PeriodOvertime,
DayTotal.PeriodTotal,
**sum(cast(EmpTime.TotalDollars as float)) as TotalDayDollars**
from TTS_EmpTimeDayTotal as DayTotal
INNER JOIN TTS_PayrollEmployees AS EmpData
ON DayTotal.EmployeeID = EmpData.EmployeeID
**inner JOIN TTS_Emptime as EmpTime
ON DayTotal.id = emptime.emptimedaytotal_id**
where
DayTotal.BureauID = #BureauID
AND DayTotal.CompanyID = #CompanyID
AND (DayTotal.DivisionID = #DivisionID)
AND daytotal.periodstart =
-- Period start date
(SELECT DISTINCT PeriodStart
FROM TTS_EmpTimeDayTotal
WHERE(BureauID = #BureauID) AND (CompanyID = #CompanyID) AND ( (DivisionID = #DivisionID))
AND (PunchDate = #punchDate)and periodend = dateadd(d,(#PeriodDays - 1),(periodstart)))
AND daytotal.periodend =
-- Period End Date
(SELECT DISTINCT PeriodEnd
FROM TTS_EmpTimeDayTotal
WHERE(BureauID = #BureauID) AND (CompanyID = #CompanyID) AND ( (DivisionID = #DivisionID))
AND (PunchDate = #punchDate)and periodend = dateadd(d,(#PeriodDays-1),(periodstart)))
-- Optional all employees or just one
AND (( #EmployeeID is Null) or (DayTotal.EmployeeID = #EmployeeID))
order by Empdata.employeetypeid,DayTotal.punchdate
I am not grouping at all so this must be caused by something else?
Any Help will be appreciated
Is this SQL Server? Looks like it. You're using SUM, an aggregate function, which I don't believe you can use without a GROUP BY clause. Did you always have the SUM in there, or did you add it alongside the new table?
If the latter, that may well be your problem.
Update
Based on OP's comment:
Wow that could be a pain would I do
somehing like groupby field1,field2,
and so on? as in a coma delimited
list. Is there another way to include
this one field that would be better?
Yes, in SQL Server you must be explicit with groupings when using an aggregate function. One alternative in your case would be to do the grouping as a subquery, and join on that, i.e.:
FROM TTS_EmpTimeDayTotal AS DayTotal
INNER JOIN TTS_PayrollEmployees AS EmpData ON DayTotal.EmployeeID = EmpData.EmployeeID
INNER JOIN (SELECT EmpTimeDayTotal_id, SUM(CAST(TotalDollars AS FLOAT)) AS TotalDayDollars
FROM TTS_Emptime
GROUP BY EmpTimeDayTotal_id) AS EmpTime ON DayTotal.id = EmpTime.EmpTimeDayTotal_id
And then simply reference EmpTime.TotalDayDollars in the SELECT list, instead of performing the SUM there.