How to Retrieve Rank Based on Total Mark in SQLite Table - sql

say I have a table like this:
S_id |ca1 |ca2 |exam
1 | 08 | 12 | 35
1 | 02 | 14 | 32
1 | 08 | 12 | 20
2 | 03 | 11 | 55
2 | 09 | 18 | 45
2 | 10 | 12 | 35
3 | 07 | 12 | 35
3 | 04 | 14 | 37
3 | 09 | 15 | 32
4 | 03 | 11 | 55
4 | 09 | 18 | 45
4 | 10 | 12 | 35
5 | 10 | 12 | 35
5 | 07 | 12 | 35
5 | 09 | 18 | 45
I want to select S_id, total and assign a rank on each student based on sum(ca1+ca2+exam) like the following:
S_id |total|rank
1 | 158 | 5
2 | 198 | 1
3 | 165 | 4
4 | 198 | 1
5 | 183 | 3
If there are the same total, like S_id 2 and S_id 4 with rank 1, I want the rank to be jumped to 3.
Thanks for helping.

Make the table:
sqlite> create table t (S_id, ca1, ca2, exam);
sqlite> insert into t values
...> ( 1 , 08 , 12 , 35 ),
...> ( 1 , 02 , 14 , 32 ),
...> ( 1 , 08 , 12 , 20 ),
...> ( 2 , 03 , 11 , 55 ),
...> ( 2 , 09 , 18 , 45 ),
...> ( 2 , 10 , 12 , 35 ),
...> ( 3 , 07 , 12 , 35 ),
...> ( 3 , 04 , 14 , 37 ),
...> ( 3 , 09 , 15 , 32 ),
...> ( 4 , 03 , 11 , 55 ),
...> ( 4 , 09 , 18 , 45 ),
...> ( 4 , 10 , 12 , 35 ),
...> ( 5 , 10 , 12 , 35 ),
...> ( 5 , 07 , 12 , 35 ),
...> ( 5 , 09 , 18 , 45 );
Make a temporary table with the total scores:
sqlite> create temp table tt
as select S_id, sum(ca1) + sum(ca2) + sum(exam) as total
from t group by S_id;
Use the temporary table to compute the ranks:
sqlite> select s.S_id, s.total,
(select count(*)+1 from tt as r where r.total > s.total) as rank
from tt as s;
1|143|5
2|198|1
3|165|4
4|198|1
5|183|3
Drop the temporary table:
sqlite> drop table tt;
ADDENDUM
With a recent change (2015-02-09) to SQLite, this formulation now works:
with tt (S_id, total) as
(select S_id, sum(ca1 + ca2 + exam) as total from t group by S_id)
select s.S_id, s.total,
(select count(*)+1 from tt as r where r.total > s.total) as rank
from tt as s;

Something like this maybe:
with tt(S_id,total) as (
select S_id, sum(ca1) + sum(ca2) + sum(exam)
from t
group by S_id
union
select null, 0
)
select s.S_id,
s.total,
(select count(*)+1
from tt as r
where r.total > s.total) as rank
from tt as s
where S_id is not null;

Per my standard Rank Rows answer, use a self join:
with tt (S_id, total) as
(select S_id, sum(ca1 + ca2 + exam) as total
from t group by S_id)
select S.S_id, S.total, 1+count(lesser.total) as RANK
from tt as S
left join tt as lesser
on S.total < lesser.total
group by S.S_id, S.total
order by S.total desc;
S_id total RANK
---------- ---------- ----------
2 198 1
4 198 1
5 183 3
3 165 4
1 143 5
You don't need a CTE; you could use a subquery instead, but you'd have to repeat it.
Using a SELECT clause in a SELECT clause to produce a column (as suggested elsewhere) is AFAIK nonstandard. A self-join is standard and should be easier for the query planner to optimize (if only for that reason). Also, the above query doesn't munge the data: it doesn't add a row to the CTE only to remove it in the main query.
I prefer the sum(ca1 + ca2 + exam) construction to adding the sums. That's how the question was posed, and it asks the system to do less work (only one summation). Sure, addition is commutative, but I wouldn't depend on the query optimizer to notice.

Related

SQL to allocate rows from one table to another

