Getting the first and last result Oracle - oracle11g

Based on this query is it possible to take the first and last result of the year and annualized_return.
CREATE TABLE t (YEARS, PERCENTAGE) AS
SELECT 2014, 38.15 FROM DUAL UNION ALL
SELECT 2015, -25.51 FROM DUAL UNION ALL
SELECT 2016, -8.47 FROM DUAL UNION ALL
SELECT 2017, 18.51 FROM DUAL UNION ALL
SELECT 2018, -2.07 FROM DUAL UNION ALL
SELECT 2019, 16.27 FROM DUAL UNION ALL
SELECT 2020, 108.94 FROM DUAL UNION ALL
SELECT 2021, 29.67 FROM DUAL
in this select how can i take the first row (result) from YEARS 2014
and
annualized_return 16.71
SELECT YEARS,
PERCENTAGE,
ROUND(
(EXP(Sum(LN(1 + PERCENTAGE/100)) OVER(Order By Years)) - 1)*100,
2
) AS ACCUMULATIVE,
ROUND(
POWER(
EXP(Sum(LN(1 + PERCENTAGE/100)) OVER(Order By Years)),1/COUNT(*) OVER (ORDER BY Years)) * 100 - 100,2) AS annualized_return
FROM tbl

If you just want the first and last rows based on years then you can use the ROW_NUMBER analytic function with my original query to filter for the row with the earliest and latest year:
SELECT years, percentage, annualized_return
FROM (
SELECT YEARS,
PERCENTAGE,
ROUND(
(EXP(Sum(LN(1 + PERCENTAGE/100)) OVER(Order By Years)) - 1)*100,
2
) AS ACCUMULATIVE,
ROUND(
POWER(
EXP(
Sum(LN(1 + PERCENTAGE/100)) OVER(Order By Years)
),
1/COUNT(*) OVER (ORDER BY Years)
) * 100 - 100,
2
) AS annualized_return,
ROW_NUMBER() OVER (ORDER BY Years ASC ) AS rn_asc,
ROW_NUMBER() OVER (ORDER BY Years DESC) AS rn_desc
FROM t
)
WHERE rn_asc = 1
OR rn_desc = 1;
Which, for the sample data, outputs:
YEARS
PERCENTAGE
ANNUALIZED_RETURN
2021
29.67
16.71
2014
38.15
38.15
db<>fiddle here

Related

SQLite query JOIN , CONCAT, subquery

I have been trying to use LEFT OUTER JOIN, GROUP BY and (failing) to use the CONCAT || function to get the maximum score for the best book of the decade but have had no luck finding out when two books get the same max score in the same decade.
I need the output below:
There are 2 tables:
Table 1: bookName
Schema: uniqueBookNameId, BookName, yearPublished (from 1901 to 2022)
Table 2: bookRating
Schema: uniqueBookNameId, bookRating
Use a CTE where you join the tables and rank the books with RANK() window function.
Then filter the results to get the top books of each decade:
WITH cte AS (
SELECT r.bookRating,
n.BookName,
n.yearPublished / 10 * 10 || 's' AS Decade,
RANK() OVER (PARTITION BY n.yearPublished / 10 * 10 ORDER BY r.bookRating DESC) AS rnk
FROM bookName n INNER JOIN bookRating r
ON r.uniqueBookNameId = n.uniqueBookNameId
)
SELECT DISTINCT bookRating, BookName, Decade
FROM cte
WHERE rnk = 1
ORDER BY Decade;
or:
WITH cte AS (
SELECT r.bookRating,
n.BookName,
n.yearPublished / 10 * 10 || 's' AS Decade,
RANK() OVER (PARTITION BY n.yearPublished / 10 * 10 ORDER BY r.bookRating DESC) AS rnk
FROM bookName n INNER JOIN bookRating r
ON r.uniqueBookNameId = n.uniqueBookNameId
)
SELECT bookRating, GROUP_CONCAT(DISTINCT BookName) AS BookName, Decade
FROM cte
WHERE rnk = 1
GROUP BY Decade
ORDER BY Decade;

Calculate first day and last day of week of current month

