UPDATE row from query with multiple matches - sql-server

Given an update statement like so:
UPDATE
UserAssesment
SET
AssessmentDate = comp.AssessmentDate
FROM
UserAssesment ua
INNER JOIN
vw_CompletedAssessments comp
On
ua.NatId = comp.NatId and
ua.FamilyName = comp.ClientLastName and
ua.GivenName = comp.ClientFirstName
WHERE
ua.HasCompletedAssessment <> 0
if a user can have multiple records that match the join clause to vw_CompletedAssessments, which record will be used for the update? Is there a way to order it so the max or min AssessmentDate is used?

Your Syntax for UPDATE needs some tweaking see below:
UPDATE ua
SET
ua.AssessmentDate = comp.AssessmentDate
FROM UserAssesment ua
INNER JOIN vw_CompletedAssessments comp
ON ua.NatId = comp.NatId and
ua.FamilyName = comp.ClientLastName and
ua.GivenName = comp.ClientFirstName
WHERE ua.HasCompletedAssessment <> 0
Now coming to the point if you have multiple values and you want to Pick a particular value from Comp table for that you can make use of ROW_NUMBER functions something like this...
UPDATE ua
SET
ua.AssessmentDate = comp.AssessmentDate
FROM UserAssesment ua
INNER JOIN (SELECT *
, ROW_NUMBER() OVER (PARTITION BY NatId ORDER BY AssessmentDate DESC) rn
FROM vw_CompletedAssessments) comp
ON ua.NatId = comp.NatId
and ua.FamilyName = comp.ClientLastName
and ua.GivenName = comp.ClientFirstName
WHERE ua.HasCompletedAssessment <> 0
AND Comp.rn = 1
This query will update the ua.AssessmentDate to the latest comp.AssessmentDate for a particular NatId. Similarly you can see how you can manipulate the results using row number. If you want to update it to the oldest comp.AssessmentDate value just change the order by clause in row_number() function to ASC and so on....

If more than one match is found, then all will be updated.
If you want only one to be updated, can you use UPDATE TOP (1)?
If you want to guarantee the order of the one to be updated, try to add an appropriate ORDER BY clause.

Related

How to update multiple rows in a temp table with multiple values from another table using only one ID common between them?

I am trying to reconcile the IDs in a temp table (top) from another DB table (bottom). Since I only have one ID that's common between the two, I am only getting the top result for all the rows (ReconGlobalRemunerationGrantID) in my temp table. I am aiming to get each of the unique ID and update my temp table as such.
Right now, my update query is simple and I update using the ID common between the 2 tables. Is there a function or another command statement I can use to get the result intended?
update tgrg set ReconGlobalRemunerationGrantID = grg.GlobalRemunerationGrantID from #GlobalRemunerationGrant tgrg
join #GlobalRemuneration tgr on tgr.GlobalRemunerationID = tgrg.GlobalRemunerationID
join DataCore..GlobalRemuneration gr on gr.CompanyID = #CompanyID and gr.FiscalYearID = tgr.FiscalYearID and gr.DirectorDetailID = tgr.DirectorDetailID and tgr.GlobalRoleIDCODE = gr.GlobalRoleID
join DataCore..GlobalRemunerationGrant grg on gr.GlobalRemunerationID = grg.GlobalRemunerationID
Thank you.
Based on the comment - you have 2 values to match on, not just one? e.g., both GlobalRemunerationID and GlobalRemunerationGrantID?
Here's an example using tables 'temptable' and 't1'
UPDATE temptable
SET ReconGlobalRemunerationGrantID = t1.GlobalRemunerationGrantID
FROM temptable
INNER JOIN t1 ON temptable.GlobalRemunerationID = t1.GlobalRemunerationID
AND temptable.GlobalRemunerationGrantID = t1.GlobalRemunerationGrantID
Update below
The below version takes the two data sets
Partitions them by GlobalRemunerationID and orders them by ReconGlobalRemunerationGrantID to get the 'row numbers' (rn)
Joins them on GlobalRemunerationID and rn to get them in order
Key code is below (with slightly different tables than your full set sorry - matches the data set you gave though).
; WITH tgrg AS
(SELECT GlobalRemunerationID, ReconGlobalRemunerationGrantID,
ROW_NUMBER() OVER (PARTITION BY GlobalRemunerationID ORDER BY GlobalRemunerationGrantID) AS rn
FROM #GlobalRemunerationGrant
)
UPDATE tgrg
SET ReconGlobalRemunerationGrantID = tgr.GlobalRemunerationGrantID
FROM tgrg
INNER JOIN
(SELECT GlobalRemunerationID, GlobalRemunerationGrantID,
ROW_NUMBER() OVER (PARTITION BY GlobalRemunerationID ORDER BY GlobalRemunerationGrantID) AS rn
FROM GlobalRemuneration
) AS tgr ON tgrg.GlobalRemunerationID = tgr.GlobalRemunerationID AND tgrg.rn = tgr.rn
A db<>fiddle with the full set is there - note that I changed some of the IDs to demonstrate that it wasn;t using them to match.

