Insert a row for each month in the range [duplicate] - sql

This question already has answers here:
Generate series of months for every row in Oracle
(1 answer)
Create all months list from a date column in ORACLE SQL
(3 answers)
Closed 1 year ago.
I want to make my table here in Oracle
+----+------------+------------+
| N | Start | End |
+----+------------+------------+
| 1 | 2018-01-01 | 2018-05-31 |
| 1 | 2018-01-01 | 2018-06-31 |
+----+------------+------------+
Into, as silly as it looks I need to insert one row for each month in the range for each in the first table
+----+------------+
| N | month| |
+----+------------+
| 1 | 2018-01-01 |
| 1 | 2018-01-01 |
| 1 | 2018-02-01 |
| 1 | 2018-02-01 |
| 1 | 2018-03-01 |
| 1 | 2018-03-01 |
| 1 | 2018-04-01 |
| 1 | 2018-04-01 |
| 1 | 2018-05-01 |
| 1 | 2018-05-01 |
| 1 | 2018-06-01 |
+----+------------+
I been trying to follow SQL: Generate Record Per Month In Date Range but I haven't had any luck figuring out the result I want.
Thanks for helping

My best guess is that you want to show all begining of months that are in the interval start to end in your table.
create table t1 as
select date'2018-01-01' start_d, date'2018-05-31' end_d from dual union all
select date'2018-01-01' start_d, date'2018-06-30' end_d from dual;
with cal as
(select add_months(date'2018-01-01', rownum-1) month_d
from dual connect by level <= 12)
select cal.month_d from cal
join t1 on cal.month_d between t1.start_d and t1.end_d
order by 1;
MONTH_D
-------------------
01.01.2018 00:00:00
01.01.2018 00:00:00
01.02.2018 00:00:00
01.02.2018 00:00:00
01.03.2018 00:00:00
01.03.2018 00:00:00
01.04.2018 00:00:00
01.04.2018 00:00:00
01.05.2018 00:00:00
01.05.2018 00:00:00
01.06.2018 00:00:00
So probaly there is a cut & paste error in your expectation for January.
Some other points
do not use reserved word as start for column names
Use DATE format to store dates to aviod invalid entries such as 2018-06-31

You can use a recursive CTE. For example:
with
n (s, e, cur) as (
select s, e, s from t
union all
select s, e, add_months(cur, 1)
from n
where add_months(cur, 1) < e
)
select cur from n;
Result:
CUR
---------
01-JAN-18
01-JAN-18
01-FEB-18
01-FEB-18
01-MAR-18
01-MAR-18
01-APR-18
01-APR-18
01-MAY-18
01-MAY-18
01-JUN-18
See running example at db<>fiddle.

Related

How to concat two fields and use the result in WHERE clause?

I have to get all oldest records based on the date-time information.
Data
Id | External Id | Date | Time
1 | 1000 | 2020-08-18 00:00:00 | 02:30:22
2 | 1000 | 2020-08-12 00:00:00 | 12:45:51
3 | 1556 | 2020-08-17 00:00:00 | 10:09:01
4 | 1919 | 2020-08-14 00:00:00 | 18:19:18
5 | 1919 | 2020-08-14 00:00:00 | 11:45:21
6 | 1919 | 2020-08-14 00:00:00 | 15:54:15
Expected result
Id | External Id | Date | Time
2 | 1000 | 2020-08-12 00:00:00 | 12:45:51
3 | 1556 | 2020-08-17 00:00:00 | 10:09:01
5 | 1919 | 2020-08-14 00:00:00 | 11:45:21
I'm currently doing this
SELECT *
FROM RUN AS T1
WHERE CONCAT(T1.DATE, T1.TIME) = (
SELECT MIN(CONCAT(T2.DATE, T2.TIME))
FROM RUN AS T2
WHERE T2.EXTERNAL_ID = T1.EXTERNAL_ID
)
Is it a correct way to do ?
Thank you, regards
Update 1 : Data type
DATE column is datetime
TIME column is varchar
You can use a window function such as DENSE_RANK()
SELECT ID, External_ID, Date, Time
FROM
(
SELECT DENSE_RANK() OVER (PARTITION BY External_ID ORDER BY Date, Time) AS dr,
r.*
FROM run r
) AS q
WHERE dr = 1
Demo

Generate date range for missing dates and assign the current maximum value