I want to calculate first date and last date of a week for the current month given current datetime.
For example, 01 December 2017 is first day and 02 December 2017 is last day of week for that week for the month of December. In the same week of year, 26 November 2017 is first day of week and
30 November 2017 is last day of week for previous month.
So if today is 01 December 2017, I should get 01-02 from current datetime , instead of 26-02 range.
PS: I am trying to do this thing in a query of Sqlite. I want to group some values on a weekly basis. The days of the week should belong to current month. Hence this requirement. Till now this is my query.
SELECT SUM(amount) as Y,
strftime('%d', datetime(createdOn/1000, 'unixepoch'), '-'||strftime('%w', datetime(createdOn/1000, 'unixepoch'))||' day' )
||'-'||
strftime('%d', datetime(createdOn/1000, 'unixepoch'), '+'||(6-strftime('%w', datetime(createdOn/1000, 'unixepoch')))||' day' ) AS X
FROM my_table
GROUP BY strftime('%d', datetime(createdOn/1000, 'unixepoch'), '-'||strftime('%w', datetime(createdOn/1000, 'unixepoch'))||' day' )
||'-'||
strftime('%d', datetime(createdOn/1000, 'unixepoch'), '+'||(6-strftime('%w', datetime(createdOn/1000, 'unixepoch')))||' day' )
How to achieve that; given current datetime.
Common table expressions are useful to hold intermediate results:
WITH t1 AS (
SELECT amount, date(createdOn / 1000, 'unixepoch', 'localtime') AS date
FROM my_table
),
t2 AS (
SELECT *,
date(date, 'weekday 6') AS end_of_week,
date(date, 'weekday 6', '-6 days') AS start_of_week
FROM t1
),
t3 AS (
SELECT *,
strftime('%m', date ) AS month,
strftime('%m', start_of_week) AS sowk_month,
strftime('%m', end_of_week ) AS eowk_month,
date(date, 'start of month') AS start_of_month,
date(date, '+1 month', 'start of month', '-1 day') AS end_of_month
FROM t2
),
t4 AS (
SELECT *,
CASE WHEN sowk_month = month THEN start_of_week
ELSE start_of_month
END AS week_in_month_start,
CASE WHEN eowk_month = month THEN end_of_week
ELSE end_of_month
END AS week_in_month_end
FROM t3
),
t5 AS (
SELECT amount,
week_in_month_start || '-' || week_in_month_end AS week_in_month
FROM t4
)
SELECT SUM(amount) AS Y,
week_in_month
FROM t5
GROUP BY week_in_month;

Insert one row per hour between two date time range including duration (Oracle)

