SQL Update query using select statement - sql-server

I am trying to update a column in a table where the another column matches and selecting the top 1 for that column as the value to update.
Hard to explain, but this is what I wrote:
UPDATE CameraSpecifications AS a
SET a.Variant = (
SELECT TOP 1 GTIN
FROM CameraSpecifcations
WHERE b.ModelGroup = a.ModelGroup )
Hopefully that explains what I am trying to do.
I have a select statement that might also help:
SELECT
(
SELECT TOP 1 b.GTIN
FROM CameraSpecifications AS b
WHERE b.ModelGroup = a.ModelGroup
) AS Gtin,
a.ModelGroup,
COUNT(a.ModelGroup)
FROM CameraSpecifications AS a
GROUP BY a.ModelGroup

We can try doing an update join from CameraSpecifications to a CTE which finds the top GTIN value for each model group. Note carefully that I use an ORDER BY clause in ROW_NUMBER. It makes no sense to use TOP 1 without ORDER BY, so you should at some point update your question and mention TOP 1 with regard to a certain column.
WITH cte AS (
SELECT ModelGroup, GTIN,
ROW_NUMBER() OVER (PARTITION BY ModelGroup ORDER BY some_col) rn
FROM CameraSpecifications
)
UPDATE cs
SET Variant = t.GTIN
FROM CameraSpecifcations cs
INNER JOIN cte t
ON cs.ModelGroup = t.ModelGroup
WHERE
t.rn = 1;

Related

Custom Sort Order in CTE

I need to get a custom sort order in a CTE but the error shows
"--The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
What's a better way to get the custom order in the CTE?
WITH
ctedivisiondesc
as
(
SELECT * FROM (
SELECT --TOP 1 --[APPID]
DH1.[ID_NUM]
--,[SEQ_NUM_2]
--,[CUR_DEGREE]
--,[NON_DEGREE_SEEKING]
,DH1.[DIV_CDE]
,DDF.DEGREE_DESC 'DivisionDesc'
--,[DEGR_CDE]
--,[PRT_DEGR_ON_TRANSC]
--,[ACAD_DEGR_CDE]
,[DTE_DEGR_CONFERRED]
--,MAX([DTE_DEGR_CONFERRED]) AS Date_degree_conferred
,ROW_NUMBER() OVER (
PARTITION BY [ID_NUM]
ORDER BY [DTE_DEGR_CONFERRED] DESC --Getting last degree
) AS [ROW NUMBER]
FROM [TmsePrd].[dbo].[DEGREE_HISTORY] As DH1
inner join [TmsePrd].[dbo].[DEGREE_DEFINITION] AS DDF
on DH1.[DEGR_CDE] = DDF.[DEGREE]
--ORDER BY
--DIV_CDE Level
--CE Continuing Education
--CT Certificate 1
--DC Doctor of Chiropractic 4
--GR Graduate 3
--PD Pending Division
--UG Undegraduate 2
--The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
ORDER BY CASE
WHEN DDF.DEGREE_DESC = 'Certificate' THEN 1
WHEN DDF.DEGREE_DESC = 'Undegraduate' THEN 2
WHEN DDF.DEGREE_DESC = 'Graduate' THEN 3
WHEN DDF.DEGREE_DESC = 'Doctor of Chiropractic' THEN 4
ELSE 5
END
) AS t
WHERE [ROW NUMBER] <= 1
)
SELECT * FROM ctedivisiondesc
You need to sort the outer query.
Sorting a subquery is not allowed because it is meaningless, consider this simple example:
WITH CTE AS
( SELECT ID
FROM (VALUES (1), (2)) AS t (ID)
ORDER BY ID DESC
)
SELECT *
FROM CTE
ORDER BY ID ASC;
The ordering on the outer query has overridden the ordering on the inner query rendering it a waste of time.
It is not just about explicit sorting of the outer query either, in more complex scenarios SQL Server may sort the subqueries any which way it wishes to enable merge joins or grouping etc. So the only way to guarantee the order or a result is to order the outer query as you wish.
Since you may not have all the data you need in the outer query, you may would probably need to create a further column inside the CTE to use for sorting. e.g.
WITH ctedivisiondesc AS
(
SELECT *
FROM ( SELECT DH1.ID_NUM,
DH1.DIV_CDE,
DDF.DEGREE_DESC AS DivisionDesc,
DTE_DEGR_CONFERRED,
ROW_NUMBER() OVER (PARTITION BY ID_NUM ORDER BY DTE_DEGR_CONFERRED DESC) AS [ROW NUMBER],
CASE
WHEN DDF.DEGREE_DESC = 'Certificate' THEN 1
WHEN DDF.DEGREE_DESC = 'Undegraduate' THEN 2
WHEN DDF.DEGREE_DESC = 'Graduate' THEN 3
WHEN DDF.DEGREE_DESC = 'Doctor of Chiropractic' THEN 4
ELSE 5
END AS SortOrder
FROM TmsePrd.dbo.DEGREE_HISTORY AS DH1
INNER JOIN TmsePrd.dbo.DEGREE_DEFINITION AS DDF
ON DH1.DEGR_CDE = DDF.DEGREE
) AS t
WHERE t.[ROW NUMBER] <= 1
)
SELECT ID_NUM,
DIV_CDE,
DivisionDesc,
DTE_DEGR_CONFERRED
FROM ctedivisiondesc
ORDER BY SortOrder;

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