I have a table like below in DB2 -
Date | Catg| Amount
2018-05-21 | 2 | 583227.57485
2018-05-21 | 5 | 2200097.73226
2018-05-22 | 2 | 116246.63551
2018-05-22 | 4 | 231116.66241
2018-05-22 | 5 | 244093.91680
2018-05-31 | 1 | 244714.77015
2018-05-31 | 2 | 288946.64734
2018-05-31 | 3 | 330801.32189
2018-05-31 | 5 | 345984.62256
2018-06-05 | 4 | 228612.55653
2018-06-05 | 5 | 244944.22519
2018-06-11 | 2 | 288940.63303
2018-06-11 | 3 | 344938.50723
2018-06-11 | 4 | 346234.65196
2018-06-11 | 5 | 375935.22568
I want to generate the report for the month of June till 22nd for every catg. So I want the report to be -
Date | Catg| Amount
2018-06-01 | 1 | 244714.77015 -- Being 5/31 is latest for 6/1
2018-06-01 | 2 | 288946.64734 -- Being 5/31 is latest for 6/1
2018-06-01 | 3 | 330801.32189 -- Being 5/31 is latest for 6/1
2018-06-01 | 4 | 231116.66241 -- Being 5/22 is latest for 6/1
2018-06-01 | 5 | 345984.62256 -- Being 5/31 is latest for 6/1
.
.
.
.
.
2018-06-22 | 1 | 244714.77015 -- Being 5/31 is latest for 6/22
2018-06-22 | 2 | 288940.63303 -- Being 6/11 is latest for 6/22
2018-06-22 | 3 | 344938.50723 -- Being 6/11 is latest for 6/22
2018-06-22 | 4 | 346234.65196 -- Being 6/11 is latest for 6/22
2018-06-22 | 5 | 375935.22568 -- Being 6/11 is latest for 6/22
I don't know if this even doable with SQL. I have successfully generated the dates but not sure how to assign the immediate previous values to them.
I have generated the dates through below code -
WITH DATE_TAB(DATES) AS (
SELECT DATE('2018-06-01') DATES
FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT DATES + 1 DAYS AS DATES
FROM DATE_TAB
WHERE DATES < '2018-06-22')
SELECT DATES
FROM DATE_TAB
Any help is greatly appreciated.
Thanks in advance!!!
The rest-part will be CROSS JOIN :
WITH DATE_TAB(DATES) AS (
SELECT DATE('2018-06-01') DATES
FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT DATES + 1 DAYS AS DATES
FROM DATE_TAB
WHERE DATES < '2018-06-22'
)
SELECT DISTINCT dt.DATES, dt1.Catg,
(SELECT t.Amount
FROM table t
WHERE t.catg = dt.catg and t.date <= dt.date
ORDER BY t.date desc
FETCH FIRST 1 ROW ONLY
)
FROM DATE_TAB dt
CROSS JOIN (SELECT DISTINCT Catg, Amount FROM table) dt1;
You can do:
with date_tab ( . . . )
select d.dte, c.catg,
(select t.amount
from t
where t.catg = c.catg and t.dte <= d.dte
order by t.dte desc
fetch first 1 row only
) as amount
from date_tab d cross join
(select distinct catg from t) c
order by d.dte, c.catg;

Postgres GROUP BY looking at dates ranges