Replacement for UPDATE statement with ORDER BY clause

I am having a hard time trying to execute an update query that should contain ORDER BY clause, but I'm unable to find a proper solution yet.
UPDATE I
SET RefItemID = AQ.ID,
I.MagParamNum = AQ.MagParamNum
FROM SRO_VT_SHARD.._Items I
JOIN SRO_VT_SHARD.._Inventory INV ON INV.ItemID = I.ID64
JOIN SRO_VT_SHARD.._RefObjCommon ROC ON ROC.ID = I.RefItemID
JOIN _AEQItems AQ ON AQ.TypeID3 = ROC.TypeID3
AND AQ.TypeID4 = ROC.TypeID4
WHERE
INV.Slot BETWEEN 0 AND 13
AND INV.Slot != 8
AND AQ.ReqLevel1 <= #Data2
AND INV.CharID = #CharID
ORDER BY AQ.ReqLevel1 DESC
Basically my query should work this way if ORDER BY clause is usable inside an update statement, but it doesn't. Is there something I can do which should solve this?
Thanks a lot in advance.
You need to determine the exact row to update for each TypeID3 / TypeID4 combination, and you can't do that in the outer query. You may need to add additional ORDER BY clauses here to break ties. You may also want to specify only a subset of columns if you have an index that covers the columns in _AEQItems used to search and the columns you're updating.
;WITH AQ AS
(
SELECT *, rn = ROW_NUMBER() OVER
(PARTITION BY TypeID3, TypeID4 ORDER BY ReqLevel1 DESC)
FROM _AEQItems
)
UPDATE I
SET RefItemID = AQ.ID,
MagParamNum = AQ.MagParamNum
FROM SRO_VT_SHARD.._Items I
JOIN SRO_VT_SHARD.._Inventory INV ON INV.ItemID = I.ID64
JOIN SRO_VT_SHARD.._RefObjCommon ROC ON ROC.ID = I.RefItemID
JOIN AQ ON AQ.TypeID3 = ROC.TypeID3 AND AQ.TypeID4 = ROC.TypeID4
WHERE AQ.rn = 1
AND INV.Slot BETWEEN 0 AND 13
AND INV.Slot!=8
AND AQ.ReqLevel1 <= #Data2
AND INV.CharID = #CharID;
Use a subquery:
UPDATE I
SET RefItemID=AQ.ID,I.MagParamNum=AQ.MagParamNum
FROM SRO_VT_SHARD.._Items I
JOIN SRO_VT_SHARD.._Inventory INV ON INV.ItemID=I.ID64
JOIN SRO_VT_SHARD.._RefObjCommon ROC ON ROC.ID=I.RefItemID
JOIN (SELECT TypeID3, TypeID4, MAX(ReqLevel1) AS ReqLevel1 FROM _AEQItems GROUP BY TypeID3, TypeID4) AQ
ON AQ.TypeID3=ROC.TypeID3
AND AQ.TypeID4=ROC.TypeID4
WHERE INV.Slot BETWEEN 0 AND 13 AND INV.Slot!=8 AND AQ.ReqLevel1<=#Data2 AND INV.CharID=#CharID