I would like to allocate rows from one table to another using a sort on TopLvlOrd field. The inputs are the [Orders] table and the [Defects] table. I would like to create an SQL that produces [Output]. Even after a bunch of online research I'm not sure how to do this. I'd prefer not to do a cursor, but will go there if necessary. Any ideas? Using SQL Server 2012.
Rules:
(1) Allocate by TopLvlOrd asc,
(2) Allocate one TopLvlOrd row per PegQty
[Orders]
TopLvlOrd IntOrd PegQty
========= ====== ======
67 25 3
120 25 1
111 25 1
16 25 1
127 25 1
127 65 1
127 85 1
[Defects]
DefectID IntOrd TotQty
======== ====== ======
1 25 10
2 25 10
3 25 10
4 25 10
5 25 10
6 25 10
7 25 10
8 25 10
9 25 10
10 25 10
11 65 1
12 85 2
13 85 2
[Output]
DefectID IntOrd TotQty TopLvlOrd
======== ====== ====== =========
1 25 10 16
2 25 10 67
3 25 10 67
4 25 10 67
5 25 10 111
6 25 10 120
7 25 10 127
8 25 10 NULL
9 25 10 NULL
10 25 10 NULL
11 65 1 127
12 85 2 127
13 85 2 NULL
This answers the original version of the question.
I think you want to join on an implicit sequence number, which you can add using row_number():
select d.*, o.*
from (select d.*,
row_number() over (partition by intord order by defectid) as seqnum
from defects d
) d left join
(select o.*,
row_number() over (partition by IntOrd order by TopLvlOrd) as seqnum
from orders o
) o
on d.intord = o.intord and d.seqnum = o.seqnum
Please another time make another question. Please check this query:
SELECT DefectID, IntOrd,
TotQty, TopLvlOrd, PegQty
FROM
(
SELECT B. DefectID, COALESCE (A.IntOrd, B. IntOrd) IntOrd,
B.TotQty, A. TopLvlOrd, A.PegQty FROM
(
SELECT TopLvlOrd ,IntOrd, ROW_NUMBER () OVER (PARTITION By IntOrd ORDER by
TopLvlOrd) Num, PegQty FROM Orders
) A
FULL JOIN
(
SELECT DefectID , IntOrd ,TotQty, ROW_NUMBER () OVER (PARTITION By IntOrd ORDER by
TotQty) Num FROM Orders
) B
ON A. IntOrd=B.IntOrd AND A.Num=B.Num
)C
JOIN
master.dbo.spt_values Tab
ON Tab.type='P' AND Tab.number<C.PegQty
SELECT B. DefectID, COALESCE (A.IntOrd, B. IntOrd) IntOrd,
B.TotQty, A. TopLvlOrd FROM
(
SELECT TopLvlOrd ,IntOrd , ROW_NUMBER () OVER (PARTITION By IntOrd ORDER by TopLvlOrd) Num FROM Orders
) A
FULL JOIN
(
SELECT DefectID , IntOrd ,TotQty, ROW_NUMBER () OVER (PARTITION By IntOrd ORDER by TotQty) Num FROM Orders
) B
ON A. IntOrd=B.IntOrd AND A. Num=B.Num

Oracle SQL - How to calculate null values based on previous non-null record