I have two date time records "start_date" and "end_date" from date_range table.
I would like to insert one row per hour for each hour interval, plus a column with the duration (in hours) so that my results return as follows:
e.g. start_date = 2016/09/01 21:12:00 and end_date = 2016/09/02 01:30:00
Date Hour Duration
2016/09/01 21 0.8
2016/09/01 22 1
2016/09/01 23 1
2016/09/02 00 1
2016/09/02 01 0.5
Here is a plain SQL solution (best to avoid PL/SQL when possible and not too complicated). It uses a recursive factored subquery, available since Oracle 11.1. I created several "rows" of test data to show how this might work for more than one pair of inputs at the same time. Please note, the first subquery is not part of the solution - you would replace it (and the references to it in the actual solution, which is the rest of the query) with your actual table and column names, or whatever your input source.
Note also that "date" and "hour" are reserved words in Oracle, and they shouldn't be used as column names (in the output or anywhere else). I used dt and hr instead.
with
date_range ( row_id, start_date, end_date ) as (
select 101, to_date('2016/09/01 21:12:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/02 01:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 102, to_date('2016/09/02 21:00:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/02 22:00:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 103, to_date('2016/09/01 15:00:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/01 15:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 104, to_date('2016/09/01 21:12:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/01 21:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual
),
rec ( row_id, from_time, to_time, end_date ) as (
select row_id, start_date,
least(end_date, trunc(start_date, 'hh') + 1/24), end_date
from date_range
union all
select row_id, to_time, least(end_date, to_time + 1/24), end_date
from rec
where end_date > from_time + 1/24
)
select row_id,
to_char(from_time, 'yyyy/mm/dd') as dt,
to_char(from_time, 'hh24') as hr,
round(24 * (to_time - from_time), 2) as duration
from rec
order by row_id, from_time
;
Output:
ROW_ID DT HR DURATION
---------- ---------- -- ----------
101 2016/09/01 21 .8
101 2016/09/01 22 1
101 2016/09/01 23 1
101 2016/09/02 00 1
101 2016/09/02 01 .5
102 2016/09/02 21 1
103 2016/09/01 15 .5
104 2016/09/01 21 .3
8 rows selected
You can do it with procedure like this:
first I prepare your data;
create table date_range
(
start_date date,
end_date date
);
create table result_table
(
date_ varchar2(10),
hour_ varchar2(2),
duration number
);
insert into date_range
select to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'),to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') from dual ;
commit;
second I create your procedure;
CREATE OR REPLACE procedure get_dates
AS
rn number:=0;
temp varchar2(10);
BEGIN
select
ceil(24* ( end_date-start_date ))-1
into rn
from
date_range;
FOR i IN 0..rn
LOOP
temp:=i||'/24';
EXECUTE IMMEDIATE '
insert into result_table
select
to_char(start_date+'||temp||',''yyyy/mm/dd'')date_,
to_char(start_date+'||temp||',''hh24'')hour_,
case
when '||i||'=0
then 1-to_number(to_char(start_date+'||temp||',''mi''))/60
when '||i||'='||rn||'
then 1-to_number(to_char(end_date+'||temp||',''mi''))/60
else 1
end duration
from
date_range ';
commit;
END LOOP;
END get_dates;
thirdly I execute procedure ;
begin
get_dates();
end;
finally I see the expected result ;
select
* from
result_table;
DATE_ HOUR_ DURATION
2016/09/01 21 0,8
2016/09/01 22 1
2016/09/01 23 1
2016/09/02 00 1
2016/09/02 01 0,5
select case when lvl=mn then (trunc(frst)+1)- frst
when lvl=mx then lst-trunc(lst)
else 1
end ,a.*
from
(
select frst,lst,sonuc,lvl,first_value(lvl)over() mn,last_value(lvl) over() mx
from
(select to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss') frst,
to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') lst,
(to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24 sonuc from dual) a
,
( select level lvl
from dual
connect by level <=
(select
case when (to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24>
trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)
then trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)+1
else trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)
end
from dual)
) b
)a

Teradata median calculation display