SQL combine two queries result into one dataset

I am trying to combine two SQL queries the first is
SELECT
EAC.Person.FirstName,
EAC.Person.Id,
EAC.Person.LastName,
EAC.Person.EmployeeId,
EAC.Person.IsDeleted,
Controller.Cards.SiteCode,
Controller.Cards.CardCode,
Controller.Cards.ActivationDate,
Controller.Cards.ExpirationDate,
Controller.Cards.Status,
EAC.[Group].Name
FROM
EAC.Person
INNER JOIN
Controller.Cards ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN
EAC.GroupPersonMap ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN
EAC.[Group] ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id
And the second one is
SELECT
IsActive, ActivationDateUTC, ExpirationDateUTC,
Sitecode + '-' + Cardcode AS Credential, 'Badge' AS Type,
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GetUTCDate()
THEN 'Pending'
WHEN ExpirationDAteUTC < GetUTCDate()
THEN 'Expired'
ELSE 'Active'
END AS Status
FROM
EAC.Credential
JOIN
EAC.WiegandCredential ON Credential.ID = WiegandCredential.CredentialId
WHERE
PersonID = '32'
Where I would like to run the second query for each user of the first query using EAC.Person.Id instead of the '32'.
I would like all the data to be returned in one Dataset so I can use it in Report Builder.
I have been fighting with this all day and am hoping one of you smart guys can give me a hand. Thanks in advance.
Based on your description in the comments, I understand that the connection between the two datasets is actually the PersonID field, which exists in both EAC.Credential and EAC.Person; however, in EAC.Credential, duplicate values exist for PersonID, and you want only the most recent one for each PersonID.
There are a few ways to do this, and it will depend on the number of rows returned, the indexes, etc., but I think maybe you're looking for something like this...?
SELECT
EAC.Person.FirstName
,EAC.Person.Id
,EAC.Person.LastName
,EAC.Person.EmployeeId
,EAC.Person.IsDeleted
,Controller.Cards.SiteCode
,Controller.Cards.CardCode
,Controller.Cards.ActivationDate
,Controller.Cards.ExpirationDate
,Controller.Cards.Status
,EAC.[Group].Name
,X.IsActive
,X.ActivationDateUTC
,X.ExpirationDateUTC
,X.Credential
,X.Type
,X.Status
FROM EAC.Person
INNER JOIN Controller.Cards
ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN EAC.GroupPersonMap
ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN EAC.[Group]
ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id
CROSS APPLY
(
SELECT TOP 1
IsActive
,ActivationDateUTC
,ExpirationDateUTC
,Sitecode + '-' + Cardcode AS Credential
,'Badge' AS Type
,'Status' =
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GETUTCDATE()
THEN 'Pending'
WHEN ExpirationDateUTC < GETUTCDATE()
THEN 'Expired'
ELSE 'Active'
END
FROM EAC.Credential
INNER JOIN EAC.WiegandCredential
ON EAC.Credential.ID = EAC.WiegandCredential.CredentialId
WHERE EAC.Credential.PersonID = EAC.Person.PersonID
ORDER BY EAC.Credential.ID DESC
) AS X
-- Optionally, you can also add conditions to return specific rows, i.e.:
-- WHERE EAC.Person.PersonID = 32
This option uses a CROSS APPLY, which means that every row of the first dataset will return additional values from the second dataset, based on the criteria that you described. In this CROSS APPLY, I'm joining the two datasets based on the fact that PersonID exists in both EAC.Person (in your first dataset) as well as in EAC.Credential. I then specify that I want only the TOP 1 row for each PersonID, with an ORDER BY specifying that we want the most recent (highest) value of ID for each PersonID.
The CROSS APPLY is aliased as "X", so in your original SELECT you now have several values prefixed with the X. alias, which just means that you're taking these fields from the second query and attaching them to your original results.
CROSS APPLY requires that a matching entry exists in both subsets of data, much like an INNER JOIN, so you'll want to check and make sure that the relevant values exist and are returned correctly.
I think this is pretty close to the direction you're trying to go. If not, let me know and I'll update the answer. Good luck!
Try like this;
select Query1.*, Query2.* from (
SELECT
EAC.Person.FirstName,
EAC.Person.Id as PersonId,
EAC.Person.LastName,
EAC.Person.EmployeeId,
EAC.Person.IsDeleted,
Controller.Cards.SiteCode,
Controller.Cards.CardCode,
Controller.Cards.ActivationDate,
Controller.Cards.ExpirationDate,
Controller.Cards.Status,
EAC.[Group].Name
FROM
EAC.Person
INNER JOIN
Controller.Cards ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN
EAC.GroupPersonMap ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN
EAC.[Group] ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id)
Query1 inner join (SELECT top 100
IsActive, ActivationDateUTC, ExpirationDateUTC,
Sitecode + '-' + Cardcode AS Credential, 'Badge' AS Type,
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GetUTCDate()
THEN 'Pending'
WHEN ExpirationDAteUTC < GetUTCDate()
THEN 'Expired'
ELSE 'Active'
END AS Status
FROM
EAC.Credential
JOIN
EAC.WiegandCredential ON Credential.ID = WiegandCredential.CredentialId
ORDER BY EAC.Credential.ID DESC) Query2 ON Query1.PersonId = Query2.PersonID
Just select two queries to join them like Query1 and Query2 by equaling PersonId data.