I have some columns in a table like this:
id | date | change | end_value
1 | 03-JAN-20 | -9 |
2 | 04-JAN-20 | 12 |
3 | 05-JAN-20 | -43 | 523
4 | 06-JAN-20 | 0 |
5 | 07-JAN-20 | 5 |
6 | 08-JAN-20 | 10 |
7 | 09-JAN-20 | 3 | 505
8 | 10-JAN-20 | 4 |
9 | 11-JAN-20 | -3 |
10| 12-JAN-20 | 1 | 503
11| 13-JAN-20 | -6 |
I need to fill in all the null values in the end_value column based on the previous non-null value and minus the sum of change.
When the end_value is not null, keep the value as it is.
The result would be something like this:
id | date | change | end_value | result
1 | 03-JAN-20 | -9 | | 492 (=523 - 43 + 12)
2 | 04-JAN-20 | 12 | | 480 (=523 - 43)
3 | 05-JAN-20 | -43 | 523 | 523
4 | 06-JAN-20 | 0 | | 523 (=523 - 0)
5 | 07-JAN-20 | 5 | | 518 (=523 - 0 - 5)
6 | 08-JAN-20 | 10 | | 508 (=523 - 0 - 5 - 10)
7 | 09-JAN-20 | 3 | 505 | 505
8 | 10-JAN-20 | 4 | | 501 (=505 - 4)
9 | 11-JAN-20 | -3 | | 504 (=505 - 4 + 3)
10| 12-JAN-20 | 1 | 503 | 503
11| 13-JAN-20 | -6 | | 509 (=503 + 6)
I figured might need to use last_value ignore null function, but can't figure out the running minues part.
Thanks for the help!
The solution below depends on the first non null value for end_value sorted by date - i.e., it ignores the rest of the values.
with t (sid, dt,change,end_value) as (
select 1 , to_date('03-JAN-20', 'dd-MON-rr') , -9 , null from dual union all
select 2 , to_date('04-JAN-20', 'dd-MON-rr') , 12 , null from dual union all
select 3 , to_date('05-JAN-20', 'dd-MON-rr') , -43 , 523 from dual union all
select 4 , to_date('06-JAN-20', 'dd-MON-rr') , 0 , null from dual union all
select 5 , to_date('07-JAN-20', 'dd-MON-rr') , 5 , null from dual union all
select 6 , to_date('08-JAN-20', 'dd-MON-rr') , 10 , null from dual union all
select 7 , to_date('09-JAN-20', 'dd-MON-rr') , 3 , 505 from dual union all
select 8 , to_date('10-JAN-20', 'dd-MON-rr') , 4 , null from dual union all
select 9 , to_date('11-JAN-20', 'dd-MON-rr') , -3 , null from dual union all
select 10, to_date('12-JAN-20', 'dd-MON-rr') , 1 , 503 from dual union all
select 11, to_date('13-JAN-20', 'dd-MON-rr') , -6 , null from dual
)
select sid, dt, change, end_value, nvl(yy,yyy) rslt from (
select a.*
, sum(case when dt = xx then end_value when dt > xx then -change end) over ( order by dt) yy
, sum(case when dt = xx then end_value when dt < xx then ld end) over ( order by dt desc) yyy
from (
select t.*
, min(dt) keep (dense_rank first order by nvl2(end_value,0,1)) over () xx
, lead(change) over (order by dt) ld
from t
) a
) b
order by dt
This is a type of gaps-and-islands problem. The solution is actually pretty simple:
Define the islands by counting the number of non-NULL end_values on or after each row.
Within each group, do a cumulative sum of change and add to the end_value for that group.
There is a little trick because you don't want the change for the current row. That is easily handled by subtracting it out of the cumulative sum:
select t.*,
(max(end_value) over (partition by grp order by dt desc) +
sum(change) over (partition by grp order by dt desc) -
change
) as new_end_value
from (select t.*, count(end_value) over (order by dt desc) as grp
from t
) t
order by dt;
Here is a db<>fiddle.
If you want to update the value, use merge:
merge into t using
(select t.*,
(max(end_value) over (partition by grp order by dt desc) +
sum(change) over (partition by grp order by dt desc) -
change
) as new_end_value
from (select t.*, count(end_value) over (order by dt desc) as grp
from t
) t
) src
on (src.sid = t.sid)
when matched then update
set end_value = src.new_end_value;
You can use a PL/SQL cursor and store the running sum of CHANGE in a variable.
Something like this:
DECLARE
CURSOR cur IS
SELECT
id,
change,
end_value
FROM
test
ORDER BY
"DATE";
TYPE t_record IS RECORD (
id NUMBER,
change NUMBER,
end_value NUMBER
);
v_record t_record;
v_baseline NUMBER := 0;
v_change NUMBER := 0;
BEGIN
FOR row IN cur LOOP
IF row.end_value IS NOT NULL THEN
v_baseline := row.end_value;
v_change := 0;
ELSE
v_change := v_change + row.change;
UPDATE test
SET
end_value = v_baseline - v_change
WHERE
id = row.id;
-- COMMIT;
END IF;
END LOOP;
END;
/
Notice that you wrote that the column should be filled "based on the previous non-null value", but in your example the first two rows are filled based on the next non-null value (if I understood correctly), so this code doesn't work for them. Anyway, you can adjust it according to your needs.

SQL Server 2008 Cumulative Sum that resets value

