select to get rows based on minimum value of a column - sql

I have a table WT_RT.
a select on it will give me this result
select * from WT_RT;
WT_SEQ W RT_AMT R
---------- - ---------- -
0 0 125 M
1 2 9.3 N
45 2 7.19 Q
100 1 6.38 Q
300 1 5.11 Q
500 2 4.69 Q
1000 2 4.39 Q
On input of WT_SEQ=250, since 250 is in between 100 and 300 I need to select the row which has the least RT_AMT. In this case, since 5.11 < 6.38, I need to select the row with WT_SEQ = 300.
Expected Result:
WT_SEQ W RT_AMT R
---------- - ---------- -
300 1 5.11 Q
So far I have managed to find the enclosing 2 rows.
select ........;
WT_SEQ W RT_AMT R
---------- - ---------- -
100 1 6.38 Q
300 1 5.11 Q
But I need to implement the condition of minimum value of RT_AMT
How to do it?

You can try to write a subquery to get nextval by LEAD then, use BETWEEN to get between WT_SEQ column, then self join by WT_SEQ
then use Row_NUMBER with Windows Function to make Rownumber by RT_AMT, then get rn = 1
TestDLL
CREATE TABLE WT_RT(
WT_SEQ INT,
RT_AMT FLOAT
);
INSERT INTO WT_RT VALUES (0,125);
INSERT INTO WT_RT VALUES ( 1, 9.3);
INSERT INTO WT_RT VALUES ( 45,7.19);
INSERT INTO WT_RT VALUES ( 100,6.38);
INSERT INTO WT_RT VALUES ( 300,5.11);
INSERT INTO WT_RT VALUES ( 500,4.69);
INSERT INTO WT_RT VALUES (1000,4.39);
Query
WITH CTE1 AS(
SELECT t2.WT_SEQ,t2.RT_AMT,Row_NUMBER() OVER(ORDER BY t2.RT_AMT) rn
FROM (
SELECT * FROM (
SELECT t1.*,LEAD(WT_SEQ,1,WT_SEQ) over(order by WT_SEQ) nextval
FROM WT_RT t1
)t1
WHERE 250 BETWEEN WT_SEQ and nextval
) t1 INNER JOIN WT_RT t2
on t1.WT_SEQ = t2.WT_SEQ or t1.NEXTVAL = t2.WT_SEQ
)
SELECT *
FROM CTE1
WHERE RN = 1
sqlfiddle
[Results]:
| WT_SEQ | RT_AMT | RN |
|--------|--------|----|
| 300 | 5.11 | 1 |

One way is via a top 2 of an order by distance.
Then the top 1 of an order by RT_AMT.
select WT_SEQ, RT_AMT
from
(
select *
from
(
select *
from WT_RT
cross join (select 250 val from dual) v
order by ABS(WT_SEQ - val)
) q1
where rownum <= 2
order by (case when WT_SEQ = val then 1 else 2 end), RT_AMT asc
) q2
where rownum = 1;
The CASE WHEN is added to give priority to an WT_SEQ that's equal to the value.

You can just use aggregation:
select MIN(RT_AMT)
from WT_RT
where WT_SEQ <= 250
If you need the entire row, you can select one row. In Oracle 12C+:
select MIN(RT_AMT)
from WT_RT
where WT_SEQ <= 250
order by WT_SEQ desc
fetch first 1 row only;
In older version:
select x.*
from (select MIN(RT_AMT)
from WT_RT
where WT_SEQ <= 250
order by WT_SEQ desc
) x
where rownum = 1;

Related

Rolling Average in SQL with Partition [duplicate]