How do I get latest versions of modules in my left inner join?

I am using a SQL left inner join to query 4 tables. One of the tables HtmlText contains both ModuleID and Version columns. What I want to accomplish is pull only the MAX version of every ModuleID from a specific site or PortalID. Here is what I have tried
SELECT TBS.PortalID [PortalID], TBS.TabID [TabID], TBS.TabName [TabName],
TBS.TabPath [TabPath], HTM.Version[Version], TBM.ModuleID [ModuleID],
MDS.ModuleID[ModuleID], HTM.Content[Content]
FROM [MyDB].[dbo].[Tabs] TBS
Inner JOIN [MyDB].[dbo].[Modules] MDS
LEFT JOIN [MyDB].[dbo].[TabModules] TBM
LEFT JOIN [MyDB].[dbo].[HtmlText] HTM
ON HTM.[ModuleID] = TBM.[ModuleID]
ON MDS.[ModuleID] = TBM.[ModuleID]
ON TBS.[TabID] = TBM.[TabID]
WHERE TBS.[PortalID] = '0' AND DataLength(HTM.[Content]) <> 0
AND Version = (Select MAX([Version]) from [MyDB].[dbo].[HtmlText])
But this only gives me the ModuleID with the largest Version, instead of the MAX Version of all of the different ModuleIDs
Use the ROW_NUMBER() windowing function in a derived table (subquery in the FROM clause):
(SELECT iHTM.ModuleID,
iHTM.Version,
rownum = ROW_NUMBER()
OVER (
PARTITION BY iHTM.ModuleID
ORDER BY iHTM.Version DESC)
FROM [MyDB].[dbo].[HtmlText] iHTM) htmVER
and when in the ON clause, be sure to include htmVER.rownum = 1 so that you get the first instance (i.e. greatest Version).
Try changing your last predicate to this:
AND Version = (Select MAX([Version]) from [MyDB].[dbo].[HtmlText] where ModuleId = HTM.ModuleId)
This will get the max version for that particular module
You can use this query if you only need the Content column (or another single one) from HtmlText. It also makes sure the Module or the Tab the module is on has not been deleted.
SELECT Modules.PortalID, TabModules.TabID, Tabs.TabName, Tabs.TabPath, Modules.ModuleID,
(SELECT TOP (1) [Content] FROM HtmlText WHERE (ModuleID = Modules.ModuleID) ORDER BY Version DESC) AS Content
FROM Modules
INNER JOIN TabModules ON TabModules.ModuleID = Modules.ModuleID
INNER JOIN Tabs ON Tabs.TabID = TabModules.TabID
WHERE (Modules.ModuleDefID = 116) AND (Modules.IsDeleted = 0) AND (Tabs.IsDeleted = 0) AND (Modules.PortalID = 0)
ORDER BY Modules.PortalID, TabModules.TabID, Modules.ModuleID
You only need to check if the ModuleDefID is 116 or another number. This could vary per DNN install.
This can be found in the ModuleDefinitions table with the default FriendlyName being Text/HTML. You could include the ModuleDefinitions into the query and check the FriendlyName value, but that is possibly not a unique value (although unlikely)