I have a table with the history of the "Code" value changes. Every month this table gets a new record with the new value of the "Code" for the specified month.
+----------+------------+------------+------+
| Employee | FromDate | ToDate | Code |
+----------+------------+------------+------+
| Employee | 01/07/2016 | 31/07/2016 | 4 |
| Employee | 01/06/2016 | 30/06/2016 | 2 |
| Employee | 01/05/2016 | 31/05/2016 | 2 |
| Employee | 01/04/2016 | 30/04/2016 | 3 |
| Employee | 01/03/2016 | 31/03/2016 | 3 |
| Employee | 01/02/2016 | 29/02/2016 | 4 |
| Employee | 01/01/2016 | 31/01/2016 | 4 |
+----------+------------+------------+------+
I need to group by this data to get a new record every time "Code" changes and take the min value for the "From date" and the max value for the "To date". Data must be ordered descending by "FromDate". With my query I got this result:
+----------+------------+------------+------+
| Employee | FromDate | ToDate | Code |
+----------+------------+------------+------+
| Employee | 01/05/2016 | 30/06/2016 | 2 |
| Employee | 01/03/2016 | 30/04/2016 | 3 |
| Employee | 01/01/2016 | 31/07/2016 | 4 |
+----------+------------+------------+------+
It works fine but if the same "Code" has more the one date range (see the 4 code in the first table) I got a single row per code. I would like get this result with the 4 code in 2 records because its period is not continuos but it's broke by others codes (3 and 2):
+----------+------------+------------+------+
| Employee | FromDate | ToDate | Code |
+----------+------------+------------+------+
| Employee | 01/07/2016 | 31/07/2016 | 4 |
| Employee | 01/05/2016 | 30/06/2016 | 2 |
| Employee | 01/03/2016 | 30/04/2016 | 3 |
| Employee | 01/01/2016 | 29/02/2016 | 4 |
+----------+------------+------------+------+
I use this query:
SELECT
d."Employee",
MIN (d."FromDate") AS "FromDate",
MAX (d."ToDate") AS "ToDate",
d."Code"
FROM
(
SELECT
"Employees"."FromDate",
"Employees"."ToDate",
"Employees"."Code",
"Employees"."Employee"
FROM
schema_estelspa."Employees"
ORDER BY
"Employees"."FromDate" DESC
) d
GROUP BY
d."Code",
d."Employee"
ORDER BY
(MIN(d."FromDate")) DESC
Is there any trick to get the result I desired?
Date format is: dd/MM/yyyy
Here you need to make date range and make from_date as one part of group by column. you also need to self join to achieve this result. I prepared following SQL in teradata. Please make necessary changes for your database(coalesc is used as if null expression, you can use nvl or case statement as well)
Query:
SELECT E.EMPLOYEE, E.CODE,COALESCE(ET1.FROMdATE,E.FROMDATE)FROM_DATE ,MAX(E.TODATE)TO_D
FROM EMP_TEST E
LEFT OUTER JOIN EMP_TEST ET1
ON E.EMPLOYEE=ET1.EMPLOYEE
AND E.CODE=ET1.CODE
AND E.FromDate=ET1.ToDate+1
GROUP BY 1,2,3
ORDER BY FROM_DATE
Output:
Employee Code FROM_DATE TO_D
1 Employee 4 1/1/2016 2/29/2016
2 Employee 2 5/1/2016 6/30/2016
3 Employee 4 7/1/2016 7/31/2016
4 Employee 3 3/1/2016 4/30/2016
Standard recursive solution for connecting-the-dots
in practice, half-open intervals (lower_limit <= X < upper_limit) are easier to work with
Recursion starts with any segment that does not have a lower neigbor
adjacent segments are glued to the right side, building longer chains
the final query suppresses partial results
Note: the code below does not deal with overlapping intervals.
-- Table
CREATE TABLE ecode
( employee varchar NOT NULL
, code INTEGER NOT NULL
, fromdate DATE NOT NULL
, uptodate DATE NOT NULL
);
SET datestyle = 'DMY' ;
-- Data
INSERT INTO ecode(employee, fromdate, uptodate, code) VALUES
('Employee','01/07/2016','31/07/2016', 4)
, ('Employee','01/06/2016','30/06/2016', 2)
, ('Employee','01/05/2016','31/05/2016', 2)
, ('Employee','01/04/2016','30/04/2016', 3)
, ('Employee','01/03/2016','31/03/2016', 3)
, ('Employee','01/02/2016','29/02/2016', 4)
, ('Employee','01/01/2016','31/01/2016', 4)
;
-- Convert to half-open interval
UPDATE ecode SET uptodate = uptodate + '1 day'::interval;
-- SELECT * FROM ecode;
WITH RECURSIVE zzz AS (
SELECT employee, code, fromdate, uptodate
FROM ecode e0
WHERE NOT EXISTS ( -- first one in series
SELECT * FROM ecode nx
WHERE nx.employee = e0.employee
AND nx.code = e0.code
AND nx.uptodate = e0.fromdate
)
UNION ALL -- append consecutive intervals
SELECT e1.employee, e1.code, zzz.fromdate, e1.uptodate
FROM ecode e1
JOIN zzz ON zzz.employee = e1.employee
AND zzz.code = e1.code
AND zzz.uptodate = e1.fromdate
)
SELECT * FROM zzz
-- suppress the partial results
WHERE NOT EXISTS (SELECT * FROM ecode nx
WHERE nx.employee = zzz.employee
AND nx.code = zzz.code
AND nx.fromdate = zzz.uptodate
)
ORDER BY employee, code, fromdate
;
Result:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
SET
INSERT 0 7
UPDATE 7
employee | code | fromdate | uptodate
----------+------+------------+------------
Employee | 2 | 2016-05-01 | 2016-07-01
Employee | 3 | 2016-03-01 | 2016-05-01
Employee | 4 | 2016-01-01 | 2016-03-01
Employee | 4 | 2016-07-01 | 2016-08-01
(4 rows)

