Using row_number() in subquery results in ORA-00913: too many values - sql

In Oracle, I wish to do something like the SQL below. For each row in "criteria," I want to find the latest row in another table (by last_modified_date) for the same location_id, and use that value to set default_start_interval. Or, if there is no such value, then use 30. However, as you can see, the subquery must have two values in the select statement to use row_number(). That causes an error. How do I reformat it so that it works?
update criteria pc set default_start_interval =
COALESCE(
(SELECT start_interval,
row_number() over(partition by aday.location_id
order by atime.last_modified_date desc
) as rn
FROM available_time atime
JOIN available_day aday ON aday.available_day_id = atime.available_day_id
WHERE aday.location_id = pc.location_id
and rn = 1)
, 30)

There are two issues in your update query:
The update expects only one value per row for default_start_interval, however, you have two columns in the select list.
The row number should be assigned before in the inner query, and then apply filter where rn = 1 in outer query.
Your update query should look like:
UPDATE criteria pc
SET default_start_interval = NVL(
(
SELECT start_interval FROM(
SELECT
start_interval, ROW_NUMBER() OVER(
PARTITION BY aday.location_id
ORDER BY atime.last_modified_date DESC
) AS rn
FROM
available_time atime
JOIN available_day aday ON aday.available_day_id = atime.available_day_id
WHERE
aday.location_id = pc.location_id
)
WHERE rn = 1)
, 30)
Note: You could simply use NVL instead of COALESCE as you only have one value to check for NULL. COALESCE is useful when you have multiple expressions.

I think a simpler method uses aggregation and keep to get the value you want:
update criteria pc
set default_start_interval =
(select coalesce(max(start_interval) keep (dense_rank first order by atime.last_modified_date desc), 30)
from available_time atime join
available_day aday
on aday.available_day_id = atime.available_day_id
where aday.location_id = pc.location_id
);
An aggregation query with no GROUP always returns one row. If no rows match, then the returned value is NULL -- the COALESCE() captures this case.

Related

Listing multiple columns in a single row in SQL

(select ID,EXTERNAL_TRANSACTION_ID,EXTERNAL_TRANSACTION_TYPE,ROW_NUMBER() OVER(PARTITION BY EXTERNAL_TRANSACTION_ID ORDER BY ID ) AS SEQNUM
from AC_POS_TRANSACTION_TRK aptt WHERE [RESULT] ='Success'
GROUP BY ID, EXTERNAL_TRANSACTION_ID,EXTERNAL_TRANSACTION_TYPE )
Hello,
On above query, I want to get rows of transaction id's which has seqnum=1 and seqnum=2
But if that transaction id has no second row (seqnum=2), I dont want to get any row for that transaction id.
Thanks!!
Something like this
Not 100% sure if this is correct without you table definition, but my understanding is that you want to EXCLUDE records if that record has an entry with seqnum=2 -- you can't use a where clause alone because that would still return seqnum = 1.
You can use an exists /not exists or in/not in clause like this
(select ID,EXTERNAL_TRANSACTION_ID,EXTERNAL_TRANSACTION_TYPE,ROW_NUMBER() OVER(PARTITION BY EXTERNAL_TRANSACTION_ID ORDER BY ID ) AS SEQNUM
from AC_POS_TRANSACTION_TRK aptt WHERE [RESULT] ='Success'
and not exists ( select 1 from AC_POS_TRANSACTION_TRK a where a.id = aptt.id
and a.seqnum = 2)
GROUP BY ID, EXTERNAL_TRANSACTION_ID,EXTERNAL_TRANSACTION_TYPE )
basically what this does is it excludes records if a record exists as specified in the NOT EXISTS query.
One option you can try is to add a count of rows per group using the same partioning critera and then filter accordingly. Not entirely sure about your query without seeing it in context and with sample data - there's no aggregation so why use group by?
However can you try something along these lines
select * from (
select ID,EXTERNAL_TRANSACTION_ID,EXTERNAL_TRANSACTION_TYPE,
Row_Number() over(partition by EXTERNAL_TRANSACTION_ID order by ID) as SEQNUM,
Count(*) over(partition by EXTERNAL_TRANSACTION_ID) Qty
from AC_POS_TRANSACTION_TRK
where [RESULT] ='Success'
)x
where SEQNUM in (1,2) and Qty>1
This should do the job.
With Qry As (
-- Your original query goes here
),
Select Qry.*
From Qry
Where Exists (
Select *
From Qry Qry1
Where Qry1.EXTERNAL_TRANSACTION_ID = Qry.EXTERNAL_TRANSACTION_ID
And Qry1.SEQNUM = 1
)
And Exists (
Select *
From Qry Qry2
Where Qry2.EXTERNAL_TRANSACTION_ID = Qry.EXTERNAL_TRANSACTION_ID
And Qry2.SEQNUM = 2
)
BTW, your original query looks problematic to me, specifically I think that instead of a GROUP BY columns those columns should be in the PARTITION BY clause of the OVER statement, but without knowing more about the table structures and what you're trying to achieve, I could not say for sure.