I want to have the last column cumulative based on ROW_ID that resets every time it starts again with '1'.
Initially my table doesn't have the ROW_ID, this was created using partition so at least I can segregate my records.
It should add the Amt + CumulativeSum (except for the first record) all the way down and reset every time the Row_ID = 1.
I have tried several queries but it doesn't give me the desired result. I am trying to read answers from several forums but to no avail.
Can someone advise the best approach to do this?
For the sake of representation, I made the sample table as straightforward as possible.
ID ROW-ID Amt RunningTotal(Amt)
1 1 2 2
2 2 4 6
3 3 6 12
4 1 2 2
5 2 4 6
6 3 6 12
7 4 8 20
8 5 10 30
9 1 2 2
10 2 4 6
11 3 6 12
12 4 8 20
try this
declare #tb table(ID int, [ROW-ID] int, Amt money)
insert into #tb(ID, [ROW-ID], Amt) values
(1,1,2),
(2,2,4),
(3,3,6),
(4,1,2),
(5,2,4),
(7,4,8),
(8,5,10),
(9,1,2),
(10,2,4),
(11,3,6),
(12,4,8)
select *,sum(amt) over(partition by ([id]-[row-id]) order by id,[row-id]) AS cum from #tb
other version
select *,(select sum(amt) from #tb t where
(t.id-t.[row-id])=(t1.id-t1.[ROW-ID]) and (t.id<=t1.id) ) as cum
from #tb t1 order by t1.id,t1.[row-id]
Try this
SELECT distinct (T1.ID),
T1.ROW_ID,
T1.Amt,
CumulativeSum =
CASE
WHEN T1.RoW_ID=1 THEN T1.Amt
ELSE T1.Amt+ T2.Amt
END
FROM TestSum T1, TestSum T2
WHERE T1.ID = T2.ID+1
http://sqlfiddle.com/#!6/8b2a2/2
The idea is to create partitions from R column. First leave 1 if R = 1, else put 0. Then cumulative sum on that column. When you have partitions you can finally calculate cumulative sums on S column in those partitions:
--- --- ---
| 1 | | 1 | | 1 |
| 2 | | 0 | | 1 | --prev 1 + 0
| 3 | | 0 | | 1 | --prev 1 + 0
| 1 | | 1 | | 2 | --prev 1 + 1
| 2 | => | 0 | => | 2 | --prev 2 + 0
| 3 | | 0 | | 2 | --prev 2 + 0
| 4 | | 0 | | 2 | --prev 2 + 0
| 5 | | 0 | | 2 | --prev 2 + 0
| 1 | | 1 | | 3 | --prev 2 + 1
| 2 | | 0 | | 3 | --prev 3 + 0
--- --- ---
DECLARE #t TABLE ( ID INT, R INT, S INT )
INSERT INTO #t
VALUES ( 1, 1, 2 ),
( 2, 2, 4 ),
( 3, 3, 6 ),
( 4, 1, 2 ),
( 5, 2, 4 ),
( 6, 3, 6 ),
( 7, 4, 8 ),
( 8, 5, 10 ),
( 9, 1, 2 ),
( 10, 2, 4 ),
( 11, 3, 6 ),
( 12, 4, 8 );
For MSSQL 2008:
WITH cte1
AS ( SELECT ID ,
CASE WHEN R = 1 THEN 1
ELSE 0
END AS R ,
S
FROM #t
),
cte2
AS ( SELECT ID ,
( SELECT SUM(R)
FROM cte1 ci
WHERE ci.ID <= co.ID
) AS R ,
S
FROM cte1 co
)
SELECT * ,
( SELECT SUM(S)
FROM cte2 ci
WHERE ci.R = co.R
AND ci.ID <= co.ID
)
FROM cte2 co
For MSSQL 2012:
WITH cte
AS ( SELECT ID ,
SUM(CASE WHEN R = 1 THEN 1
ELSE 0
END) OVER ( ORDER BY ID ) AS R ,
S
FROM #t
)
SELECT * ,
SUM(s) OVER ( PARTITION BY R ORDER BY ID ) AS T
FROM cte
Output:
ID R S T
1 1 2 2
2 1 4 6
3 1 6 12
4 2 2 2
5 2 4 6
6 2 6 12
7 2 8 20
8 2 10 30
9 3 2 2
10 3 4 6
11 3 6 12
12 3 8 20
EDIT:
One more way. This looks way better by execution plan then first example:
SELECT * ,
CASE WHEN R = 1 THEN S
ELSE ( SELECT SUM(S)
FROM #t it
WHERE it.ID <= ot.ID
AND it.ID >= ( SELECT MAX(ID)
FROM #t iit
WHERE iit.ID < ot.ID
AND iit.R = 1
)
)
END
FROM #t ot

Dynamically pivot to fixed number of columns

