Why UPDATE does not see result of subquery? [Oracle] - database

After this query updated 0 rows:
UPDATE F_ANSWER fa
SET fa.STATUS_ID = 5
WHERE fa.ANSWER_ID IN (SELECT FA1.ANSWER_ID
FROM F_ANSWER FA1
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID
FROM F_ANSWER FA2
WHERE FA2.QUERY_ID = FA1.QUERY_ID
ORDER BY ANSWER_ID DESC
FETCH FIRST ROW ONLY)
);
but separately SELECT returned 2 rows with ID:
SELECT FA1.ANSWER_ID
FROM F_ANSWER FA1
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID FROM F_ANSWER FA2
Why is that?
Simplified subquery without link from subquery to query works fine:
UPDATE F_ANSWER fa
SET fa.STATUS_ID = 5
WHERE fa.ANSWER_ID IN (SELECT FA1.ANSWER_ID
FROM F_ANSWER FA1
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID
FROM F_ANSWER FA2
ORDER BY ANSWER_ID DESC
FETCH FIRST ROW ONLY)
);
I can resolve my problem with 2 queries - 1 for selecting necessary IDS and 2 for updating.
But it's interesting why update cannot see result of subquery with this structure.

You written two different update-query options: (1) one is written with a correlated subquery, and the other(2) just with a subquery.
I believe, that your data is such that the predicate in upd-query (1)
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID
FROM F_ANSWER FA2
WHERE FA2.QUERY_ID =
FA1.QUERY_ID
ORDER BY ANSWER_ID DESC
FETCH FIRST ROW ONLY)
cuts off the data.
in your main update query this block is a correlated subquery
UPDATE F_ANSWER fa
SET fa.STATUS_ID = 5
WHERE fa.ANSWER_ID IN (SELECT FA1.ANSWER_ID
FROM F_ANSWER FA1
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID
FROM F_ANSWER FA2
WHERE FA2.QUERY_ID =
FA1.QUERY_ID
ORDER BY ANSWER_ID DESC
FETCH FIRST ROW ONLY)
);
in this query, you are using a subquery without correlating by QUERY_ID(
....
WHERE FA2.QUERY_ID = FA1.QUERY_ID
....
):
UPDATE F_ANSWER fa
SET fa.STATUS_ID = 5
WHERE fa.ANSWER_ID IN (SELECT FA1.ANSWER_ID
FROM F_ANSWER FA1
WHERE FA1.ANSWER_ID = (SELECT ANSWER_ID
FROM F_ANSWER FA2
ORDER BY ANSWER_ID DESC
FETCH FIRST ROW ONLY)
);
You can provide an example of your data and formulate your task.
Your UPD-query can be rewritten an easier.

Related

Insert multiple comma separated columns as a single row in SQL server

