I need the most recent value in a GROUP BY - CASE expression - sql

I'm trying to make a CASE expression in T-SQL that's in a GROUP BY clause, that's basically asking if there's a a.id THEN provide b.name associated with it (where a.id = b.id) .
What I have so far is: (Updated Query)
SELECT b.name, ...
MAX(CASE
WHEN a.id IS NOT NULL
THEN b.name END)
FROM ...
LEFT JOIN table_b AS b
ON b.id = a.id
GROUP BY ...
Because it's T-SQL, the CASE has to be in a aggregate function or GROUP BY clause, which is why I included the MAX. However, without the GROUP BY clause, there would be 4 values. I need the most recent value as defined by a.datetime. How can I put that condition in the CASE statement?

Your last edit changes quite a lot. You should use ROW_NUMBER for this:
WITH CTE AS
(
SELECT b.name, ...
RN = ROW_NUMBER() OVER( PARTITION BY a.id
ORDER BY b.DateColumn DESC)
FROM ...
LEFT JOIN table_b AS b
ON b.id = a.id
)
SELECT *,
CASE
WHEN a.id IS NOT NULL
THEN b.name
END
FROM CTE
WHERE RN = 1;

If I understood you correctly(From now on try including the full query, table data\structure, input and desired output) , you can join them from the beggining and then just pick MAX() :
SELECT ....
MAX(b.name)
FROM YourTable a
LEFT JOIN YourTable b
ON(a.id = b.id)
...
GROUP BY ....
If there's a match, the name will be fetched. Otherwise - it will be NULL .

Related

SQL Select first that matches the criteria