Postgres: Adjust monthly calculations based on goals set

Below is my table:
practice_id | practice_name | practice_location | practice_monthly_revenue | practice_no_of_patients | date
-------------+-------------------+-------------------+--------------------------+-------------------------+---------------------
6 | Practice Clinic 1 | Location1 | 10000 | 8 | 2016-01-12 00:00:00
7 | Practice Clinic 1 | Location1 | 12000 | 10 | 2016-02-12 00:00:00
8 | Practice Clinic 1 | Location1 | 8000 | 4 | 2016-03-12 00:00:00
9 | Practice Clinic 1 | Location1 | 15000 | 10 | 2016-04-12 00:00:00
10 | Practice Clinic 1 | Location1 | 7000 | 3 | 2016-05-12 00:00:00
11 | Practice Clinic 2 | Location2 | 15000 | 12 | 2016-01-13 00:00:00
12 | Practice Clinic 2 | Location2 | 9000 | 8 | 2016-02-13 00:00:00
13 | Practice Clinic 2 | Location2 | 5000 | 2 | 2016-03-03 00:00:00
14 | Practice Clinic 2 | Location2 | 12000 | 9 | 2016-04-13 00:00:00
----------------------------------------------------------------------------------------------------------------------------------
I am firing below query to get monthly revenue vs monthly goal:-
select [date:month], SUM(practice_monthly_revenue) as Monthly_Revenue, 100000/12 as Goals
from practice_info
where practice_name IN ('Practice Clinic 1')
group by [date:month], practice_name
ORDER BY [date:month] ASC
Where "Monthly_Revenue" refers to exact revenue every month while Goal was the exact revenue expected to be generated.
Now I am having issue to write a sql query to adjust the goals next month if the goals aren't met.
E.g. if in March the revenue generated is below 8k which is the monthly goal then the remaining amount in goal should be adjusted in next months goal.
Will it be possible to achieve this with a sql query or I will have to write a sql procedure for it?
EDIT:- I forgot to add that the db belong to postgres.
Goals can be counted as
with recursive goals(mon, val, rev) as
(select min([pinf.date:month]) as mon /* Starting month */, 8000 as val /* Starting goal value */, pinf.practice_monthly_revenue as rev
from practice_info pinf
where pinf.practice_name IN ('Practice Clinic 1')
union all
select goals.mon + 1 as mon, 8000 + greatest(0, goals.val - goals.rev) as val, pinf.practice_monthly_revenue as rev
from practice_info pinf, goals
where goals.mon + 1 = [pinf.date:month]
and pinf.practice_name IN ('Practice Clinic 1')
)
select * from goals;
Just integrate it with your query to compare goals and revenues. It can be not exactly what you want, but I do believe you'll get the main point.

Split column and values into multiple rows in Postgres

Suppose I have a table like this:
subject | flag | first_date | last_date
----------------+----------------------------------
this is a test | 2 | 1/1/2016 | 1/4/2016
into something like this:
subject | flag | date
----------------+------------------
this is a test | .5 | 1/1/2016
this is a test | .5 | 1/2/2016
this is a test | .5 | 1/3/2016
this is a test | .5 | 1/4/2016
Is there an easy way to do this?
You can use generate_series() to produce list of consecutive days between first_date and last_date:
with dates as (
select d::date, last_date- first_date+ 1 ct
from test, generate_series(first_date, last_date, '1d'::interval) d
)
select subject, flag/ ct flag, d date
from dates
cross join test;
subject | flag | date
----------------+------------------------+------------
this is a test | 0.50000000000000000000 | 2016-01-01
this is a test | 0.50000000000000000000 | 2016-01-02
this is a test | 0.50000000000000000000 | 2016-01-03
this is a test | 0.50000000000000000000 | 2016-01-04
(4 rows)