declare #t table
(
id int,
SomeNumt int
)
insert into #t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23
select * from #t
the above select returns me the following.
id SomeNumt
1 10
2 12
3 3
4 15
5 23
How do I get the following:
id srome CumSrome
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from #t t1
inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id
SQL Fiddle example
Output
| ID | SOMENUMT | SUM |
-----------------------
| 1 | 10 | 10 |
| 2 | 12 | 22 |
| 3 | 3 | 25 |
| 4 | 15 | 40 |
| 5 | 23 | 63 |
Edit: this is a generalized solution that will work across most db platforms. When there is a better solution available for your specific platform (e.g., gareth's), use it!
The latest version of SQL Server (2012) permits the following.
SELECT
RowID,
Col1,
SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
or
SELECT
GroupID,
RowID,
Col1,
SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
This is even faster. Partitioned version completes in 34 seconds over 5 million rows for me.
Thanks to Peso, who commented on the SQL Team thread referred to in another answer.
For SQL Server 2012 onwards it could be easy:
SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM #t
because ORDER BY clause for SUM by default means RANGE UNBOUNDED PRECEDING AND CURRENT ROW for window frame ("General Remarks" at https://msdn.microsoft.com/en-us/library/ms189461.aspx)
Let's first create a table with dummy data:
Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)
Now let's insert some data into the table;
Insert Into CUMULATIVESUM
Select 1, 10 union
Select 2, 2 union
Select 3, 6 union
Select 4, 10
Here I am joining same table (self joining)
Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc
Result:
ID SomeValue SomeValue
-------------------------
1 10 10
2 2 10
2 2 2
3 6 10
3 6 2
3 6 6
4 10 10
4 10 2
4 10 6
4 10 10
Here we go now just sum the Somevalue of t2 and we`ll get the answer:
Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc
For SQL Server 2012 and above (much better performance):
Select
c1.ID, c1.SomeValue,
Sum (SomeValue) Over (Order By c1.ID )
From CumulativeSum c1
Order By c1.id Asc
Desired result:
ID SomeValue CumlativeSumValue
---------------------------------
1 10 10
2 2 12
3 6 18
4 10 28
Drop Table CumulativeSum
A CTE version, just for fun:
;
WITH abcd
AS ( SELECT id
,SomeNumt
,SomeNumt AS MySum
FROM #t
WHERE id = 1
UNION ALL
SELECT t.id
,t.SomeNumt
,t.SomeNumt + a.MySum AS MySum
FROM #t AS t
JOIN abcd AS a ON a.id = t.id - 1
)
SELECT * FROM abcd
OPTION ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.
Returns:
id SomeNumt MySum
----------- ----------- -----------
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
Late answer but showing one more possibility...
Cumulative Sum generation can be more optimized with the CROSS APPLY logic.
Works better than the INNER JOIN & OVER Clause when analyzed the actual query plan ...
/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP
SELECT * INTO #TMP
FROM (
SELECT 1 AS id
UNION
SELECT 2 AS id
UNION
SELECT 3 AS id
UNION
SELECT 4 AS id
UNION
SELECT 5 AS id
) Tab
/* Using CROSS APPLY
Query cost relative to the batch 17%
*/
SELECT T1.id,
T2.CumSum
FROM #TMP T1
CROSS APPLY (
SELECT SUM(T2.id) AS CumSum
FROM #TMP T2
WHERE T1.id >= T2.id
) T2
/* Using INNER JOIN
Query cost relative to the batch 46%
*/
SELECT T1.id,
SUM(T2.id) CumSum
FROM #TMP T1
INNER JOIN #TMP T2
ON T1.id > = T2.id
GROUP BY T1.id
/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT T1.id,
SUM(T1.id) OVER( PARTITION BY id)
FROM #TMP T1
Output:-
id CumSum
------- -------
1 1
2 3
3 6
4 10
5 15
Select
*,
(Select Sum(SOMENUMT)
From #t S
Where S.id <= M.id)
From #t M
You can use this simple query for progressive calculation :
select
id
,SomeNumt
,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from #t
There is a much faster CTE implementation available in this excellent post:
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx
The problem in this thread can be expressed like this:
DECLARE #RT INT
SELECT #RT = 0
;
WITH abcd
AS ( SELECT TOP 100 percent
id
,SomeNumt
,MySum
order by id
)
update abcd
set #RT = MySum = #RT + SomeNumt
output inserted.*
For Ex: IF you have a table with two columns one is ID and second is number and wants to find out the cumulative sum.
SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Once the table is created -
select
A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
from #t A, #t B where A.id >= B.id
group by A.id, A.SomeNumt
order by A.id
The SQL solution wich combines "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" and "SUM" did exactly what i wanted to achieve.
Thank you so much!
If it can help anyone, here was my case. I wanted to cumulate +1 in a column whenever a maker is found as "Some Maker" (example). If not, no increment but show previous increment result.
So this piece of SQL:
SUM( CASE [rmaker] WHEN 'Some Maker' THEN 1 ELSE 0 END)
OVER
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT
Allowed me to get something like this:
User 1 Rank1 MakerA 0
User 1 Rank2 MakerB 0
User 1 Rank3 Some Maker 1
User 1 Rank4 Some Maker 2
User 1 Rank5 MakerC 2
User 1 Rank6 Some Maker 3
User 2 Rank1 MakerA 0
User 2 Rank2 SomeMaker 1
Explanation of above: It starts the count of "some maker" with 0, Some Maker is found and we do +1. For User 1, MakerC is found so we dont do +1 but instead vertical count of Some Maker is stuck to 2 until next row.
Partitioning is by User so when we change user, cumulative count is back to zero.
I am at work, I dont want any merit on this answer, just say thank you and show my example in case someone is in the same situation. I was trying to combine SUM and PARTITION but the amazing syntax "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" completed the task.
Thanks!
Groaker
Above (Pre-SQL12) we see examples like this:-
SELECT
T1.id, SUM(T2.id) AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
T1.id
More efficient...
SELECT
T1.id, SUM(T2.id) + T1.id AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
T1.id
Try this
select
t.id,
t.SomeNumt,
sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from
#t t
group by
t.id,
t.SomeNumt
order by
t.id asc;
Try this:
CREATE TABLE #t(
[name] varchar NULL,
[val] [int] NULL,
[ID] [int] NULL
) ON [PRIMARY]
insert into #t (id,name,val) values
(1,'A',10), (2,'B',20), (3,'C',30)
select t1.id, t1.val, SUM(t2.val) as cumSum
from #t t1 inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.val order by t1.id
Without using any type of JOIN cumulative salary for a person fetch by using follow query:
SELECT * , (
SELECT SUM( salary )
FROM `abc` AS table1
WHERE table1.ID <= `abc`.ID
AND table1.name = `abc`.Name
) AS cum
FROM `abc`
ORDER BY Name

how to return the max seqence record

I've a table that stores the historical data, what i'm trying to do is trying to capture the max seq record. i can do that, but i need to include the tr_type, then i'll use the outupt to join with another table. below is ex of my data
CLM_NO SEQ SUB TR_TYPE
12345 1 1 50
12345 1 2 50
12345 2 1 60
12345 2 2 60
i want to return only the last row
You can try to use exists and correlated subquery
SELECT *
FROM T t1
WHERE exists(
SELECT 1
FROM T tt
GROUP BY tt.CLM_NO
HAVING MAX(tt.SEQ) = t1.SEQ AND MAX(tt.SUB) = t1.SUB
)
EDIT
You can try to use ROW_NUMBER window function.
SELECT * FROM (
SELECT *,ROW_NUMBER() OVER(PARTITION BY CLM_NO ORDER BY TRAN_SEQ DESC,TRAN_SUB DESC) rn
FROM TBL t1
)t1
where rn = 1

Oracle Remove Consecutive duplicates

I have table in which I store the evauation results of customer. Evaluation can be triggered multiple times. Below is the sample data
CUSTOMER_EVAL_RESULTS:
SEQ CUSTOMER_ID STATUS RESULT
1 100 C XYZ
3 100 C XYZ
7 100 C ABC
8 100 C PQR
11 100 C ABC
12 100 C ABC
From above data set I want only the rows with SEQ as 1,7,8,11.
I used below query suggested on other links but it is not giving the desired result. Please help
SELECT * FROM (
SELECT E.*, ROW_NUMBER() OVER(PARTITION BY CUSTOMER_ID, STATUS, RESULT ORDER BY SEQ) ROW_NUM
FROM CUSTOMER_EVAL_RESULTS E WHERE E.CUSTOMER_ID=100
) WHERE ROW_NUM=1;
You can utilize LAG to check the previous row's value:
SELECT *
FROM
(
SELECT E.*,
LAG(RESULT)
OVER(PARTITION BY CUSTOMER_ID, STATUS
ORDER BY SEQ) prevResult
FROM CUSTOMER_EVAL_RESULTS E
WHERE E.CUSTOMER_ID=100
)
WHERE prevResult IS NULL
OR prevResult <> RESULT
Please try the below
select * from CUSTOMER_EVAL_RESULTS
where not exists (select 1 from CUSTOMER_EVAL_RESULTS
a,CUSTOMER_EVAL_RESULTS b
where a.seq_no < b.seq_no and a.customer_id=b.customer_id
and a.status=b.status and a.result=b.result
and not exists(select 1 from CUSTOMER_EVAL_RESULTS c
where a.seq_no < c.seq_no and c.seq_no < b.seq_no ));

SQL: How do I display all records per unique id, but not the first record ever recorded in SQL

Example:
id Pricemoney time/date
1 100 01/20/2017
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017
2 100 01/24/2017
3 1000 01/19/2017
3 100 01/22/2017
3 10 01/24/2017
I want to run a SQL query where I can display all the Id and it's pricemoney BUT NOT include the first record (based on time/date) per unique
Just to clarify what I do not want to be displayed
userid Pricemoney issuedate
1 100 01/20/2017 -- not included
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017 --- not inlcuded
2 100 01/24/2017
3 1000 01/19/2017 -- not included
3 100 01/22/2017
3 10 01/24/2017
Expected result:
id Pricemoney time/date
1 10 01/21/2017
1 1000 01/21/20147
2 100 01/24/2017
3 100 01/22/2017
3 10 01/24/2017
You can use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum
from <tablename> t
) t
where seqnum > 1;
If you want to keep single rows, you can do:
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum,
count(*) over (partition by id) as cnt
from <tablename> t
) t
where seqnum > 1 and cnt > 1;
You may use EXISTS
select t1.*
from data t1
where exists (
select 1
from data t2
where t1.id = t2.id and t2.time_date < t1.time_date
)
you can try this :
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney
above query not duplicated records also ignore, if want
not duplicated records then use having count(id) > 1 in left query e,g.
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
having COUNT(id) > 1
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney

SQL Rank() function excluding rows

Consider I have the following table.
ID value
1 100
2 200
3 200
5 250
6 1
I have the following query which gives the result as follows. I want to exclude the value 200 from rank function, but still that row has to be returned.
SELECT
CASE WHEN Value = 200 THEN 0
ELSE DENSE_RANK() OVER ( ORDER BY VALUE DESC)
END AS RANK,
ID,
VALUE
FROM #table
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
But I want the result as follows. How to achieve it?
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
If VAL column is not nullable, taking into account NULL is the last value in ORDER BY .. DESC
select *, dense_rank() over (order by nullif(val,200) desc) * case val when 200 then 0 else 1 end
from myTable
order by val desc;
There is no way to exclude Val in Dense Rank currently ,unless you filter in where clause..that is the reason ,you get below result
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
You will need to filter once and then do a union all
;with cte(id,val)
as
(
select 1, 100 union all
select 2, 200 union all
select 3, 200 union all
select 5, 250 union all
select 6, 1 )
select *, dense_rank() over (order by val desc)
from cte
where val<>200
union all
select 0,id,val from cte where val=200
You could split the ranking in to separate queries for the values you want to include/exclude from the ranking and UNION ALL the results like so:
Standalone executable example:
CREATE TABLE #temp ( [ID] INT, [value] INT );
INSERT INTO #temp
( [ID], [value] )
VALUES ( 1, 100 ),
( 2, 200 ),
( 3, 200 ),
( 5, 250 ),
( 6, 1 );
SELECT *
FROM ( SELECT 0 RANK ,
ID ,
value
FROM #temp
WHERE value = 200 -- set rank to 0 for value = 200
UNION ALL
SELECT DENSE_RANK() OVER ( ORDER BY value DESC ) AS RANK ,
ID ,
value
FROM #temp
WHERE value != 200 -- perform ranking on records != 200
) t
ORDER BY value DESC ,
t.ID
DROP TABLE #temp
Produces:
RANK ID value
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
You can modify the ordering at the end of the statement if required, I set it to produce your desired results.
You can also try this, too:
SELECT ISNULL(R, 0) AS Rank ,t.id ,t.value
FROM tbl1 AS t
LEFT JOIN ( SELECT id ,DENSE_RANK() OVER ( ORDER BY value DESC ) AS R
FROM dbo.tbl1 WHERE value <> 200
) AS K
ON t.id = K.id
ORDER BY t.value DESC
The solution in the original question was actually pretty close. Just adding a partition clause to the dense_rank can do the trick.
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
The 'partition by' creates separate groups for the dense_rank such that the order is performed on these groups individually. This essentially means you create two ranks at the same time, one for the group without the 200 value and one for the group with only the 200 value. The latter one to be set to 0 in the 'case when'.
Standalone executable example:
DECLARE #table TABLE
(
ID INT NOT NULL PRIMARY KEY
,VALUE INT NULL
)
INSERT INTO #table
(
ID
,VALUE
)
SELECT 1, 100
UNION SELECT 2, 200
UNION SELECT 3, 200
UNION SELECT 5, 250
UNION SELECT 6, 1;
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1