How to select duplicates by first order of appearance

I am looking to select unique values from a SQL database but I want to make sure that I am selecting only the first duplicate in order of appearance (in my case - date in the hospital, intime col)
You can see the code below.
I am trying to take only the IDs of the first time the patients were hospitalized which correspond to the "intime" col.
I have no absolute way to check that by ordering as I did and by using groupby, SQL will in fact return the id in the same order.
Thank you very much.
WITH ccupatients AS
(SELECT HADM_ID
FROM `physionet-data.mimiciii_clinical.icustays` i
WHERE first_careunit = 'CCU'
ORDER BY intime)
SELECT hadm_id
FROM ccupatients
GROUP BY hadm_id
Use ROW_NUMBER() if your RDBMS supports it: this works by ranking records by increasing intime within groups of records having the same ham_id, and then filtering in the outer query on the top record per group:
SELECT hadm_id
FROM (
SELECT hadm_id, ROW_NUMBER() OVER(PARTITION BY hadm_id ORDER BY intime) rn
FROM `physionet-data.mimiciii_clinical`.icustays
WHERE first_careunit = 'CCU'
) x
WHERE rn = 1
If you RDBMS does not support window functions such as ROW_NUMBER(), another option is to use a NOT EXISTS condition with a correlated subquery:
SELECT hadm_id
FROM `physionet-data.mimiciii_clinical`.icustays i
WHERE
first_careunit = 'CCU'
AND NOT EXISTS (
SELECT 1
FROM `physionet-data.mimiciii_clinical`.icustays i1
WHERE
i1.first_careunit = 'CCU'
AND i1.hadm_id = i.hadm_id
AND i1.intime < i.intime
)

Max() not filtering out MIN()

when I use run the query below, it returns duplicate StockNo's because some of them have duplicate WorkInProgress codes (FiWipStatus Code).
Is there a way to exclude the record based on the the MIN() on rowlastupdated?
as always, appreciate any help!
SELECT dbo.InventoryVehicle.StockNo, dbo.VehicleSales.FiWipStatusCode,
MAX(dbo.VehicleSales.RowLastUpdated) AS Expr1
FROM dbo.VehicleSales RIGHT OUTER JOIN
dbo.InventoryVehicle ON dbo.VehicleSales.StockNo = dbo.InventoryVehicle.StockNo
GROUP BY dbo.InventoryVehicle.StockNo, dbo.VehicleSales.FiWipStatusCode,
dbo.VehicleSales.RowLastUpdated
If I got it correctly, you need to get the records based on their last update date and time (which is RowLastUpdated). if so, you can do something like this :
SELECT
iv.StockNo
, vs.FiWipStatusCode
, vs.RowLastUpdated
FROM (
SELECT
iv.StockNo
, vs.FiWipStatusCode
, vs.RowLastUpdated
, ROW_NUMBER() OVER(PARTITION BY iv.StockNo ORDER BY vs.RowLastUpdated DESC) AS RN
FROM
VehicleSales vs
LEFT JOIN InventoryVehicle iv ON vs.StockNo = iv.StockNo
) D
WHERE
RN = 1
where ROW_NUMBER() will number the rows based on StockNo and order them based on RowLastUpdated in DESC. So, the first row of each distinct StockNo will be the MAX() datetime in your aggregation query. if you want to get the MIN() just change the order to ASC