This is the structure of my data
Name TransID Amount
Joe 123 56
Joe 124 55
Joe 125 58
Tom 126 31
Tom 127 48
I have a requirement to report from this data in the below format
Name Amount1 Amount2
Joe 56 55
Joe 58
Tom 31 48
Joe has three Amounts in the original data set but I need a fixed number of columns (two) in the view. Therefore, the third Amount for Joe is inserted as a new record in the view. Is it possible to achieve this as a stored procedure or creating a view.
Break the problems into smaller steps. These are the steps that I would take:
Use ROW_NUMBER() OVER (PARTITION BY ...):
Name TransID Amount Row_Number
Joe 123 56 1
Joe 124 55 2
Joe 125 58 3
Tom 126 31 1
Tom 127 48 2
Subtract 1.
Name TransID Amount RowNumberStartingWith0
Joe 123 56 0
Joe 124 55 1
Joe 125 58 2
Tom 126 31 0
Tom 127 48 1
Divide it by 2, get the result of the division and the remainder modulo 2:
Name TransID Amount Result Remainder
Joe 123 56 0 0
Joe 124 55 0 1
Joe 125 58 1 0
Tom 126 31 0 0
Tom 127 48 0 1
Drop the TransID column. The remainder is always 0 or 1, so you can pivot on it:
Name Result AmountForRemainder0 AmountForRemainder1
Joe 0 56 55
Joe 1 58
Tom 0 31 48
Now you drop the Result column and rename your columns:
Name Amount1 Amount2
Joe 56 55
Joe 58
Tom 31 48
Profit.
TRY this and let me know .Check with other sample data also.I am getting the desire output.
DECLARE #t TABLE (
NAME VARCHAR(50)
,TransID INT
,Amount INT
)
INSERT INTO #t
VALUES ('Joe',123,56)
,('Joe',124,55)
,('Joe',125,58)
,('Tom',126,31)
,('Tom',127,48)
,('Tom',128,89)
,('Tom',129,90)
,('Joe',130,68);
WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY NAME ORDER BY amount
) rn
FROM #t
)
,CTE1
AS (
SELECT NAME
,(
SELECT amount
FROM cte
WHERE rn = 1
AND NAME = a.NAME
) [Amount1]
,(
SELECT amount
FROM cte
WHERE rn = 2
AND NAME = a.NAME
) [Amount2]
,rn
FROM cte A
WHERE rn = 1
UNION ALL
SELECT b.NAME
,a.amount
,isnull(c.amount, 0)
,a.rn
FROM CTE1 B
INNER JOIN CTE A ON a.NAME = b.NAME
AND a.rn % 2 <> 0
AND a.rn > 1
AND b.rn <> a.rn
OUTER APPLY (
SELECT Amount
FROM CTE C
WHERE NAME = b.NAME
AND rn % 2 = 0
AND rn > 2
) c
)
SELECT *
FROM cte1

How do I find repeating records In a SQL table

I had a table with almost 20000 records
with columns
Id SubjectId UniqueId
1 54 1
1 58 2
1 59 3
1 60 4
2 54 5
2 58 6
2 59 7
2 60 8
2 60 9
3 54 10
3 70 11
I want to Select those Records Which Are repeating
like
result is Like
Id SubjectId UniqueId
2 60 8
2 60 9
7 54 15
7 54 18
7 54 30
Help Me how could I do this
use EXISTS()
SELECT a.*
FROM tableName a
WHERE EXISTS
(
SELECT 1
FROM tableName b
WHERE a.ID = b.ID AND
a.SubjectID = b.subjectID
GROUP BY Id, SubjectId
HAVING COUNT(*) > 1
)
SQLFiddle Demo
You can utilize analytic COUNT() since you're using SQL Server 2008
SELECT id, subjectid, uniqueid
FROM
(
SELECT id, subjectid, uniqueid,
COUNT(*) OVER (PARTITION BY id, subjectid) cnt
FROM table1
) q
WHERE cnt > 1
or another way
SELECT t.*
FROM
(
SELECT id, subjectid
FROM table1
GROUP BY id, SubjectId
HAVING COUNT(*) > 1
) q JOIN table1 t
ON q.id = t.id
AND q.subjectid = t.subjectid
Output for both queries:
| ID | SUBJECTID | UNIQUEID |
|----|-----------|----------|
| 2 | 60 | 8 |
| 2 | 60 | 9 |
| 7 | 54 | 15 |
| 7 | 54 | 18 |
| 7 | 54 | 30 |
Here is SQLFiddle demo
Try this
fetch only duplicate record
SELECT * FROM TABLE_NAME as t1 where SubjectId in (SELECT SubjectId FROM TABLE_NAME as t2 where t2.Id=t1.Id and t1.UniqueId<>t2.UniqueId) order by Id,SubjectId
Count your IDs , if greater then 1 then then select it
SELECT *
FROM table
HAVING COUNT(id) > 1