The cod below gives the output as shown. I would like to display the median values for all the respective rows instead of "?". What am I doing wrong?
It should display the median value.
SELECT
sales_segment,
pickup_yyyymm,
Days,
COUNT(*) over(partition by sales_segment,pickup_yyyymm order by sales_segment,pickup_yyyymm desc) AS no_of_records,
SUM(Days) over(partition by sales_segment,pickup_yyyymm order by sales_segment,pickup_yyyymm desc) AS sum_days,
AVERAGE(Days) over(partition by sales_segment,pickup_yyyymm order by sales_segment,pickup_yyyymm desc) AS AVG_days,
Min(Days) over(partition by sales_segment,pickup_yyyymm order by sales_segment,pickup_yyyymm desc) AS Min_days,
MAX (Days) OVER(PARTITION BY sales_segment,pickup_yyyymm ORDER BY sales_segment,pickup_yyyymm DESC) AS Max_days,
CASE
WHEN ROW_NUMBER( ) OVER (PARTITION BY sales_segment,pickup_yyyymm ORDER BY Days) = COUNT(*) OVER (PARTITION BY sales_segment,pickup_yyyymm) / 2 +1
THEN
CASE
WHEN COUNT (*) OVER (PARTITION BY sales_segment,pickup_yyyymm) MOD 2=1 THEN Days
ELSE AVERAGE(Days) OVER(PARTITION BY sales_segment,pickup_yyyymm ORDER BY Days ROWS 1 PRECEDING)
END
END AS Median_Days
FROM
(SELECT
sales_segment,
pickup_yyyymm,
Days
FROM
(SELECT
A.shp_pro_nbr,
CASE
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (20,21,22,23,24,25,26,27,28,29,70,71,72,73,74,75,76,77,78,79) then 'FSAD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (30,31,32,33,34,35,36,37,38,39,50,51,52,53,54,55,56,57,58,59) then 'FSMD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (40,41,42,43,44,45,46,47,48,49) then 'FSSD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 then 'FS Other'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=7 and b.sales_area_nbr=3 then 'BSF'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=7 then 'BSI'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=8 then 'Presls'
when b.sales_div_nbr=2 then 'WWS'
when b.sales_div_nbr=3 then 'Specialty'
when b.sales_div_nbr=8 then 'Non-US'
when b.sales_div_nbr=90 then 'MKTG'
when b.sales_div_nbr=80 then 'WWS'
else 'None' end as sales_segment,
--A.eff_dt,
--A.pckup_dt,
SUBSTR( CAST(CAST (A.pckup_dt AS DATE) AS DATE FORMAT 'yyyy/mm/dd'),1,7) AS pickup_yyyymm,
(CAST( A.eff_dt AS DATE) - CAST (A.pckup_dt AS DATE) ) AS Days
FROM ISH_FEDXFGT_PROD_VIEW_DB.fxf_ship_rev_comp A
INNER JOIN UI_ISH_PROD_DB.sales_quarter_end_alignment B
ON A.payor_cust_nbr = B.cf_cust_nbr AND B.align_typ_cd ='P'AND fscl_qtr_nbr = 4 AND fscl_yr_nbr = 2016 AND B.prim_cvge_flg= 'Y'
AND CAST(A.pckup_dt AS DATE) BETWEEN ADD_MONTHS(CURRENT_DATE,-24) AND CURRENT_DATE
GROUP BY
1,2,3,4
) a
GROUP BY 1,2,3)b
--QUALIFY ROW_NUMBER( ) OVER (PARTITION BY b. sales_segment,b.pickup_yyyymm ORDER BY b.days)= COUNT(*) OVER PARTITION BY b.sales_segment,b. pickup_yyyymm) /2+1;
--GROUP BY 1,2;
--ORDER BY 1,2
The last query in my post on MEDIAN shows how to get the it as OLAP result, you need to add another nesting level:
SELECT
sales_segment,
pickup_yyyymm,
...,
MIN(Median_Days) over(partition by sales_segment,pickup_yyyymm) AS Median_Days
FROM
(
SELECT
sales_segment,
pickup_yyyymm,
Days,
COUNT(*) over(partition by sales_segment,pickup_yyyymm) AS no_of_records,
SUM(Days) over(partition by sales_segment,pickup_yyyymm) AS sum_days,
AVERAGE(Days) over(partition by sales_segment,pickup_yyyymm) AS AVG_days,
Min(Days) over(partition by sales_segment,pickup_yyyymm) AS Min_days,
MAX (Days) OVER(PARTITION BY sales_segment,pickup_yyyymm) AS Max_days,
CASE
WHEN ROW_NUMBER( ) OVER (PARTITION BY sales_segment,pickup_yyyymm ORDER BY Days) = COUNT(*) OVER (PARTITION BY sales_segment,pickup_yyyymm) / 2 +1
THEN
CASE
WHEN COUNT (*) OVER (PARTITION BY sales_segment,pickup_yyyymm) MOD 2=1 THEN Days
ELSE AVERAGE(Days) OVER(PARTITION BY sales_segment,pickup_yyyymm ORDER BY Days ROWS 1 PRECEDING)
END
END AS Median_Days
FROM
(
SELECT
sales_segment,
pickup_yyyymm,
Days
FROM
(
SELECT
A.shp_pro_nbr,
CASE
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (20,21,22,23,24,25,26,27,28,29,70,71,72,73,74,75,76,77,78,79) then 'FSAD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (30,31,32,33,34,35,36,37,38,39,50,51,52,53,54,55,56,57,58,59) then 'FSMD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 and b.sales_terr_nbr in (40,41,42,43,44,45,46,47,48,49) then 'FSSD'
when b.sales_div_nbr=1 and b.sales_grp_nbr<>2 then 'FS Other'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=7 and b.sales_area_nbr=3 then 'BSF'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=7 then 'BSI'
when b.sales_div_nbr=1 and b.sales_grp_nbr=2 and b.sales_org_nbr=8 then 'Presls'
when b.sales_div_nbr=2 then 'WWS'
when b.sales_div_nbr=3 then 'Specialty'
when b.sales_div_nbr=8 then 'Non-US'
when b.sales_div_nbr=90 then 'MKTG'
when b.sales_div_nbr=80 then 'WWS'
else 'None' end as sales_segment,
--A.eff_dt,
--A.pckup_dt,
SUBSTR( CAST(CAST (A.pckup_dt AS DATE) AS DATE FORMAT 'yyyy/mm/dd'),1,7) AS pickup_yyyymm,
(CAST( A.eff_dt AS DATE) - CAST (A.pckup_dt AS DATE) ) AS Days
FROM ISH_FEDXFGT_PROD_VIEW_DB.fxf_ship_rev_comp A
INNER JOIN UI_ISH_PROD_DB.sales_quarter_end_alignment B
ON A.payor_cust_nbr = B.cf_cust_nbr AND B.align_typ_cd ='P'AND fscl_qtr_nbr = 4 AND fscl_yr_nbr = 2016 AND B.prim_cvge_flg= 'Y'
AND CAST(A.pckup_dt AS DATE) BETWEEN ADD_MONTHS(CURRENT_DATE,-24) AND CURRENT_DATE
GROUP BY 1,2,3,4
) a
GROUP BY 1,2,3
)b
) as dt
I removed all the order by sales_segment,pickup_yyyymm desc because it's not needed.
Another remark on the pickup_yyyymm calculation, you don't need a substring:
TRIM(CAST (A.pckup_dt FORMAT 'yyyy/mm')) AS pickup_yyyymm,
Would be more efficient if there's no cast to string at all:
EXTRACT(YEAR FROM A.pckup_dt) * 100 + EXTRACT(MONTH FROM A.pckup_dt)

