How do I get running total for distinct values in a column - sql

I'd like to group by date range as the below example
Date ItemNo Qty
==================================
1/1/2014 101 20
2/1/2014 102 10
3/1/2014 103 5
4/1/2014 104 10
1/1/2014 101 5
2/1/2014 101 10
3/1/2014 102 15
4/1/2014 104 20
I want to get the balance daily by sum the qty till that day grouped by ItemNo to be as below
Date ItemNo Qty
==================================
1/1/2014 101 25
2/1/2014 101 35
2/1/2014 102 10
3/1/2014 102 25
3/1/2014 103 5
4/1/2014 104 30
I know I can solve the problem by using cursors but I need another solution
thanks

so just use SUM
SELECT Date, ItemNo, SUM(Qty)
FROM table
GROUP BY Date, ItemNo
please read on agregate function and sum
Edit
i took your comment and did this:
SELECT a.Date, a.ItemNo, tmp.qty + a.ItemNo
FROM table a
JOIN (SELECT TOP 1 * FROM table t WHERE t.date < a.Date ORDER BY t.date DESC) tmp ON a.ItemNo = tmp.ItemNo
i'm checking it now, so it might need some tweaks, but i wanted to release it straight away so you'll have the general idea

Here is your sample table
SELECT * INTO #TEMP
FROM
(
SELECT '1/1/2014' [DATE], 101 [ItemNo], 20 QTY
UNION ALL
SELECT '2/1/2014', 102, 10
UNION ALL
SELECT '3/1/2014', 103, 5
UNION ALL
SELECT '4/1/2014', 104, 10
UNION ALL
SELECT '1/1/2014', 101, 5
UNION ALL
SELECT '2/1/2014', 101, 10
UNION ALL
SELECT '3/1/2014', 102, 15
UNION ALL
SELECT '4/1/2014', 104, 20
)TAB
Use Row_Number to get number for each Item's date do the sum inside CTE
;WITH CTE1 AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY [ItemNo] ORDER BY CAST([DATE] AS DATE))RNO,
[DATE],[ItemNo],SUM(Qty)Qty
FROM #TEMP
GROUP BY [DATE],[ItemNo]
)
SELECT A.RNO,[DATE],[ItemNo],
CASE WHEN RNO=1 THEN Qty
ELSE (SELECT SUM(b.Qty)
FROM CTE1 b
WHERE A.ItemNo=B.ItemNo AND B.RNO<=A.RNO)
END QTY
FROM CTE1 A
ORDER BY A.itemno,CAST(A.[DATE] AS DATE);
RESULT

Here's a solution using a recursive common table expression.
Not sure if it will be faster or not than the answer by Sarath Avanavu, but you can try!
Sample data:
DECLARE #t TABLE([Date] DATETIME, ItemNo INT, QTY INT)
INSERT #t
( Date, ItemNo, QTY )
SELECT '1/1/2014', 101, 20
UNION ALL SELECT '2/1/2014', 102, 10
UNION ALL SELECT '3/1/2014', 103, 5
UNION ALL SELECT '4/1/2014', 104, 10
UNION ALL SELECT '1/1/2014', 101, 5
UNION ALL SELECT '2/1/2014', 101, 10
UNION ALL SELECT '3/1/2014', 102, 15
UNION ALL SELECT '4/1/2014', 104, 20
Query:
;WITH dSum AS (
SELECT [Date], ItemNo, SUM(QTY) AS QTY
FROM #t AS t
GROUP BY [Date], [ItemNo]
), dSumRN AS (
SELECT [Date], ItemNo, QTY, ROW_NUMBER() OVER(PARTITION BY ItemNo ORDER BY [Date]) AS rn
FROM dSum
), cte AS (
SELECT [Date], ItemNo, QTY, rn
FROM dSumRN
WHERE rn = 1
UNION ALL SELECT
dSumRN.[Date], dSumRN.ItemNo, cte.QTY + dSumRN.QTY AS QTY, cte.rn + 1 AS rn
FROM cte
JOIN dSumRN ON cte.ItemNo = dSumRN.ItemNo AND cte.rn + 1 = dSumRN.rn
)
SELECT [Date], [ItemNo], QTY FROM cte
ORDER BY [Date], [ItemNo]
OPTION (MAXRECURSION 1000) -- maximum this can be set to is 32767

