MDS 2016: Entity purge - hard delete - purge

When I try to purge my entity with the Master Data Service interface or with the SP mdm.udpDeletedMembersPurge, I get this error:
MDSERR200221|Members cannot be purged from the model version. It contains at least one entity that is the target of a sync relationship.
Yes, I have implemented some entity Sync, and when I run them manually, it doesn't solve the problem.
Any idea?

There is a check in the mdm.udpDeletedMembersPurge stored procedure to ensure that you are not trying to purge a sync entity. It does not specify the entity you are trying to purge, so any sync entity in your current model will prevent a purge.
I modified the store procedure to look at the current entity by adding another clause to the predicate:
This code starts at line 172.
-- Verify the entity is not a sync target.
DECLARE #TargetEntityName NVARCHAR(50) = NULL;
SELECT TOP 1
#TargetEntityName = e.Name
FROM mdm.tblSyncRelationship sr
INNER JOIN mdm.tblEntity e
ON sr.TargetEntity_ID = e.ID
WHERE e.Model_ID = #Model_ID
AND sr.TargetVersion_ID = #Version_ID
AND e.id = #Entity_ID --Add this to the predicate

Similar to the other answer, I added a condition using the following, slightly altered version. This will still correctly throw the error if you try to execute the procedure against an entire model, without defining an entity, where the model contains a sync target.
-- Verify the entity is not a sync target.
DECLARE #TargetEntityName NVARCHAR(50) = NULL;
SELECT TOP 1
#TargetEntityName = e.Name
FROM mdm.tblSyncRelationship sr
INNER JOIN mdm.tblEntity e
ON sr.TargetEntity_ID = e.ID
WHERE e.Model_ID = #Model_ID
AND sr.TargetVersion_ID = #Version_ID
AND (#Entity_ID IS NULL OR e.ID = #Entity_ID); --Added line

Related

SQL Server - Using joins in Update statement

I have a HRUser and an Audit table, both are in production with large number of rows.
Now I have added one more column to my HRUser table called IsActivated.
I need to create a one-time script which will be executed in production and populate data into this IsActivated column. After execution of this one-time script onwards, whenever the users activate their account, the HRUser table's IsActivated column will automatically be updated.
For updating the IsActivated column in the HRUser table, I need to check the Audit table whether the user has logged in till now.
UPDATE [dbo].HRUser
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14
AuditTypeId=14 means the user has logged in and user can login any number of times and every time the user logs in it will get captured in the UserAudit table...
The logic is that if the user has logged in at least once means the user is activated.
This cannot be tested on lower environments and it need to be directly executed on production as in lower environments we don’t have any data in the UserAudit table.
I am not really sure if that works as I have never used joins in update statement, I am looking for suggestions for any better approach than this for accomplishing my task
You could use EXISTS and a correlated subquery to filter on rows whose UserId has at least one audit event of id 14:
UPDATE h
SET IsActivated = 1
FROM [dbo].HRUser h
WHERE EXISTS (
SELECT 1 FROM
FROM dbo.[UserAudit] a
WHERE a.UserId = h.UserId AND a.AuditTypeId = 14
)
Note that there is no point reopening the target table in the subquery; you just need to correlate it with the outer query.
Two methods below. Method 1 is NOT recommended for tables "in production with large number of rows". But it is much easier to code. Method 2 works in production with no downtime.
Whichever method you choose: TEST it outside production. Copy the data from production. If you cannot do that, then build your own. Build a toy system. Highly recommended that you test at some level before running either method in production.
METHOD 1:
Updating on a join is straight forward. Use an alias. Reminder, this is NOT RECOMMENDED "with large number of rows" AND production running. The SQL Server optimizer most likely will escalate locks on both tables and block the tables until the update completes. IF you are taking an outage and are not concerned with how long the update takes, this method works.
UPDATE U
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14
METHOD 2:
IF you cannot afford to stop your production systems for this update (and most of us cannot), then I recommend that you do 2 things:
Set up a loop with the transaction inside the loop. This means that the optimizer will use row locks and not block the entire table. This method may take longer, but it will not block production. If the update takes longer, not a concern as long as the devops team never calls because production is blocked.
Capture rows to be updated outside the transaction. THEN, update based on a primary key (fastest). The total transaction time is how long the rows updated will be blocked.
Here is a toy example for looping.
-- STEP 1: get data to be updated
CREATE TABLE #selected ( ndx INT IDENTITY(1,1), UserId INT )
INSERT INTO #selected (UserId)
SELECT UserId
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14
-- STEP 2: update on primary key in steps of 1000
DECLARE #RowsToUpdate INT = 1000
, #LastId INT = 0
, #RowCnt INT = 0
DECLARE #v TABLE(ndx INT, UserId INT)
WHILE 1=1
BEGIN
DELETE #v
INSERT INTO #v
SELECT TOP(#RowsToUpdate) *
FROM #selected WHERE ndx > #LastId
ORDER BY ndx
SET #RowCnt = ##ROWCOUNT
IF #RowCnt = 0
BREAK;
BEGIN TRANSACTION
UPDATE a
SET IsActivated = 1
FROM #v v
JOIN dbo.HRUser a ON a.Id = v.UserId
COMMIT TRANSACTION
SELECT #LastId = MAX(ndx) FROM #v
END

Is it possible to pick one result set to return from a batch execution?

I have to retrieve some data from a MSSQL 2014 server through a propriatery application which then uses an odbc data source. The only thing I can modify is the query the application uses. I cannot modify the application or how the application handles the results.
The following query is doing what I want if I execute it directly e.g. in Heidi.
USE MY_DB;
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DECLARE #myvar1 INT = 2;
DECLARE #myvar2 INT = 2;
PRINT #myvar1;
SELECT TOP 20 [vwPGIA].[OrNum],[vwPGIA].[DBM],[vwPGIA].[MBM],[vwPGIA].[MN],[NOMID],[Priority],SUBSTRING([Comment],0,254) AS Comment,[TLSAP],[Box],[SequenceNumber]
INTO #tmp_tbl
FROM [MY_DB].[dbo].[vwPGIA]
INNER JOIN [MY_DB].[dbo].[tblDLA] ON [dbo].[tblDLA].[OrNum]=[dbo].[vwPGIA].[OrNum]
INNER JOIN [dbo].[tblMDM] ON [vwPGIA].[MBM]=[tblMDM].[MBM]
WHERE ([TLSAP] = #myvar1)
AND [vwPGIA].[MBM] NOT IN (SELECT [MBM] FROM [MY_DB].[dbo].[vwDPS])
AND [vwPGIA].[OrNum] NOT IN (SELECT [OrNum] FROM [MY_DB].[dbo].[vwDPS] WHERE [MY_DB].[dbo].[vwDPS].[TLR] <> #myvar1)
ORDER BY [SequenceNumber];
SELECT TOP 1 [OrNum],[DBM],[MBM],[MN],[NOMID],[Priority],[Comment],[TLSAP],[Box],[WTT],[SequenceNumber]
FROM #tmp_tbl
INNER JOIN [dbo].[tblTBN] ON [Box]=[BoxN]
WHERE ([WTT]=#myvar2)
ORDER BY [SequenceNumber];
INSERT INTO [dbo].[tblDPS]
(OrNum,DBM,MBM,State,StateStartTime,Info,TLR)
SELECT TOP 1 [OrNum],[DBM],[MBM],'1',GETDATE(),'info',#myvar1
FROM #tmp_tbl
INNER JOIN [dbo].[tblTBN] ON [Box]=[BoxN]
WHERE ([WTT]=#myvar2)
ORDER BY [SequenceNumber]
;
DROP TABLE #tmp_tbl;
COMMIT TRANSACTION
Running this through the ODBC interface results in an empty result. The problem seems to be, that I am doing a batch request which results in multiple result sets. The application probably only handles the first result set or maybe cannot handle more than one result set.
Finally the question: Is there a way or workaround to reduce the result sets to only the one returned by the SELECT TOP 1 ... part?

Placement of WITH(NOLOCK) in nested queries

In the Following query where would I place WITH(NOLOCK)?
SELECT *
FROM (SELECT *
FROM (SELECT *
FROM (SELECT *
FROM (SELECT *
FROM dbo.VBsplit(#mnemonicList, ',')) a) b
JOIN dct
ON dct.concept = b.concept
WHERE b.geo = dct.geo) c
JOIN dct_rel z
ON c.db_int = z.db_int) d
JOIN rel_d y
ON y.rel_id = d.rel_id
WHERE y.update_status = 0
GROUP BY y.rel_id,
d.concept,
d.geo_rfa
You should not put NOLOCK anywhere in that query. If you are trying to prevent readers from blocking writers, a much better alternative is READ COMMITTED SNAPSHOT. Of course, you should read about this, just like you should read about NOLOCK before blindly throwing it into your queries:
Is the NOLOCK SQL Server hint bad practice?
Is NOLOCK always bad?
What risks are there if we enable read committed snapshot in SQL Server?
Also, since you're using SQL Server 2008, you should probably replace your VBSplit() function with a table-valued parameter - this will be much more efficient than splitting up a string, even if the function is baked in CLR as implied.
First, create a table type that can hold appropriate strings. I'm going to assume the list is guaranteed to be unique and no individual mnemonic word can be > 900 characters.
CREATE TYPE dbo.Strings AS TABLE(Word NVARCHAR(900) PRIMARY KEY);
Now, you can create a procedure that takes a parameter of this type, and which sets the isolation level of your choosing in one location:
CREATE PROCEDURE dbo.Whatever
#Strings dbo.Strings READONLY
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL --<choose wisely>;
SELECT -- please list your columns here instead of *
FROM #Strings AS s
INNER JOIN dbo.dct -- please always use proper schema prefix
ON dct.concept = s.Word
...
END
GO
Now you can simply pass a collection (such as a DataTable) in from your app, be it C# or whatever, and not have to assemble or deconstruct a messy comma-separated list at all.
Since the question really is, "where should I put NOLOCK". I am not going do debate the use of OR reformat the query with better joins. I will just answer the question.
In no way am I intending to say this is the better way or to say that the other answers are bad. The other answer solve the actual problem. I'm just intending to show where exactly to place the lock hints as the question asks
SELECT *
FROM (SELECT *
FROM (SELECT *
FROM (SELECT *
FROM (SELECT *
FROM dbo.VBsplit(#mnemonicList, ',')) a) b
JOIN dct WITH (NOLOCK) -- <---
ON dct.concept = b.concept
WHERE b.geo = dct.geo) c
JOIN dct_rel z WITH (NOLOCK) -- <---
ON c.db_int = z.db_int) d
JOIN rel_d y WITH (NOLOCK) -- <---
ON y.rel_id = d.rel_id
WHERE y.update_status = 0
GROUP BY y.rel_id,
d.concept,
d.geo_rfa
Like this, to use the tidiest method.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM (SELECT * FROM
(SELECT * FROM (SELECT * FROM
(SELECT * FROM dbo.VBsplit(#mnemonicList,',')) a ) b
JOIN dct ON dct.concept = b.concept WHERE b.geo = dct_variable.geo_rfa) c
JOIN dct_rel z ON c.db_int = z.db_int) d
JOIN rel_d y ON y.rel_id = d.rel_id
WHERE y.update_status = 0
GROUP BY y.rel_id,d.concept,d.geo_rfa
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
However, unless you are using this for reporting purposes on an active database, enabling dirty reads may not be the best way to go.
Edited as (NOLOCK) itself is not deprecated except as described here: http://technet.microsoft.com/en-us/library/ms143729.aspx.

Updating Field Based on Another Table

As someone new to SQL can you please point me in the right direction. I know the following is wrong but I am not sure why.
UPDATE cus
SET cus.leg_no = new.leg_no
WHERE cus.c_no = new.c_no
The cus table currently has null in the leg_no. I want to update it from the new table. I will be joining on c_no which is in both tables.
I have tried searching the web but I am getting further confused. This has lead me to think I need FROM but something is telling me that is when using SELECT rather than UPDATE.
UPDATE cus,new
SET cus.leg_no = new.leg_no
WHERE cus.c_no = new.c_no
Here's the standard way to do it:
UPDATE cus
SET cus.leg_no = (select new.leg_no from new WHERE cus.c_no = new.c_no)
Here's the SQL Server/MS access dialect way to do it:
UPDATE cus
SET cus.leg_no = new.leg_no
FROM cus
INNER JOIN new
ON cus.c_no = new.c_no
Note that, with the standard method, if there are multiple rows in new that match a particular row from cus, you'll get an error message. Whereas with the SQL Server/MS access dialect form, the system will arbitrarily select one of the rows, and issue no warning or error.

Update database from another using joins?

I am trying to update a table from another database using joins and having a hard time. This is what I am trying to do in pseudo:
UPDATE [Database1].[dbo].[Sessions]
SET [SpeakerID] = ?STATEMENT1?
WHERE ?STATEMENT2?
For "Statement1", this would be coming from another database and table that has columns: SessionID and SpeakerID. How can this be achieved?
UPDATE a
SET a.SpeakerID = b.colName -- SET valoue here
FROM Database1.dbo.Sessions a
INNER JOIN Database2.dbo.Sessions b
ON a.SessionID = b.SessionID -- assumes that their
-- relationship column is SessionID,
-- change it in your original columnName
WHERE ....
a and b are called alias. They are useful when you have longer source name.
UPDATE L
SET SpeakerID = R.SpeakerID
FROM dbo.LocalTable AS L
INNER JOIN RemoteDatabase.dbo.RemoteTable AS R
ON L.SomeValue = R.SomeValue;
This really is no different from this problem except you have to add a database prefix to one of the tables in the join.
try
UPDATE [Database1].[dbo].[Sessions]
SET
Sessions.col1 = other_table.col1
FROM
[Database1].[dbo].[Sessions] Sessions
INNER JOIN
[Database2].[dbo].other_table AS other_table
ON
Sessions.id = other_table.id
WHERE Sessions.id = ??
Note if the database is on another server you will need to create a linked server first
http://msdn.microsoft.com/en-us/library/ff772782.aspx