SQL Server - Update a whole column using Order By

I'm using SQL Server and have Management Studio installed if this is relevant.
I would like to copy a whole column from one table to another, but the catch is that the table I must copy to needs to be ordered a certain way, as there is no common identity between these tables I could use to join them.
I have read these two questions:
Copy data from one column to other column (which is in a different table)
SQL Server: UPDATE a table by using ORDER BY
and I tried to combine their answers as follows:
WITH cte AS
(
-- I must specify TOP to use ORDER BY
SELECT TOP(50000) *
FROM TableToCopyTo
ORDER BY ColumnUsedToOrder
)
UPDATE cte
SET ColumnToCopyTo = (SELECT ColumnToCopyFrom FROM TableToCopyFrom)
When I try to execute this query, it returns the following error:
Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I've tried looking up the error but couldn't find relevant information.
I would like to either understand why my query is wrong or find an alternative to achieve what I'm looking for.
Try the following answer. You have to give the matching columns for the two tables.
;WITH cte AS
(
-- I must specify TOP to use ORDER BY
SELECT TOP(50000) *
FROM TableToCopyTo
ORDER BY ColumnUsedToOrder
)
UPDATE cte SET ColumnToCopyTo = ColumnToCopyFrom
FROM cte
JOIN TableToCopyFrom A
ON cte.ColumnName = A.EquvaliantColumnName
Provided that your TableToCopyFrom has equal to or more rows than TableToCopyTo I'd use something like:
WITH cte1 AS
(
-- I must specify TOP to use ORDER BY
SELECT TOP(50000) *
, ROW_NUMBER() OVER (ORDER BY ColumnUsedToOrder) AS RwNr
FROM TableToCopyTo
ORDER BY ColumnUsedToOrder
), cte2 AS (
SELECT *
, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RwNr -- or order by a column you know makes sense; this way it'll order it the way the data "sits" in the table, but it's not guaranteed
FROM TableToCopyFrom
)
UPDATE cte1
SET ColumnToCopyTo = ColumnToCopyFrom
FROM cte1
INNER JOIN cte2 ON cte1.RwNr = cte2.RwNr;
EDIT: please make sure you test this logic on some copy of the database first of course.
you can use Row number as key as follows
WITH tblDest AS
(
SELECT Row_Number() over(order by ColumnUsedToOrder) as RowNum, TOP(50000) *
FROM TableToCopyTo
ORDER BY ColumnUsedToOrder
),tblSrc as
(
select Row_Number() over(order by (select null)) as RowNum,ColumntoCopyFrom
from TableToCopyFrom
)
update tblDest set ColumntoCopyto = ColumntoCopyFrom
FROM tblDest join tblSrc on tblDest.RowNum = tblSrc.RowNum

SQL - aggregate function to get value from same row as MAX()

I have one table with columns channel, value and timestamp, and another table with 7 other columns with various data.
I'm joining these two together, and I want to select the maximum value of the value column within an hour, and the timestamp of the corresponding row. This is what I've tried, but it (obviously) doesn't work.
SELECT
v.channel,
MAX(v.value),
v.timestamp,
i.stuff,
...
FROM
Values v
INNER JOIN
#Information i
ON i.type = v.type
GROUP BY channel, DATEPART(HOUR, timestamp), i.stuff, ...
I'm (not very surprisingly) getting the following error:
"dbo.Values.timestamp" is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
How should I do this correctly?
You could use the RANK() or DENSE_RANK() features to get the results as appropriate. Something like:
;WITH RankedResults AS
(
SELECT
channel,
value,
timestamp,
type,
RANK() OVER (PARTITION BY DATEPART(hour,timestamp) ORDER BY value desc) as Position
FROM
Values
)
SELECT
v.channel,
v.value,
v.timestamp,
i.stuff
/* other columns */
FROM
RankedResults v
inner join
#Information i
on
v.type = i.type
WHERE
v.Position = 1
(whether to use RANK or DENSE_RANK depends on what you want to do in the case of ties, really)
(Edited the SQL to include the join, in response to Tomas' comment)
you must include 'v.timestamp' in the Group By clause.
Hope this will help for you.