i have a table like this
CREATE TABLE #tbl(PackId NVARCHAR(MAX),AmntRemain NVARCHAR(MAX),AmntUsed NVARCHAR(MAX),IsCount NVARCHAR(MAX),IsValue NVARCHAR(MAX))
INSERT INTO #tbl VALUES('1,2','10,20','10,20','1,0','0,1')
above table output is
my concern is how to get output like below
how to insert data into a table of above table data as all columns individual value as a independent row
You should never store your data like this. You should really fix your etl processes and database schema per all the comments on your question.
Using cross apply(values ...) to unpivot your data, splitting the strings, and using conditional aggregation to pivot the data back to rows:
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden:
;with cte as (
select
Id = row_number() over (order by (select null)) /* adding an id to uniquely identify rows */
, *
from #tbl
)
select
cte.Id
, s.ItemNumber
, PackId = max(case when u.column_name = 'PackId' then s.item end)
, AmntRemain = max(case when u.column_name = 'AmntRemain' then s.item end)
, AmntUsed = max(case when u.column_name = 'AmntUsed' then s.item end)
, IsCount = max(case when u.column_name = 'IsCount' then s.item end)
, IsValue = max(case when u.column_name = 'IsValue' then s.item end)
from cte
cross apply (values ('PackId',PackId),('AmntRemain',AmntRemain),('AmntUsed',AmntUsed),('IsCount',IsCount),('IsValue',IsValue)) u (column_name,column_value)
cross apply dbo.delimitedsplit8K(u.column_value,',') s
group by cte.Id, s.ItemNumber
rextester demo: http://rextester.com/ZIFFQX41171
returns:
+----+------------+--------+------------+----------+---------+---------+
| Id | ItemNumber | PackId | AmntRemain | AmntUsed | IsCount | IsValue |
+----+------------+--------+------------+----------+---------+---------+
| 1 | 1 | 1 | 10 | 10 | 1 | 0 |
| 1 | 2 | 2 | 20 | 20 | 0 | 1 |
+----+------------+--------+------------+----------+---------+---------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Ordinal workaround for **string_split()** - Solomon Rutzky
You want to insert into 2 rows. Try:
INSERT INTO #tbl VALUES
('1','10','10','1','0')
,('2','20','20','0','1')
Just about any parse/split function will do. The one supplied below also returns an Item Sequence Number which can be used to join and the individual elements in an appropriate row.
The number of elements are NOT fixed, one record could have 2 while another has 5.
I should add, if you can't use or want a UDF, it would be a small matter to create an in-line approach.
Example
Declare #Staging TABLE (PackId NVARCHAR(MAX),AmntRemain NVARCHAR(MAX),AmntUsed NVARCHAR(MAX),IsCount NVARCHAR(MAX),IsValue NVARCHAR(MAX))
INSERT INTO #Staging VALUES
('1,2','10,20','10,20','1,0','0,1')
Select B.*
From #Staging A
Cross Apply (
Select PackId = B1.RetVal
,AmntRemain = B2.RetVal
,AmntUsed = B3.RetVal
,IsCount = B4.RetVal
,IsValue = B5.RetVal
From [dbo].[udf-Str-Parse-8K](A.PackId,',') B1
Join [dbo].[udf-Str-Parse-8K](A.AmntRemain,',') B2 on B1.RetSeq=B2.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.AmntUsed,',') B3 on B1.RetSeq=B3.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.IsCount,',') B4 on B1.RetSeq=B4.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.IsValue,',') B5 on B1.RetSeq=B5.RetSeq
) B
Returns
PackId AmntRemain AmntUsed IsCount IsValue
1 10 10 1 0
2 20 20 0 1
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
You can't, unless your inserting data from another table you will have to create individual insert statements for each row you want to create.

Generate a new Id column in SQL Server that will be dependent on other 2 id columns

I want to generate a new id column as IndexID that will depend on 2 other ids. in my case the 2 other ids are OrderID and PatientOrderID.
This is my current table structure:
In this above image you can see there are 3 rows:
1st row has OrderID of 121336 and PatientOrderID of 230216
2nd row has OrderID of 121337 and PatientOrderID of 230217
3rd row has OrderID of 121337 and PatientOrderID of 230218
I want to add IndexId that will depend on OrderID and PatientOrderID.
For example:
1st row IndexID should be 1
2nd row IndexID should be 1
3rd row IndexId should be 2
This is because for each OrderID the IndexId will start at 1 & if there are 2 rows with same OrderID then we will check PatientOrderID to create the IndexID as 1,2 and so on.
Currently I have used dense_rank which is not correct in my query so please ignore it.
I am I will be able to make you understand my requirements. Any help would be appreciated. Thanks in advance.
Here is my query :
SELECT
O.ID AS OrderID,
smsFDGPatientOrder.ID AS PatientOrderID,
dense_rank() over (order by smsFDGPatientOrder.ID ) as IndexID,
--'Example' AS IndexID,
smsFDGPatientOrder.Isotope,
smsFDGPatientOrder.ActualIsotope,
OS.StatusID StatusID,
NCS.StatusID AS NoChangeStatusID,
smsFDGPatientOrder.[Order] AS FDGOrderID,
smsFDGPatientOrder.ExamDate,
smsFDGPatientOrder.ActualExamDate,
smsFDGPatientOrder.Indication,
smsFDGPatientOrder.[Procedure],
smsFDGPatientOrder.ActualProcedure
FROM
dbo.smsFDGOrder O WITH(NOLOCK)
INNER JOIN
[dbo].[smsFDGPatientOrder] as smsFDGPatientOrder WITH(NOLOCK) ON smsFDGPatientOrder.[Order] = O.[ID]
CROSS APPLY
(SELECT TOP 1 OS.StatusID
FROM dbo.smsFDGOrderStatus OS WITH(NOLOCK)
WHERE OS.FDGOrder = O.ID
ORDER BY OS.TimeAdded DESC, OS.ID DESC) OS
OUTER APPLY
(SELECT TOP 1 OS.StatusID
FROM dbo.smsFDGOrderStatus OS WITH(NOLOCK)
WHERE OS.FDGOrder = O.ID AND OS.StatusID != 3
ORDER BY OS.TimeAdded DESC, OS.ID DESC) NCS
INNER JOIN
dbo.smsStatus S WITH(NOLOCK) ON S.ID = OS.StatusID
LEFT JOIN
dbo.smsStatus NOChange WITH(NOLOCK) ON NOChange.ID = NCS.StatusID
WHERE
(S.Status IN ('In Queue'))
AND (S.Status != 'Changed' OR NOChange.Status IN ('In Queue'))
ORDER BY
O.ID
row_number () over (partition by O.Id order by smsFDGPatientOrder.Id) as IndexId
If I have your requirements right, you are right to use dense_rank. You just need to add a little extra logic:
cast(dense_rank() over (order by OrderID) as nvarchar(10))
+ case when dense_rank() over (order by PatientOrderID) > 1
then N',' + cast(dense_rank() over (partition by OrderID order by PatientOrderID) as nvarchar(10))
else N''
end as IndexID
Which will output 1 for the first OrderID and then 1,2, 1,3, etc for any additional PatientOrderID values in the same OrderID:
OrderID PatientOrderID IndexID
121336 230216 1
121337 230217 2,1
121337 230218 2,2