Select 1st matching record from table one that has a match in table two

I'm struggling to achieve what I trying to do, I need to select the 1st record from table 1 that has a match in table 2 and update table 2 with the value.
I have tried numerous ways and have looked at numerous post. But I either can only return 1 record or all records.
My previous attempts are below:
SELECT a.URL,
a.Caption
FROM [EAN].[DBO].[tblHotelImageList] a
WHERE a.EANHotelID IN
(SELECT b.EanHotelId FROM [EAN].[DBO].[tblEanFullTextSearch] b
)
GROUP BY a.URL,
a.Caption;
Above returns all records
SELECT a.Caption,
a.URL
FROM [EAN].[dbo].[tblHotelImageList] a
LEFT JOIN [EAN].[dbo].[tblEanFullTextSearch] b
ON b.EanHotelId = a.EANHotelID
AND b.EanHotelId =
( SELECT TOP 1
a.EANHotelID
FROM [EAN].[dbo].[tblHotelImageList]
WHERE a.EANHotelID = b.EanHotelId
)
;
Above returns all records
The above code at the moment is only trying to retrieve the records, I have not started on trying to update table with records
Instead of left join, use outer apply:
SELECT hil.Caption, hil.URL, . . .
FROM [EAN].[dbo].[tblHotelImageList] hil OUTER APPLY
(SELECT TOP 1 efts.*
FROM [EAN].[dbo].[tblEanFullTextSearch] efts
WHERE efts.EanHotelId = hil.EANHotelID
ORDER BY efts.??
) efts;
Usually, when you want one row, you want the row based on a particular order. That is what the '??' is for.
You can use similar logic for an UPDATE.
Since the mandatory record is in table 2 (tblEanFullTextSearch), make that the driver of your query. This query does what you asked for:
SELECT TOP 1
a.Caption,
a.URL
FROM [EAN].[dbo].[tblEanFullTextSearch] b
INNER JOIN [EAN].[dbo].[tblHotelImageList] a
ON b.EanHotelId = a.EANHotelID
ORDER BY a.EANHotelID;
If you need the first match for all EanHotelId values, you can modify the query like this:
SELECT TOP 1
a.Caption,
a.URL
FROM [EAN].[dbo].[tblEanFullTextSearch] b
INNER JOIN (
SELECT Caption,
URL,
EanHotelId,
ROW_NUMBER() OVER (Order By EanHotelId) as RowID
FROM [EAN].[dbo].[tblHotelImageList]
) a
ON b.EanHotelId = a.EANHotelID
WHERE RowID = 1
ORDER BY a.EANHotelID;
There is probably another criteria for selecting the first match, but it is not in your question.

Join the table valued function in the query

I have one table vwuser. I want join this table with the table valued function fnuserrank(userID). So I need to cross apply with table valued function:
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
For each userID it generates multiple records. I only want the last record for each empid that does not have a Rank of Term(inated). How can I do this?
Data:
HistoryID empid Rank MonitorDate
1 A1 E1 2012-8-9
2 A1 E2 2012-9-12
3 A1 Term 2012-10-13
4 A2 E3 2011-10-09
5 A2 TERM 2012-11-9
From this 2nd record and 4th record must be selected.
In SQL Server 2005+ you can use this Common Table Expression (CTE) to determine the latest record by MonitorDate that doesn't have a Rank of 'Term':
WITH EmployeeData AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY empId, ORDER BY MonitorDate DESC) AS RowNumber
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
WHERE Rank != 'Term'
)
SELECT *
FROM EmployeeData AS ed
WHERE ed.RowNumber = 1;
Note: The statement before this CTE will need to end in a semi-colon. Because of this, I have seen many people write them like ;WITH EmployeeData AS...
You'll have to play with this. Having trouble mocking your schema on sqlfiddle.
Select bar.*
from
(
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
where rank != 'TERM'
) foo
left join
(
SELECT *
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empId = bar.empId
and foo.MonitorDate > bar.MonitorDate
where bar.empid is null
I always need to test out left outers on dates being higher. The way it works is you do a left outer. Every row EXCEPT one per user has row(s) with a higher monitor date. That one row is the one you want. I usually use an example from my code, but i'm on the wrong laptop. to get it working you can select foo., bar. and look at the results and spot the row you want and make the condition correct.
You could also do this, which is easier to remember
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
) foo
join
(
select empid, max(monitordate) maxdate
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empid = bar.empid
and foo.monitordate = bar.maxdate
I usually prefer to use set based logic over aggregate functions, but whatever works. You can tweak it also by caching the results of your TVF join into a table variable.
EDIT:
http://www.sqlfiddle.com/#!3/613e4/17 - I mocked up your TVF here. Apparently sqlfiddle didn't like "go".
select foo.*, bar.*
from
(
SELECT f.*
FROM vwuser AS a
join fnuserrank f
on a.empid = f.empid
where rank != 'TERM'
) foo
left join
(
SELECT f1.empid [barempid], f1.monitordate [barmonitordate]
FROM vwuser AS b
join fnuserrank f1
on b.empid = f1.empid
where rank != 'TERM'
) bar
on foo.empId = bar.barempid
and foo.MonitorDate > bar.barmonitordate
where bar.barempid is null

Resources