Calculate Average for the month in SQlite

I have an Sqlite table as below called balance history:
Table Balance History
Date Amount
2013-11-01 16:26:52 1000
2013-11-15 13:20:52 2000
2013-11-27 12:26:55 3000
I would like to calculate the average for the month.
**
The Expected OutPut will be 1666.67
**
Which will be (1000 * 14 days + 2000 * 12 days + 3000 * 4 days)/30 days
= (14000 + 24000 + 12000)/30 = 1666.67
How can I achieve this in SQlite? any help will be appreciated.
thanks
First, we have to compute the start and the end of each interval.
The simple query (SELECT Date AS "From", Amount FROM BalanceHistory WHERE Date GLOB '2013-11*') gets the start for each interval in the month.
(GLOB is case sensitive and thus allows to use a normal index; LIKE would require a special case-insensitive index.)
If there is no record for the first day of the month, the part after the UNION ALL adds the last record of the previous month and changes the day to the 1st.
The COALESCE computes the end of the interval.
The subquery gets the next date from the table, if there is one in the current month.
If there is no such record, it takes the first day of the next month:
SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB '2013-11*'),
date('2013-11-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB '2013-11*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < '2013-11'
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB '2013-11-01*')
ORDER BY Date DESC
LIMIT 1)
);
From To Amount
---------- ---------- ------
2013-11-01 2013-11-15 1000
2013-11-15 2013-11-27 2000
2013-11-27 2013-12-01 3000
We can then wrap this in another query to compute the number of days, and add them up.
The strftime calculates the last day of the month, i.e., the number of days:
SELECT SUM((julianday("To") - julianday("From")) * Amount) /
strftime('%d', '2013-11-01', '+1 month', '-1 day') AS MonthAvg
FROM (SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB '2013-11*'),
date('2013-11-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB '2013-11*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < '2013-11'
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB '2013-11-01*')
ORDER BY Date DESC
LIMIT 1)
)
)
And while we're at it, we can wrap this in yet another query to replace all those 2013-11 strings with the month as read from the table.
This allows as to compute this for every month:
SELECT Month,
(SELECT SUM((julianday("To") - julianday("From")) * Amount) /
strftime('%d', Month || '-01', '+1 month', '-1 day')
FROM (SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB Month || '*'),
date(Month || '-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB Month || '*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < Month
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB Month || '-01*')
ORDER BY Date DESC
LIMIT 1)
)
)
) AS MonthAvg
FROM (SELECT DISTINCT strftime('%Y-%m', Date) AS Month
FROM BalanceHistory)
ORDER BY 1

Resources