Easiest code below for your query:
select Date,itemno,
(select sum(Qty) from #temp where date<=T.date and itemno=T.itemno)
from #temp T
group by Date,itemno order by date

Related

Fetch latest start date, if there are two minimum start dates in a table

I'm trying to build a query for the following scenario,
Group records by license ID and get min and max dates
For a given license ID, if there are two earliest start dates, then start date of the particular ID has to be updated as latest start date in that grouping.
Since I'm new to sql, I need help to satisfy condition 2. Any help is greatly appreciated. Thanks
Actual data
LicenseID
StartDate
EndDate
100
4/3/2000
3/1/2013
100
4/3/2000
2/2/2017
100
3/1/2013
1/23/2015
100
1/23/2015
2/2/2017
100
2/2/2017
2/9/2018
100
2/2/2017
12/18/2018
100
12/18/2018
2/16/2021
Expected output
LicenseID
StartDate
EndDate
100
12/18/2018
2/16/2021
Here's one option; read comments within code.
Sample data:
SQL> with test (id, start_date, end_date) as
2 (select 100, date '2000-04-03', date '2013-03-01' from dual union all
3 select 100, date '2000-04-03', date '2017-02-02' from dual union all
4 select 100, date '2018-12-18', date '2021-02-16' from dual
5 ),
Query begins here:
6 -- rank start dates per each ID
7 temp as
8 (select id,
9 min(start_date) over (partition by id) min_sd,
10 max(start_date) over (partition by id) max_sd,
11 rank() over (partition by id order by start_date) rnk_sd,
12 --
13 max(end_date) over (partition by id) max_ed
14 from test
15 ),
16 -- count number of the 1st start dates
17 temp2 as
18 (select id,
19 sum(case when rnk_sd = 1 then 1 else 0 end) cnt_sd
20 from temp
21 group by id
22 )
23 -- if number of the 1st start dates is 1, take MIN_SD. Otherwise, take MAX_SD
24 select distinct
25 b.id,
26 case when b.cnt_sd = 1 then a.min_sd else a.max_sd end start_date,
27 a.max_ed end_date
28 from temp2 b join temp a on a.id = b.id;
Result:
ID START_DATE END_DATE
---------- ---------- ----------
100 12/18/2018 02/16/2021
SQL>
This can filter them:
WITH sample_data AS
(
SELECT 100 AS LicenseID, TO_DATE('04/03/2000','MM/DD/YYYY') AS StartDate, TO_DATE('03/01/2013','MM/DD/YYYY') AS EndDate FROM DUAL UNION ALL
SELECT 100, TO_DATE('04/03/2000','MM/DD/YYYY'), TO_DATE('02/02/2017','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 100, TO_DATE('03/01/2013','MM/DD/YYYY'), TO_DATE('01/23/2015','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 100, TO_DATE('01/23/2015','MM/DD/YYYY'), TO_DATE('02/02/2017','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 100, TO_DATE('02/02/2017','MM/DD/YYYY'), TO_DATE('02/09/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 100, TO_DATE('02/02/2017','MM/DD/YYYY'), TO_DATE('12/18/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 100, TO_DATE('12/18/2018','MM/DD/YYYY'), TO_DATE('02/16/2021','MM/DD/YYYY') FROM DUAL
)
SELECT dat.licenseID, CASE WHEN dups.licenseID IS NOT NULL THEN MAX(StartDate)
ELSE MIN(StartDate)
END,
CASE WHEN dups.licenseID IS NOT NULL THEN MAX(EndDate)
ELSE MIN(EndDate)
END
FROM sample_data dat
LEFT OUTER JOIN (SELECT COUNT(1), sd.LicenseID
FROM sample_data sd
INNER JOIN (SELECT MIN(StartDate) AS StartDate, LicenseID
FROM sample_data
GROUP BY LicenseID) mins
ON sd.LicenseID = mins.LicenseID AND sd.startDate = mins.StartDate
GROUP BY sd.LicenseID
HAVING COUNT(1) > 1) dups
ON dups.LicenseID = dat.licenseID
GROUP BY dat.licenseID, dups.licenseID;
You can use:
SELECT licenseid,
MAX(startdate) AS startdate,
MAX(enddate) KEEP (DENSE_RANK LAST ORDER BY startdate) AS enddate
FROM table_name
GROUP BY licenseid
HAVING COUNT(*) KEEP (DENSE_RANK FIRST ORDER BY startdate) > 1;
or:
SELECT licenseid,
max_startdate AS startdate,
max_enddate As enddate
FROM (
SELECT licenseid,
RANK()
OVER (PARTITION BY licenseid ORDER BY startdate) AS rnk,
ROW_NUMBER()
OVER (PARTITION BY licenseid, startdate ORDER BY enddate) AS rn,
MAX(startdate)
OVER (PARTITION BY licenseid) AS max_startdate,
MAX(enddate)
KEEP (DENSE_RANK LAST ORDER BY startdate)
OVER (PARTITION BY licenseid) AS max_enddate
FROM table_name t
)
WHERE rnk = 1
AND rn = 2;
Which, for the sample data:
CREATE TABLE table_name (licenseid, startdate, enddate) AS
SELECT 100, DATE'2000-04-03', DATE'2013-03-01' FROM DUAL UNION ALL
SELECT 100, DATE'2000-04-03', DATE'2017-02-02' FROM DUAL UNION ALL
SELECT 100, DATE'2013-03-01', DATE'2015-01-23' FROM DUAL UNION ALL
SELECT 100, DATE'2015-01-23', DATE'2017-02-02' FROM DUAL UNION ALL
SELECT 100, DATE'2017-02-02', DATE'2018-02-09' FROM DUAL UNION ALL
SELECT 100, DATE'2018-02-02', DATE'2018-12-18' FROM DUAL UNION ALL
SELECT 100, DATE'2018-12-18', DATE'2021-02-16' FROM DUAL;
Both output:
LICENSEID
STARTDATE
ENDDATE
100
2018-12-18 00:00:00
2021-02-16 00:00:00
If you do want to perform an UPDATE of that second row then:
MERGE INTO table_name dst
USING (
SELECT ROWID AS rid,
max_startdate,
max_enddate
FROM (
SELECT RANK()
OVER (PARTITION BY licenseid ORDER BY startdate) AS rnk,
ROW_NUMBER()
OVER (PARTITION BY licenseid, startdate ORDER BY enddate) AS rn,
MAX(startdate)
OVER (PARTITION BY licenseid) AS max_startdate,
MAX(enddate)
KEEP (DENSE_RANK LAST ORDER BY startdate)
OVER (PARTITION BY licenseid) AS max_enddate
FROM table_name t
)
WHERE rnk = 1
AND rn = 2
)src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE
SET startdate = src.max_startdate,
enddate = src.max_enddate;
db<>fiddle here

Compare data of current week against same week of previous years

I have this table that contains sales by stores & date.
-------------------------------------------
P_DATE - P_STORE - P_SALES
-------------------------------------------
2019-02-05 - S1 - 5000
2019-02-05 - S2 - 9850
2018-06-17 - S1 - 6980
2018-05-17 - S2 - 6590
..
..
..
-------------------------------------------
I want to compare Sum of sales for each store of last 10 weeks of this year with same week of previous years.
I want a result like this :
---------------------------------------------------
Week - Store - Sales-2019 - Sales2018
---------------------------------------------------
20 - S1 - 2580 - 2430
20 - S2 - 2580 - 2430
.
.
10 - S1 - 5905 - 5214
10 - S2 - 4789 - 6530
---------------------------------------------------
I'v tried this :
Select
[Week] = DATEPART(WEEK, E_Date),
[Store] = E_store
[Sales 2019] = Case when Year(P_date) = '2019' Then Sum (P_Sales)
[Sales 2018] = Case when Year(P_date) = '2018' Then Sum (P_Sales)
From
PIECE
Group by
DATEPART(WEEK, E_Date),
E_store
I need your help please.
This script will consider 10 weeks including current week-
WITH wk_list (COMMON,DayMinus)
AS
(
SELECT 1,0 UNION ALL
SELECT 1,1 UNION ALL
SELECT 1,2 UNION ALL
SELECT 1,3 UNION ALL
SELECT 1,4 UNION ALL
SELECT 1,5 UNION ALL
SELECT 1,6 UNION ALL
SELECT 1,7 UNION ALL
SELECT 1,8 UNION ALL
SELECT 1,9
)
SELECT
DATEPART(ISO_WEEK, P_DATE) WK,
P_STORE,
SUM(CASE WHEN YEAR(P_DATE) = 2019 THEN P_SALES ELSE 0 END) SALES_2019,
SUM(CASE WHEN YEAR(P_DATE) = 2018 THEN P_SALES ELSE 0 END) SALES_2018
FROM your_table
WHERE YEAR(P_DATE) IN (2019,2018)
AND DATEPART(ISO_WEEK, P_DATE) IN
(
SELECT A.WKNUM-wk_list.DayMinus AS [WEEK NUMBER]
FROM wk_list
INNER JOIN (
SELECT 1 AS COMMON,DATENAME(ISO_WEEK,GETDATE()) WKNUM
) A ON wk_list.COMMON = A.COMMON
)
GROUP BY DATEPART(ISO_WEEK, P_DATE),P_STORE
But if you want to exclude current week, just replace the following part in above script
, wk_list (COMMON,DayMinus)
AS
(
SELECT 1,1 UNION ALL
SELECT 1,2 UNION ALL
SELECT 1,3 UNION ALL
SELECT 1,4 UNION ALL
SELECT 1,5 UNION ALL
SELECT 1,6 UNION ALL
SELECT 1,7 UNION ALL
SELECT 1,8 UNION ALL
SELECT 1,9 UNION ALL
SELECT 1,10
)
Is this what you're looking for?
DECLARE #t TABLE (TransactionID INT, Week INT, Year INT, Amount MONEY)
INSERT INTO #t
(TransactionID, Week, Year, Amount)
VALUES
(1, 20, 2018, 50),
(2, 20, 2019, 20),
(3, 19, 2018, 35),
(4, 19, 2019, 40),
(5, 20, 2018, 70),
(6, 20, 2019, 80)
SELECT TOP 10 Week, [2018], [2019] FROM (SELECT Week, Year, SUM(Amount) As Amount FROM #t GROUP BY Week, Year) t
PIVOT
(
SUM(Amount)
FOR Year IN ([2018], [2019])
) sq
ORDER BY Week DESC

How to calculate MTD and QTD by YTD value in Oracle

There are some data in my table t1 looks like below:
date dealer YTD_Value
2018-01 A 1100
2018-02 A 2000
2018-03 A 3000
2018-04 A 4200
2018-05 A 5000
2018-06 A 5500
2017-01 B 100
2017-02 B 200
2017-03 B 500
... ... ...
then I want to write a SQL to query this table and get below result:
date dealer YTD_Value MTD_Value QTD_Value
2018-01 A 1100 1100 1100
2018-02 A 2000 900 2000
2018-03 A 3000 1000 3000
2018-04 A 4200 1200 1200
2018-05 A 5000 800 2000
2018-06 A 5500 500 2500
2017-01 B 100 100 100
2017-02 B 200 100 200
2017-03 B 550 350 550
... ... ... ... ...
'YTD' means Year to date
'MTD' means Month to date
'QTD' means Quarter to date
So if I want to calculate MTD and QTD value for dealer 'A' in '2018-01', it should be the same as YTD.
If I want to calculate MTD value for dealer 'A' in '2018-06', MTD value should equal to YTD value in '2018-06' minus YTD value in '2018-05'. And the QTD value in '2018-06' should equal to YTD value in '2018-06' minus YTD value in '2018-03' or equal to sum MTD value in (2018-04,2018-05,2018-06)
The same rule for other dealers such as B.
How can I write the SQL to achieve this purpose?
The QTD calculation is tricky, but you can do this query without subqueries. The basic idea is to do a lag() for the monthly value. Then use a max() analytic function to get the YTD value at the beginning of the quarter.
Of course, the first quarter of the year has no such value, so a coalesce() is needed.
Try this:
with t(dte, dealer, YTD_Value) as (
select '2018-01', 'A', 1100 from dual union all
select '2018-02', 'A', 2000 from dual union all
select '2018-03', 'A', 3000 from dual union all
select '2018-04', 'A', 4200 from dual union all
select '2018-05', 'A', 5000 from dual union all
select '2018-06', 'A', 5500 from dual union all
select '2017-01', 'B', 100 from dual union all
select '2017-02', 'B', 200 from dual union all
select '2017-03', 'B', 550 from dual
)
select t.*,
(YTD_Value - lag(YTD_Value, 1, 0) over (partition by substr(dte, 1, 4) order by dte)) as MTD_Value,
(YTD_Value -
coalesce(max(case when substr(dte, -2) in ('03', '06', '09') then YTD_VALUE end) over
(partition by substr(dte, 1, 4) order by dte rows between unbounded preceding and 1 preceding
), 0
)
) as QTD_Value
from t
order by 1
Here is a db<>fiddle.
The following query should do the job. It uses a CTE that translates the varchar date column to dates, and then a few joins to recover the value to compare.
I tested it in this db fiddle and the output matches your expected results.
WITH cte AS (
SELECT TO_DATE(my_date, 'YYYY-MM') my_date, dealer, ytd_value FROM my_table
)
SELECT
TO_CHAR(ytd.my_date, 'YYYY-MM') my_date,
ytd.ytd_value,
ytd.dealer,
ytd.ytd_value - NVL(mtd.ytd_value, 0) mtd_value,
ytd.ytd_value - NVL(qtd.ytd_value, 0) qtd_value
FROM
cte ytd
LEFT JOIN cte mtd ON mtd.my_date = ADD_MONTHS(ytd.my_date, -1) AND mtd.dealer = ytd.dealer
LEFT JOIN cte qtd ON qtd.my_date = ADD_MONTHS(TRUNC(ytd.my_date, 'Q'), -1) AND mtd.dealer = qtd.dealer
ORDER BY dealer, my_date
PS : date is a reserved word in most RDBMS (including Oracle), I renamed that column to my_date in the query.
You can use lag() windows analytic and sum() over .. aggregation functions as :
select "date",dealer,YTD_Value,MTD_Value,
sum(MTD_Value) over (partition by qt order by "date")
as QTD_Value
from
(
with t("date",dealer,YTD_Value) as
(
select '2018-01','A',1100 from dual union all
select '2018-02','A',2000 from dual union all
select '2018-03','A',3000 from dual union all
select '2018-04','A',4200 from dual union all
select '2018-05','A',5000 from dual union all
select '2018-06','A',5500 from dual union all
select '2017-01','B', 100 from dual union all
select '2017-02','B', 200 from dual union all
select '2017-03','B', 550 from dual
)
select t.*,
t.YTD_Value - nvl(lag(t.YTD_Value)
over (partition by substr("date",1,4) order by substr("date",1,4) desc, "date"),0)
as MTD_Value,
substr("date",1,4)||to_char(to_date("date",'YYYY-MM'),'Q')
as qt,
substr("date",1,4) as year
from t
order by year desc, "date"
)
order by year desc, "date";
Rextester Demo

How can update a column based on the value of another column in SQL?

Basically I have Product table like this:
date price
--------- -----
02-SEP-14 50
03-SEP-14 60
04-SEP-14 60
05-SEP-14 60
07-SEP-14 71
08-SEP-14 45
09-SEP-14 45
10-SEP-14 24
11-SEP-14 60
I need to update the table in this form
date price id
--------- ----- --
02-SEP-14 50 1
03-SEP-14 60 2
04-SEP-14 60 2
05-SEP-14 60 2
07-SEP-14 71 3
08-SEP-14 45 4
09-SEP-14 45 4
10-SEP-14 24 5
11-SEP-14 60 6
What I have tried:
CREATE SEQUENCE user_id_seq
START WITH 1
INCREMENT BY 1
CACHE 20;
ALTER TABLE Product
ADD (ID number);
UPDATE Product SET ID = user_id_seq.nextval;
This is updating the ID in the usual way like 1,2,3,4,5..
I have no idea how to do it using basic SQL commands. Please suggest how can I make it. Thank you in advance.
Here is one way to create a view from your base data. I assume you have more than one product (identified by product id), and that the price dates aren't necessarily consecutive. The sequence is separate for each product id. (Also, product should be the name of a different table - where the product id is primary key, and you have other information such as product name, category, etc. The table in your post would be more properly called something like price_history.)
alter session set nls_date_format='dd-MON-rr';
create table product ( prod_id number, dt date, price number );
insert into product ( prod_id, dt, price )
select 101, '02-SEP-14', 50 from dual union all
select 101, '03-SEP-14', 60 from dual union all
select 101, '04-SEP-14', 60 from dual union all
select 101, '05-SEP-14', 60 from dual union all
select 101, '07-SEP-14', 71 from dual union all
select 101, '08-SEP-14', 45 from dual union all
select 101, '09-SEP-14', 45 from dual union all
select 101, '10-SEP-14', 24 from dual union all
select 101, '11-SEP-14', 60 from dual union all
select 102, '02-SEP-14', 45 from dual union all
select 102, '04-SEP-14', 45 from dual union all
select 102, '05-SEP-14', 60 from dual union all
select 102, '06-SEP-14', 50 from dual union all
select 102, '09-SEP-14', 60 from dual
;
commit;
create view product_vw ( prod_id, dt, price, seq ) as
select prod_id, dt, price,
count(flag) over (partition by prod_id order by dt)
from ( select prod_id, dt, price,
case when price = lag(price) over (partition by prod_id order by dt)
then null else 1 end as flag
from product
)
;
Now check what the view looks like:
select * from product_vw;
PROD_ID DT PRICE SEQ
------- ------------------- ---------- ----------
101 02/09/0014 00:00:00 50 1
101 03/09/0014 00:00:00 60 2
101 04/09/0014 00:00:00 60 2
101 05/09/0014 00:00:00 60 2
101 07/09/0014 00:00:00 71 3
101 08/09/0014 00:00:00 45 4
101 09/09/0014 00:00:00 45 4
101 10/09/0014 00:00:00 24 5
101 11/09/0014 00:00:00 60 6
102 02/09/0014 00:00:00 45 1
102 04/09/0014 00:00:00 45 1
102 05/09/0014 00:00:00 60 2
102 06/09/0014 00:00:00 50 3
102 09/09/0014 00:00:00 60 4
NOTE: This answers the question that was originally asked. The OP changed the data.
If your data is not too large, you can use a correlated subquery:
update product p
set id = (select count(distinct p2.price)
from product p2
where p2.date <= p.date
);
If your data is larger, then merge is more appropriate.
WITH cts AS
(
SELECT row_number() over (partition by price order by price ) as id
,date
,price
FROM Product
)
UPDATE p
set p.id = cts.id
from product p join cts on cts.id = p.id
This is the best way by which you try to do.
There is no another simple way to do this using simple statements

How to identify positive minimum or negative maximum in a column for a key?

I have the following columns - Person_ID Days. For one person id, multiple days are possible. Something like this:
Person_Id Days
1000 100
1000 200
1000 -50
1000 -10
1001 100
1001 200
1001 50
1001 10
1002 -50
1002 -10
I need to address the following scenarios:
If all values for days column are positive, I need minimum of the days for a person_id. If the days column has both positive and negative, I need minimum of positive. If all negatives, I need maximum of negative.
The output like:
Person_id Days
1000 100
1001 10
1002 -10
I tried using case statement, but I am unable to use a same column in the condition as well as grouping.
Try this (Postgres 9.4+):
select person_id, coalesce(min(days) filter (where days > 0), max(days))
from a_table
group by 1
order by 1;
Oracle Setup:
CREATE TABLE table_name ( Person_Id, Days ) AS
SELECT 1000, 100 FROM DUAL UNION ALL
SELECT 1000, 200 FROM DUAL UNION ALL
SELECT 1000, -50 FROM DUAL UNION ALL
SELECT 1000, -10 FROM DUAL UNION ALL
SELECT 1001, 100 FROM DUAL UNION ALL
SELECT 1001, 200 FROM DUAL UNION ALL
SELECT 1001, 50 FROM DUAL UNION ALL
SELECT 1001, 10 FROM DUAL UNION ALL
SELECT 1002, -50 FROM DUAL UNION ALL
SELECT 1002, -10 FROM DUAL;
Query:
SELECT person_id, days
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY person_id
ORDER BY SIGN( ABS( days ) ),
SIGN( DAYS ) DESC,
ABS( DAYS )
) AS rn
FROM table_name t
)
WHERE rn = 1;
Output:
PERSON_ID DAYS
---------- ----------
1000 100
1001 10
1002 -10
Oracle solution:
with
input_data ( person_id, days) as (
select 1000, 100 from dual union all
select 1000, 200 from dual union all
select 1000, -50 from dual union all
select 1000, -10 from dual union all
select 1001, 100 from dual union all
select 1001, 200 from dual union all
select 1001, 50 from dual union all
select 1001, 10 from dual union all
select 1002, -50 from dual union all
select 1002, -10 from dual
)
select person_id,
NVL(min(case when days > 0 then days end), max(days)) as days
from input_data
group by person_id;
PERSON_ID DAYS
---------- ----------
1000 100
1001 10
1002 -10
For each person_id, if there is at least one days value that is strictly positive, then the min will be taken over positive days only and will be returned by NVL(). Otherwise the min() will return null, and NVL() will return max() over all days (all of which are, in this case, negative or 0).
select Person_id, min(abs(days)) * days/abs(days) from table_name
group by Person_id
-- + handle zero_divide .. SORRY.. the above works only in MySQL .
Something like this will work anywhere which is equivalent of above query:
select t.Person_id , min(t.days) from table_name t,
(select Person_id, min(abs(days)) as days from table_name group by Person_id) v
where t.Person_id = v.Person_id
and abs(t days) = v.days
group by Person_id;
OR
select id, min(Days) from (
select Person_id, min(abs(Days)) as Days from temp group by Person_id
union
select Person_id, max(Days) as Days from temp group by Person_id
) temp
group by Person_id;
You can do this by using GroupBy clause in sql server. Take a look into below query:-
CREATE TABLE #test(Person_Id INT, [Days] INT)
DECLARE #LargestNumberFromTable INT;
INSERT INTO #test
SELECT 1000 , 100 UNION
SELECT 1000 , 200 UNION
SELECT 1000 , -50 UNION
SELECT 1000 , -10 UNION
SELECT 1001 , 100 UNION
SELECT 1001 , 200 UNION
SELECT 1001 , 50 UNION
SELECT 1001 , 10 UNION
SELECT 1002 , -50 UNION
SELECT 1002 , -10
SELECT #LargestNumberFromTable = ISNULL(MAX([Days]), 0)
FROM #test
SELECT Person_Id
,CASE WHEN SUM(IIF([Days] > 0,[Days] , 0)) = 0 THEN MAX([Days]) -- All Negative
WHEN SUM([Days]) = SUM(IIF([Days] > 0, [Days], 0)) THEN MIN ([Days]) -- ALL Positive
WHEN SUM([Days]) <> SUM(IIF([Days] > 0, [Days], 0)) THEN MIN(IIF([Days] > 0, [Days], #LargestNumberFromTable)) --Mix (Negative And positive)
END AS [Days]
FROM #test
GROUP BY Person_Id
DROP TABLE #test