How to apply order by on Update?

I am trying to update values in Temporary table but before update i want to order by the records on the basis of date.
UPDATE INS
set ins.PrefferedEmail = IC.CntcInfoTxt
From #Insured INS
Inner Join InsuredContact IC
on IC.InsuredId = INS.Insuredid and IC.ExpDt < Getdate() And (INS.InsuredStatus = 'Expired' or INS.InsuredStatus = 'Merged')
Where IC.CntcTypeCd = 'EML' and IC.InsuredId = #InsuredId and MAX(IC.ExpDt) ExpDt
I want to update on the basis of this column IC.ExpDt
Thanks in advance
I think you are confusing an UPDATE with SELECT(ing) the correct data to update with.
I solved this problem with a common table expression (cte) and the rank() function. The cte is a nice way to get a sub-query results. The rank is needed to find the most recent contact info.
-- 1 - Get id, contact text, expired date, with a rank by expired date
-- 2 - Join with table to update, select rank = 1
;
WITH cteRecentContactInfo
AS
(
SELECT
ic.InsuredId,
ic.CntcInfoTxt,
ic.ExpDt,
RANK() OVER (ORDER BY ic.ExpDt DESC) as RankByDt
FROM
InsuredContact as ic
WHERE
ic.CntcTypeCd = 'EML' and ic.ExpDt < getdate()
)
UPDATE ins
FROM #Insured ins INNER JOIN cteRecentContactInfo rci
ON ins.Insuredid = rci.Insuredid and ins.ExpDt = rci.ExpDt
WHERE
(ins.InsuredStatus = 'Expired' OR ins.InsuredStatus = 'Merged') AND
rci.RankByDt = 1
Update and sorting doesn't work that way. The rows are not necessarily stored in any particular order, so sorting and updating are completely independent.
If you only want to update based on MAX(ExpDt) you need a subquery that pulls that up.
UPDATE INS
SET INS.PrefferedEmail = tempOutside.CntcInfo
FROM (SELECT IC.CntcInfo,IC.InsuredID FROM
InsuredContact IC INNER JOIN
(SELECT MAX(IC.ExpDt) AS MaxExpDt,IC.InsuredID
FROM IC
WHERE IC.ExpDt < Getdate()
AND IC.CntcTypeCd = 'EML'
GROUP BY IC.InsuredID) AS tempInside
ON tempInside.InsuredID = IC.InsuredID
AND IC.ExpDt = tempInside.MaxExpDt) AS tempOutside
INNER JOIN INS ON tempOutside.InsuredID = INS.InsuredId
WHERE (INS.InsuredStatus = 'Expired' OR INS.InsuredStatus = 'Merged')
AND INS.InsuredID = #InsuredID
On unrelated notes, for the good of whoever is doing maintenance on your code you might want to consider fixing the spelling errors (e.g. should be Preferred instead of 'Preffered') and giving the tables more meaningful names. Also since you're only working with one ID from #InsuredID you could simplify the code and remove an inner join or two but what I have should work for updating several records at once not just the one selected by #InsuredID.
Thanks for your time and comments. I have done this and its working fine
UPDATE INS
set ins.PrefferedEmail = ICC.CntcInfoTxt
From #Insured INS
Inner Join
(
SELECT
InsuredId,
CntcInfoTxt,
CntcTypeCd
From InsuredContact ICC
Where ExpDt = (select MAX(ExpDt) from InsuredContact where ExpDt < GETDATE() and CntcTypeCd = 'EML' and InsuredId = 10)
) As ICC
on ICC.InsuredId = INS.InsuredId And (INS.InsuredStatus = 'Expired' or INS.InsuredStatus = 'Merged')
Where ICC.InsuredId = #InsuredId

Resources