CASE Statement with a JOIN and GROUP BY

I'm trying to select the winners of a race from an event by the specific competition they entered, for example.
The competition table
competition_ID, eventss_ID, competitor_ID, stageName, roundNo, startTime, finisheTime, judges_ID
The eventss table
eventss_ID, eventsName, noOfStages, eventsDate, entryFee, venue_ID, judges_ID
The results I want are;
Event RoundNo competitior_ID Competiton Winner
swimming 1 COM101 1st Place
swimming 1 COM213 2nd Place
swimming 2 COM101 1st Place
swimming 2 COM234 2nd Place
golf 1 COM654 1st Place
golf 1 COM874 2nd Place
Query I tried:
SELECT *
,CASE
WHEN finshTime = (SELECT MIN(finshTime) FROM competition) THEN '1st Place'
WHEN finshTime = (SELECT MAX(finshTime) FROM competition) THEN '2nd Place'
ELSE 'Draw'
END [Competition Winner]
FROM competition
JOIN eventss on eventss.eventss_ID = competition.eventss_ID
GROUP BY competition.roundNo
This would probably work to produce the desired output but it's hard to tell without proper sample data.
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, CTE2 AS (
SELECT *
, CASE (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO) - (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = c.eventss_ID AND roundNo = c.roundNO)
WHEN 0 THEN 'Draw'
ELSE CASE RN
WHEN 1 THEN '1st Place'
ELSE '2nd Place' END END Drawn
FROM CTE c
WHERE RN IN (1, 2))
SELECT e.eventsName [Event], CTE2.RoundNo, CTE2.competitor_ID, CTE2.Drawn [Competition Winner]
FROM eventss e
JOIN CTE2 ON CTE2.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, CTE2.roundNo, CTE2.Drawn
Note: I'm making the assumption that "finishtime" is stored as TIME or DATETIME. If it's stored as something else, this won't work.
EDIT: In the case of more than two people tied for first place, or in the case of a tie for second place, this query should work...
; WITH CTE AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY eventss_ID, roundNo ORDER BY finishTime) RN
FROM competition)
, tiesforfirst AS (
SELECT *
, '1st Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 1 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo))
, tiesforsecond AS (
SELECT *
, '2nd Place' Drawn
FROM CTE T
WHERE finishTime = (SELECT finishTime FROM CTE WHERE RN = 2 AND eventss_ID = T.eventss_ID AND roundNo = T.roundNo)
AND (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = T.eventss_ID AND roundNo = T.roundNo) = 1)
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforfirst WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 1st' END [Competition Winner]
FROM eventss e
JOIN tiesforfirst tf ON tf.eventss_ID = e.eventss_ID
UNION
SELECT e.eventsName [Event], tf.RoundNo, tf.competitor_ID
, CASE (SELECT COUNT(*) FROM tiesforsecond WHERE eventss_ID = tf.eventss_ID AND roundNo = tf.roundNo) WHEN 1 THEN tf.Drawn ELSE 'Drawn 2nd' END [Competition Winner]
FROM eventss e
JOIN tiesforsecond tf ON tf.eventss_ID = e.eventss_ID
ORDER BY e.eventsName, roundNo

How to modify Row_Number syntax in SQL so that row numbers are assigned based on a condition?

I am using SQL Server 2014 and I have the following T-SQL query:
SELECT
d.PropertyCode,
b.ProfileID,
a.RSY_RESERVATIONSTAYID,
b.StatusCode, c2.CreatedOn AS 'Original CreatedOn',
a.RSY_UDFCHAR07 AS 'Original PMS No',
c2.ReservationStayID AS 'Original Resa ID',
b.CreatedOn,
b.PMSConfirmationNumber,
ROW_NUMBER() OVER (PARTITION BY b.ProfileID ORDER BY Count(*) DESC) AS rownum
FROM
ReservationStay b
LEFT JOIN
P5RESERVATIONSTAY a ON a.RSY_RESERVATIONSTAYID = b.ReservationStayID
LEFT JOIN
GuestNameInfo c on c.Reservationstayid = b.ReservationStayID
LEFT JOIN
RESERVATIONSTAY c2 on c2.PMSConfirmationNumber = a.RSY_UDFCHAR07
LEFT JOIN
GuestStaySummary d ON d.ReservationStayID = b.ReservationStayID
Here is an extract of my output:
PropertyCode ProfileID .... CreatedOn .... rownum
AXL 90072 2015-06-03 14:15:27.000 1
AXL 90072 2015-03-16 19:10:27.000 2
I need the rownum to be assigned based on the CreatedOn dates. In other words, for ProfileID 90072, rownum 1 is to be assigned where CreatedOn = 2015-03-16 and rownum 2 to be assigned where CreatedOn = 2015-06-03.
To simplify, rownum is assigned to a ProfileID in the ascending order of its CreatedOn dates.
How do I modify my row_number syntax to achieve this?
ROW_NUMBER() OVER (PARTITION BY b.ProfileID ORDER BY CreatedOn ASC) AS rownum

Column-values to rows-header without summarzie

I have this simple single table:
Group Name
------------------------------
Group A Marco
Group B Sven
Group A Adrian
Group B Tina
Group B Steffi
Group C Jil
Group C Bastian
and want to rotate is this way. For every value in column Group one new column:
Group A Group B Group C
------------------------------
Adrian Steffi Bastian
Marco Sven Jil
Tina
Column "Group A", "Group B" and "Group C" are alphabetically sorted.
I tried it by "case when end" but the result was this:
Group A Group B Group C
------------------------------
Marco null null
Adrian null null
null Steffi null
null Sven null
null Tina null
null null Jil
null null Bastian
And it isn't sorted, either.
How can I achieve this? I need your help.
the static solution here : (but be carefully, in CTE a must be group having max count of names :))
(if you want sometheng more dynamically, write...)
;WITH CTE AS (SELECT ROW_NUMBER() OVER (partition by [Group] ORDER BY [Group]) AS RN,[Group], Name
FROM YourTable)
SELECT x.Name AS GroupA, a.Name AS GroupB, z.Name AS GroupC
FROM CTE a
OUTER APPLY(SELECT Name FROM CTE b WHERE a.RN = b.RN AND b.[Group] = 'Group A') x
OUTER APPLY(SELECT Name FROM CTE c WHERE a.RN = c.RN AND c.[Group] = 'Group C') z
WHERE a.[Group] = 'Group B'
Okay you can also use this one : edited by your request( but its only for 3 groups...)
DECLARE #param_group1 varchar(50),
#param_group2 varchar(50),
#param_group3 varchar(50)
SET #param_group1 =
(SELECT TOP 1 [Group]
FROM YourTable
GROUP BY [Group]
ORDER BY COUNT([Group]) DESC)
SET #param_group2 =
(SELECT TOP 1 [Group]
FROM YourTable
WHERE [Group] NOT IN (#param_group1)
GROUP BY [Group]
ORDER BY COUNT([Group]) DESC)
SET #param_group3 =
(SELECT TOP 1 [Group]
FROM YourTable
WHERE [Group] NOT IN (#param_group1, #param_group2)
GROUP BY [Group]
ORDER BY COUNT([Group]) DESC)
DECLARE #cmd VARCHAR(MAX)
SET #cmd = '
WITH CTE AS (SELECT ROW_NUMBER() OVER (partition by [Group] ORDER BY [Group]) AS RN,[Group], Name
FROM YourTable)
SELECT x.Name AS ['+#param_group1+'], a.Name AS ['+#param_group2+'], z.Name AS ['+#param_group3+']
into ##tmp1
FROM CTE a
OUTER APPLY(SELECT Name FROM CTE b WHERE a.RN = b.RN AND b.[Group] = '''+#param_group2+''') x
OUTER APPLY(SELECT Name FROM CTE c WHERE a.RN = c.RN AND c.[Group] = '''+#param_group3+''') z
WHERE a.[Group] = '''+#param_group1+''''
IF OBJECT_ID('tempdb..##tmp1') IS NOT NULL DROP TABLE ##tmp1
EXEC (#cmd)
SELECT [Group A],[Group B],[Group C]
FROM ##tmp1

Resources