I have a query where in I have to sort the table and retrieve the first value that matches for every id.
The scenario that I would like to achieve is to get the ID of Table A that matches the first ID_2 from the sorted Table B
I have a slight concept of the code.
select A.ID, A.COL1, B.COL1, B.COL2
from A, B
where A.ID = B.ID
and B.ID_2 = (select ID_2
from (select ID_2
from B B2
where B2.ID = A.ID
order by (case when B2.PRIO ...))
where rownum = 1)
The problem here is A.ID is not accessible within the select in where clause.
Another way that I found was using analytic function
select ID, COL1, COL2
from (select A.ID, A.COL1, B.COL2,
row_number() over (partition by A.ID order by (case when B.PRIO ...) row_num
from A, B
where A.ID = B.ID)
where row_num = 1
The problem with this code is I think it is not good performance wise.
Can anyone help me? =)
row_number() is not a statistic function. It is an analytic or window function. It is probably your best bet. I would do:
select a.*
from A join
(select b.*,
row_number() over (partition by b.ID order by (case when b.PRIO ...) as seqnum
from b
) b
on A.ID = B.ID and b.seqnum = 1;
If you really only want A.ID, then you don't need A at all . . . the information is in B.ID (assuming it is not being used for filtering). The above then simplifies to:
select b.id
from (select b.*,
row_number() over (partition by b.ID order by (case when b.PRIO ...) as seqnum
from b
) b
where b.seqnum = 1;
You don't need a correlated sub-subquery (which is invalid in Oracle), and you don't need an analytic function either. You need the aggregate first/last function.
... and b.id_2 = (select max(id_2) keep (dense_rank first order by case.....)
from b b2
where b2.id = a.id
) .....
Even this is probably too complicated. If you would describe your requirement (instead of just posting some incomplete code), the community may be able to help you simplify the query even further.

Oracle rownum returning wrong result

I am trying a query to return only the latest row from table.
Initially I used max(id) in query
But as I use sequence and my envoirnment is clustered, I cannot rely on sequence as its out of order.
So I decided to order based on creation time and pick top row using rownum.
I used something like
SELECT A.id
FROM Table_A, Table_B B
WHERE A.status = 'COMPLETED'
AND B.name = 'some_name'
AND A.id = B.id
AND rownum = 1
order by A.Creation_Time;
This some how returns me some wrong result say 42145.
If I remove the rownum condtn the top record is differnet say 45343;
When using rownum with order by, you need to use a subquery. This has to do with the order of evaluation of the where and order by. So, try this:
SELECT t.*
FROM (SELECT A.id
FROM Table_A JOIN
Table_B B
ON A.id = B.id
WHERE A.status = 'COMPLETED' AND B.name = 'some_name'
ORDER BY A.Creation_Time
) ab
WHERE rownum = 1;
I should add: Oracle 12 supports fetch first 1 row only, which is more convenient:
SELECT A.id
FROM Table_A JOIN
Table_B B
ON A.id = B.id
WHERE A.status = 'COMPLETED' AND B.name = 'some_name'
ORDER BY A.Creation_Time
FETCH FIRST 1 ROW ONLY;
Any chance you meant to do this? (Specifying to order DESC)
SELECT A.id
FROM Table_A, Table_B B
WHERE A.status = 'COMPLETED'
AND B.name = 'some_name'
AND A.id = B.id
AND rownum = 1
order by A.Creation_Time DESC;
EDIT: Obviously specifying order DESC was critical to your query. I had to upvote Gordon also, since he's completely correct that you need to limit the rows returned after sorting, so the methods he suggests are perfect for that. So, for anyone reading this far, I wanted to leave no doubt that rownum is assigned after the where clause is processed but before sorting or aggregation (source).
NB: As good as Gordon's answer is, there is one more consideration that may or may not be important here. It seems that it might be possible to have duplicate values of A.Creation_Time, which could cause you to see non-deterministic behavior as far as which of the duplicate rows might be returned when executing that query. If that problem might arise, Tom Kyte (same link as earlier) suggests adding some unique column value to the order by clause as an easy dodge. For example:
SELECT t.id
FROM (SELECT *
FROM (SELECT A.id, A.Creation_Time
FROM Table_A A
JOIN Table_B B
ON A.id = B.id
WHERE A.status = 'COMPLETED' AND B.name = 'some_name'
)
ORDER BY Creation_Time, rowid DESC
) t
WHERE rownum = 1;
Where ROWID is a unique pseudo column that can serve the purpose.

SQL Subquery to get first record

I need to execute a query something like below.
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
(SELECT * FROM (
SELECT ROUND(LAST_ORDER_AMOUNT,5) FROM ORDERS WHERE ID=A.id AND REQUEST_LEVEL='N' ORDER BY O_DATE DESC)
WHERE ROWNUM =1) AS AMOUNT
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
But this gives me A.ID is invalid error in oracle. I need to get the first record from inner query as it will return multiple records.
Can someone please let me know how can i bind these tables to achieve my goal.
Thank you in advance.
You can rewrite subquery using WITH clause, not exactly sure on syntax but should be something like following.
WITH AmountQuery
AS (
SELECT ID
,ROUND(LAST_ORDER_AMOUNT, 5) AS AmountValue
,ROW_NUMBER() OVER ( ORDER BY O_DATE DESC ) AS RN
FROM ORDERS
WHERE REQUEST_LEVEL = 'N'
)
SELECT TO_CHAR(ROWNUM)
,A.Name
,B.Order
,C.AmountValue
FROM Table1 A
LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN AmountQuery C
ON a.ID = c.ID
AND c.RN = 1
here is SQLFiddle to show how it works.
http://sqlfiddle.com/#!4/696b6/36
Probably, LIMIT will do the job for you selecting just one record from the subquery (It worked for me in MySQL. I do not have Oracle, but I think it may be similar). Try something like this:
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
COALESCE( C.AMOUNT ) as AMOUNT,
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN ( SELECT ROUND(LAST_ORDER_AMOUNT,5) AS AMOUNT FROM ORDERS WHERE REQUEST_LEVEL='N' ORDER BY O_DATE DESC ) C ON C.ID = A.id
group by A.id;

Select DISTINCT or UNIQUE records or rows in Oracle

I want to select distinct or unique records from a database I am querying. How can I do this but at the same time select the entire record instead of just the column that I am distinguishing as unique? Do I have to do unruly joins?
Depending on the database that you are using, you can use window functions. If you want only rows that never repeat:
select t.*
from (select t.*,
count(*) over (partition by <id>) as numdups
from t
) t
where numdups = 1
If you want one example of each row:
select t.*
from (select t.*,
row_number(*) over (partition by <id> order by <id>) as seqnum
from t
) t
where seqnum = 1
If you don't have window functions, you can get the same thing done with "unruly joins".
If you want only one column out of several to be unique and you have joins that might include multiple records, then you have to determine which of the two or more values you want the query to provide. This can be done with aggregate functions, with correlated sub-queries or derived tables or CTEs (In SQL Server not sure if Oracle has those).
But you have to determine which value you want before you write the query. Once you know that then you probably know how to get it.
Here are some quick examples (I used SQL Server coding conventions but most of this should make sense in Oracle as it is all basic SQL, Oracle may have a different way of declaring a parameter):
select a.a_id, max (b.test) , min (c.test2)
from tablea a
join tableb b on a.a_id = b.a_id
join tablec c on a.a_id = c.a_id
group by a.a_id
order by b.test, c.test2
Select a.a_id, (select top 1 b.test from tableb b where a.a_id = b.a_id order by test2),
(select top 1 b.test2 from tableb b where a.a_id = b.a_id order by test2),
(select top 1 c.test3 from tablec c where a.a_id = c.a_id order by test4)
from tablea a
declare #a_id int
set #a_id = 189
select a.a_id , b.test, b.test4
from tablea a
join tableb b on a.a_id = b.a_id
join (select min(b.b_id) from tableb b where b.a_id = #a_id order by b.test3) c on c.b_id = b.b_id
where a.a_id = #a_id
In the second example
select t.*
from (select t.*,
row_number() over (partition by id order by id ) as seqnum
from t
) t
where seqnum = 1
the row_number() must be without star in the braces.

SQL: Turn a subquery into a join: How to refer to outside table in nested join where clause?

I am trying to change my sub-query in to a join where it selects only one record in the sub-query. It seems to run the sub-query for each found record, taking over a minute to execute:
select afield1, afield2, (
select top 1 b.field1
from anothertable as b
where b.aForeignKey = a.id
order by field1
) as bfield1
from sometable as a
If I try to only select related records, it doesn't know how to bind a.id in the nested select.
select afield1, afield2, bfield1
from sometable a left join (
select top 1 id, bfield, aForeignKey
from anothertable
where anothertable.aForeignKey = a.id
order by bfield) b on
b.aForeignKey = a.id
-- Results in the multi-part identifier "a.id" could not be bound
If I hard code values in the nested where clause, the select duration drops from 60 seconds to under five. Anyone have any suggestions on how to join the two tables while not processing every record in the inner table?
EDIT:
I ended up adding
left outer join (
select *, row_number() over (partition by / order by) as rank) b on
b.aforeignkey = a.id and b.rank = 1
went from ~50 seconds to 8 for 22M rows.
Try this:
WITH qry AS
(
SELECT afield1,
afield2,
b.field1 AS bfield1,
ROW_NUMBER() OVER(PARTITION BY a.id ORDER BY field1) rn
FROM sometable a LEFT JOIN anothertable b
ON b.aForeignKey = a.id
)
SELECT *
FROM qry
WHERE rn = 1
Try this
select afield1,
afield2,
bfield1
from sometable a
left join
(select top 1 id, bfield, aForeignKey from anothertable where aForeignKey in(a.id) order by bfield) b on b.aForeignKey = a.id