Problem: I have dated salary information stored in a table. I need to display one result per year. For each year, I want to display the max dated record from the year prior. The problem is that some years don’t have data (salary didn’t change). I need these rows to contain the max record prior to that year (it may be from 2 years before or even 3).
My query right now would work if each row has data… but it doesn’t account for years where there is no data. How can I update this sql to pull the desired results below:
Example of data:
sch_sal_svc.beg_date -------sch_sal_svc.beg_date.per_plan_data
1/1/2007---100
6/1/2007---200
1/1/2008---300
1/1/2011---400
8/1/2011---500
9/1/2012---600
Current Results
1/1/2008---200
1/1/2011---300
1/1/2012---500
Desired Results
1/1/2008---200
1/1/2009---300
1/1/2010---300
1/1/2011---300
1/1/2012---500
SQL:
SELECT
years.control_id,
years.ssn,
ebe.plan_id,
to_number(to_char(years.sal_date,'yyyy')),
null as per_plan_salary,
null as per_vest_hours,
null as per_credsvc_hours,
LEAST(s.rate_1,cl.comp_genl),
null as salary_1,
null as per_comm,
null as per_overtime,
null as per_ncr,
null as salary_2
FROM
sch_sal_svc s
, (select distinct ssn, control_id, TRUNC(beg_date,'YEAR') as sal_date from sch_sal_svc where beg_date > to_date('12/31/1900', 'mm/dd/yyyy')) years
, employee_benefit_elig ebe, compliance_limits cl
WHERE
years.ssn = ebe.ssn
and years.control_id = ebe.control_id
and to_number(to_char(years.sal_date,'yyyy')) = cl.limit_year
and to_number(to_char(years.sal_date,'yyyy')) <= to_number(to_char(sysdate,'yyyy'))
and s.beg_date = (
select max(s2.beg_date) from sch_sal_svc s2
where s2.ssn = years.ssn and s2.control_id = years.control_id
and s2.beg_date <= years.sal_date
)
and s.ssn = years.ssn
and s.control_id = years.control_id
and ebe.benefit_id = 'DB'
and ebe.control_id = 'CLIENT'
and ebe.plan_id in ('100', '200')
CREATE TABLE sch_sal_svc
(
beg_date DATE
, per_plan_data NUMBER
);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2007', 'DD/MM/YYYY'), 100);
INSERT INTO sch_sal_svc VALUES(TO_DATE('06/01/2007', 'DD/MM/YYYY'), 200);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2008', 'DD/MM/YYYY'), 300);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2011', 'DD/MM/YYYY'), 400);
INSERT INTO sch_sal_svc VALUES(TO_DATE('08/01/2011', 'DD/MM/YYYY'), 500);
INSERT INTO sch_sal_svc VALUES(TO_DATE('09/01/2012', 'DD/MM/YYYY'), 600);
SELECT MIN(beg_date) FROM sch_sal_svc;
-- 2007-01-01 00:00:00
SELECT d.r_level + NUMTOYMINTERVAL(1, 'YEAR') AS d_date
, NVL -- the salary must be updated at least once in three years
(
NVL
(
NVL
(
s.per_plan_data
, LAG(s.per_plan_data, 1) OVER (PARTITION BY 1 ORDER BY d.r_level)
)
, LAG(s.per_plan_data, 2) OVER (PARTITION BY 1 ORDER BY d.r_level)
)
, LAG(s.per_plan_data, 3) OVER (PARTITION BY 1 ORDER BY d.r_level)
) AS lag_per_plan_data
FROM
(
SELECT DATE'2006-01-01' + NUMTOYMINTERVAL(LEVEL, 'YEAR') AS r_level -- min beg_date minus 1
FROM DUAL
CONNECT BY
LEVEL < (SELECT TO_CHAR(MAX(beg_date), 'YYYY') - TO_CHAR(MIN(beg_date), 'YYYY') + 2 FROM sch_sal_svc)
) d
LEFT JOIN
(
SELECT beg_date
, per_plan_data
FROM sch_sal_svc
WHERE (beg_date) IN
(
SELECT MAX(beg_date)
FROM sch_sal_svc
GROUP BY
TRUNC(beg_date, 'YYYY')
)
) s
ON d.r_level = TRUNC(s.beg_date, 'YYYY')
;
/*
2008-01-01 00:00:00 200
2009-01-01 00:00:00 300
2010-01-01 00:00:00 300
2011-01-01 00:00:00 300
2012-01-01 00:00:00 500
2013-01-01 00:00:00 600
*/
Related
I am getting data based on maintenance_due_date before 7 days or sysdate greater then maintenance_due_date. Now I want to add one more condition that if data not available in maintenace_schedule table against instrument_no and plan_id then also I want to get rows against plan_id from activity_detail table.
SELECT
instrument_no,
frequency,
detail,
plan_id,
activity_id,
period,
-- maintenance_date,
maintenance_due_date
FROM
(
SELECT
ms.instrument_no,
ad.frequency,
ad.detail,
ad.plan_id,
ad.activity_id,
ad.period,
-- ms.maintenance_date,
MAX(ms.maintenance_due_date) maintenance_due_date
FROM
maintenance_schedule ms,
activity_detail ad
--left join maintenance_schedule ms1 on ms1.plan_id = ad.plan_id and ms1.instrument_no = ms.instrument_no --am.inactive <> 1
WHERE
ms.instrument_no = 10073
AND ad.plan_id = 100
AND ms.plan_id = ad.plan_id
and ad.end_date is null
AND ms.activity_id = ad.activity_id
GROUP BY
ms.instrument_no,
ad.frequency,
ad.detail,
ad.plan_id,
ad.activity_id,
ad.period
-- ms.maintenance_date
)
WHERE
( maintenance_due_date BETWEEN sysdate AND sysdate + 7
OR maintenance_due_date < sysdate)
order by activity_id asc, maintenance_due_date asc;
Your inner query should be changed to use LEFT JOIN so you could handle the null values from a missing row within both of WHERE clauses (inner and outer query). For readability I moved your inner query to CTE definitions and named it activities. You don't need to do that - you can leave it where it was if you wish so.
Here are your (imagined) sample data and the new activities CTE with some comments in the code:
WITH
maintenance_schedule AS
( Select 100 "PLAN_ID", 1 "ACTIVITY_ID", To_Date('20.12.2022', 'dd.mm.yyyy') "MAINTENANCE_DUE_DATE", 10073 "INSTRUMENT_NO", 'some other data' "SOME_COLUMN" From Dual Union All
Select 100 "PLAN_ID", 2 "ACTIVITY_ID", To_Date('29.12.2022', 'dd.mm.yyyy') "MAINTENANCE_DUE_DATE", 10073 "INSTRUMENT_NO", 'some other data' "SOME_COLUMN" From Dual
),
activity_detail AS
( Select 1 "ACTIVITY_ID", 100 "PLAN_ID", 'details about ...' "DETAIL", 'frequency X...' "FREQUENCY", 'A' "PERIOD", Null "END_DATE" From Dual Union All
Select 2 "ACTIVITY_ID", 100 "PLAN_ID", 'details about ...' "DETAIL", 'frequency Y...' "FREQUENCY", 'B' "PERIOD", Null "END_DATE" From Dual Union All
Select 3 "ACTIVITY_ID", 100 "PLAN_ID", 'details about ...' "DETAIL", 'frequency Z...' "FREQUENCY", 'C' "PERIOD", Null "END_DATE" From Dual
),
activities AS
( SELECT ms.instrument_no,
ad.frequency,
ad.detail,
ad.plan_id,
ad.activity_id,
ad.period,
MAX(ms.maintenance_due_date) maintenance_due_date
FROM activity_detail ad
-- LEFT JOIN (line below) is the solution to your problem because it gives you options to handle null values from a missing row
LEFT JOIN maintenance_schedule ms ON( ms.plan_id = ad.plan_id And ms.activity_id = ad.activity_id )
WHERE ( Nvl(ms.instrument_no, 10073) = 10073 AND ad.plan_id = 100 AND ad.end_date Is Null)
-- you can use this ---> ( Nvl(ms.instrument_no, 10073) = 10073 AND ad.plan_id = 100 AND ad.end_date Is Null)
-- OR this ---> ( ms.instrument_no = 10073 AND ad.plan_id = 100 AND ad.end_date Is Null) OR ( ms.instrument_no Is Null AND ad.plan_id = 100 AND ad.end_date Is Null)
GROUP BY ms.instrument_no,
ad.frequency,
ad.detail,
ad.plan_id,
ad.activity_id,
ad.period
)
with above data your main SQL shoulld be like below:
SELECT instrument_no,
frequency,
detail,
plan_id,
activity_id,
period,
maintenance_due_date
FROM activities -- moved your subquery to CTE definition above for readability - you can leave it here if you wish so
WHERE ( Nvl(maintenance_due_date, sysdate + 1) BETWEEN sysdate AND sysdate + 7 OR maintenance_due_date < sysdate)
-- you can use this ---> ( Nvl(maintenance_due_date, sysdate + 1) BETWEEN sysdate AND sysdate + 7 OR maintenance_due_date < sysdate)
-- OR this ---> ( maintenance_due_date BETWEEN sysdate AND sysdate + 7 OR maintenance_due_date < sysdate OR maintenance_due_date Is Null)
ORDER BY activity_id, maintenance_due_date
... R e s u l t :
INSTRUMENT_NO
FREQUENCY
DETAIL
PLAN_ID
ACTIVITY_ID
PERIOD
MAINTENANCE_DUE_DATE
10073
frequency X...
details about ...
100
1
A
20-DEC-22
10073
frequency Y...
details about ...
100
2
B
29-DEC-22
frequency Z...
details about ...
100
3
C
Additionaly, you can use, let's say, Nvl(INSTRUMENT_NO , 0) in the SELECT list to give a value for missing data if you want.
I have a transaction table that stores amount paid(+amount) and corrected (-ve amount). I am looking for a query that would ignore a positive and a negative matching value of the amount for a date and post the sum of remaining number of transactions ignoring the 2 .
Id Dept Date Amount
1 A 21-Apr-21 1100
1 A 21-Apr-21 1100
1 A 21-Apr-21 -1100
1 A 07-Apr-21 1100
1 A 03-Feb-21 100
1 A 12-Jan-21 500
The sql query should ignore Rows 2 and 3 as the amount was corrected and should not be counted as a transaction.
o/p should be
Id Dept sum(Amount) count(transaction)
1 A 2800 4
If I got you well, you can use below solution for that purpose.
I first ranked all the occurrences of the same amount value, before I grouped them in order to make oracle ignore all matching positive and negative values.
with YourSample (Id, Dept, Date#, Amount) as (
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('21-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), -1100 from dual union all
select 1, 'A', to_date('07-Apr-21', 'dd-Mon-RR', 'nls_date_language=english'), 1100 from dual union all
select 1, 'A', to_date('03-Feb-21', 'dd-Mon-RR', 'nls_date_language=english'), 100 from dual union all
select 1, 'A', to_date('12-Jan-21', 'dd-Mon-RR', 'nls_date_language=english'), 500 from dual
)
, ranked_rws as (
select Id, Dept, Date#
, abs(Amount)Amount
, sign(AMOUNT) row_sign
, row_number() OVER (PARTITION BY Id, Dept, Amount order by date#, rownum) rn
from YourSample t
)
, ingored_matched_pos_neg_values as (
select ID, DEPT, sum(row_sign) * AMOUNT AMOUNT/*, sum(row_sign)*/
from ranked_rws
group by ID, DEPT, AMOUNT, RN
having sum(row_sign) != 0 /* this line filters out all matching positive
and negatives values (equality in terms of occurrences)*/
)
select ID, DEPT, sum(AMOUNT) sum, count(*) transactions
from ingored_matched_pos_neg_values
group by ID, DEPT
;
demo
Maybe some idea like this could work.
SELECT Id, Dept, Date, Amount, COUNT(*) AS RecordCount
INTO #temptable
FROM table GROUP BY ...
SELECT
t1.Id
,t1.Dept
,t1.Date
,(t1.RecordCount - COALESCE(t2.RecordCount, 0)) * t1.Amount
,t1.RecordCount - COALESCE(t2.RecordCount, 0)
FROM #temptable t1
LEFT JOIN #temptable t2 ON
t1.Id = t2.Id
AND t1.Dept = t2.Dept
AND t1.Date = t2.Date
AND (t1.Amount * -1) = t2.Amount
I have a table that has aggregations down to the hour level YYYYMMDDHH. The data is aggregated and loaded by an external process (I don't have control over). I want to test the data on a monthly basis.
The question I am looking to answer is: Does every hour in the month exist?
I'm looking to produce output that will return a 1 if the hour exists or 0 if the hour does not exist.
The aggregation table looks something like this...
YYYYMM YYYYMMDD YYYYMMDDHH DATA_AGG
201911 20191101 2019110100 100
201911 20191101 2019110101 125
201911 20191101 2019110103 135
201911 20191101 2019110105 95
… … … …
201911 20191130 2019113020 100
201911 20191130 2019113021 110
201911 20191130 2019113022 125
201911 20191130 2019113023 135
And defined as...
CREATE TABLE YYYYMMDDHH_DATA_AGG AS (
YYYYMM VARCHAR,
YYYYMMDD VARCHAR,
YYYYMMDDHH VARCHAR,
DATA_AGG INT
);
I'm looking to produce the following below...
YYYYMMDDHH HOUR_EXISTS
2019110100 1
2019110101 1
2019110102 0
2019110103 1
2019110104 0
2019110105 1
... ...
In the example above, two hours do not exist, 2019110102 and 2019110104.
I assume I'd have to join the aggregation table against a computed table that contains all the YYYYMMDDHH combos???
The database is Snowflake, but assume most generic ANSI SQL queries will work.
You can get what you want with a recursive CTE
The recursive CTE generates the list of possible Hours. And then a simple left outer join gets you the flag for if you have any records that match that hour.
WITH RECURSIVE CTE (YYYYMMDDHH) as
(
SELECT YYYYMMDDHH
FROM YYYYMMDDHH_DATA_AGG
WHERE YYYYMMDDHH = (SELECT MIN(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG)
UNION ALL
SELECT TO_VARCHAR(DATEADD(HOUR, 1, TO_TIMESTAMP(C.YYYYMMDDHH, 'YYYYMMDDHH')), 'YYYYMMDDHH') YYYYMMDDHH
FROM CTE C
WHERE TO_VARCHAR(DATEADD(HOUR, 1, TO_TIMESTAMP(C.YYYYMMDDHH, 'YYYYMMDDHH')), 'YYYYMMDDHH') <= (SELECT MAX(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG)
)
SELECT
C.YYYYMMDDHH,
IFF(A.YYYYMMDDHH IS NOT NULL, 1, 0) HOUR_EXISTS
FROM CTE C
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG A
ON C.YYYYMMDDHH = A.YYYYMMDDHH;
If your timerange is too long you'll have issues with the cte recursing too much. You can create a table or temp table with all of the possible hours instead. For example:
CREATE OR REPLACE TEMPORARY TABLE HOURS (YYYYMMDDHH VARCHAR) AS
SELECT TO_VARCHAR(DATEADD(HOUR, SEQ4(), TO_TIMESTAMP((SELECT MIN(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG), 'YYYYMMDDHH')), 'YYYYMMDDHH')
FROM TABLE(GENERATOR(ROWCOUNT => 10000)) V
ORDER BY 1;
SELECT
H.YYYYMMDDHH,
IFF(A.YYYYMMDDHH IS NOT NULL, 1, 0) HOUR_EXISTS
FROM HOURS H
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG A
ON H.YYYYMMDDHH = A.YYYYMMDDHH
WHERE H.YYYYMMDDHH <= (SELECT MAX(YYYYMMDDHH) FROM YYYYMMDDHH_DATA_AGG);
You can then fiddle with the generator count to make sure you have enough hours.
You can generate a table with every hour of the month and LEFT OUTER JOIN your aggregation to it:
WITH EVERY_HOUR AS (
SELECT TO_CHAR(DATEADD(HOUR, HH, TO_DATE(YYYYMM::TEXT, 'YYYYMM')),
'YYYYMMDDHH')::NUMBER YYYYMMDDHH
FROM (SELECT DISTINCT YYYYMM FROM YYYYMMDDHH_DATA_AGG) t
CROSS JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY NULL) - 1 HH
FROM TABLE(GENERATOR(ROWCOUNT => 745))
) h
QUALIFY YYYYMMDDHH < (YYYYMM + 1) * 10000
)
SELECT h.YYYYMMDDHH, NVL2(a.YYYYMM, 1, 0) HOUR_EXISTS
FROM EVERY_HOUR h
LEFT OUTER JOIN YYYYMMDDHH_DATA_AGG a ON a.YYYYMMDDHH = h.YYYYMMDDHH
Here's something that might help get you started. I'm guessing you want to have 'synthetic' [YYYYMMDD] values? Otherwise, if the value aren't there, then they shouldn't appear in the list
DROP TABLE IF EXISTS #_hours
DROP TABLE IF EXISTS #_temp
--Populate a table with hours ranging from 00 to 23
CREATE TABLE #_hours ([hour_value] VARCHAR(2))
DECLARE #_i INT = 0
WHILE (#_i < 24)
BEGIN
INSERT INTO #_hours
SELECT FORMAT(#_i, '0#')
SET #_i += 1
END
-- Replicate OP's sample data set
CREATE TABLE #_temp (
[YYYYMM] INTEGER
, [YYYYMMDD] INTEGER
, [YYYYMMDDHH] INTEGER
, [DATA_AGG] INTEGER
)
INSERT INTO #_temp
VALUES
(201911, 20191101, 2019110100, 100),
(201911, 20191101, 2019110101, 125),
(201911, 20191101, 2019110103, 135),
(201911, 20191101, 2019110105, 95),
(201911, 20191130, 2019113020, 100),
(201911, 20191130, 2019113021, 110),
(201911, 20191130, 2019113022, 125),
(201911, 20191130, 2019113023, 135)
SELECT X.YYYYMM, X.YYYYMMDD, X.YYYYMMDDHH
-- Case: If 'target_hours' doesn't exist, then 0, else 1
, CASE WHEN X.target_hours IS NULL THEN '0' ELSE '1' END AS [HOUR_EXISTS]
FROM (
-- Select right 2 characters from converted [YYYYMMDDHH] to act as 'target values'
SELECT T.*
, RIGHT(CAST(T.[YYYYMMDDHH] AS VARCHAR(10)), 2) AS [target_hours]
FROM #_temp AS T
) AS X
-- Right join to keep all of our hours and only the target hours that match.
RIGHT JOIN #_hours AS H ON H.hour_value = X.target_hours
Sample output:
YYYYMM YYYYMMDD YYYYMMDDHH HOUR_EXISTS
201911 20191101 2019110100 1
201911 20191101 2019110101 1
NULL NULL NULL 0
201911 20191101 2019110103 1
NULL NULL NULL 0
201911 20191101 2019110105 1
NULL NULL NULL 0
With (almost) standard sql, you can do a cross join of the distinct values of YYYYMMDD to a list of all possible hours and then left join to the table:
select concat(d.YYYYMMDD, h.hour) as YYYYMMDDHH,
case when t.YYYYMMDDHH is null then 0 else 1 end as hour_exists
from (select distinct YYYYMMDD from tablename) as d
cross join (
select '00' as hour union all select '01' union all
select '02' union all select '03' union all
select '04' union all select '05' union all
select '06' union all select '07' union all
select '08' union all select '09' union all
select '10' union all select '11' union all
select '12' union all select '13' union all
select '14' union all select '15' union all
select '16' union all select '17' union all
select '18' union all select '19' union all
select '20' union all select '21' union all
select '22' union all select '23'
) as h
left join tablename as t
on concat(d.YYYYMMDD, h.hour) = t.YYYYMMDDHH
order by concat(d.YYYYMMDD, h.hour)
Maybe in Snowflake you can construct the list of hours with a sequence much easier instead of all those UNION ALLs.
This version accounts for the full range of days, across months and years. It's a simple cross join of the set of possible days with the set of possible hours of the day -- left joined to actual dates.
set first = (select min(yyyymmdd::number) from YYYYMMDDHH_DATA_AGG);
set last = (select max(yyyymmdd::number) from YYYYMMDDHH_DATA_AGG);
with
hours as (select row_number() over (order by null) - 1 h from table(generator(rowcount=>24))),
days as (
select
row_number() over (order by null) - 1 as n,
to_date($first::text, 'YYYYMMDD')::date + n as d,
to_char(d, 'YYYYMMDD') as yyyymmdd
from table(generator(rowcount=>($last-$first+1)))
)
select days.yyyymmdd || lpad(hours.h,2,0) as YYYYMMDDHH, nvl2(t.yyyymmddhh,1,0) as HOUR_EXISTS
from days cross join hours
left join YYYYMMDDHH_DATA_AGG t on t.yyyymmddhh = days.yyyymmdd || lpad(hours.h,2,0)
order by 1
;
$first and $last can be packed in as sub-queries if you prefer.
I'm working with SQL Teradata and I have a table as such:
cust_id start_dt end_dt amount is_current_y_n
12345 1/8/2018 7/8/2018 7044 N
12345 7/9/2018 7/10/2018 8142 N
12345 7/11/2018 7/13/2018 7643 N
12345 7/14/2018 7/14/2018 8630 N
12345 7/14/2018 7/19/2018 5597 N
12345 7/20/2018 12/31/9999 5680 Y
Another case that I've seen:
cust_id start_dt end_dt amount is_current_y_n
54321 1/1/2015 12/31/9999 8650 Y
I need to calculate with SQL the average amount for the past:
7 days
30 days
90 days
180 days
"Average", meaning that if during the past 7 days the amount changed from 1000 to 2000 in the 3rd day, the average should be:
(1000x3 + 2000x4)/7
I tried to join the table with a date table but it's not very efficient.
Is there any efficient way to achieve that?
It can probably be done via Recursive Common Table Expression Query.
To unfold those date ranges.
With the amounts for each date, the CTE can be joined back to the table to get those averages.
I couldn't test the SQL on a TeraData (don't have it).
But it should almost work on that RDBMS (probably)
WITH RECURSIVE CTE (cust_id, dt, amount, start_dt, end_dt) AS
(
SELECT cust_id, start_dt as dt, amount, start_dt,
case when end_dt - start_dt > 4200 then start_dt else end_dt end
FROM table_as_such
UNION ALL
SELECT cust_id, dt+1, amount, start_dt, end_dt
FROM CTE
WHERE dt < end_dt
)
SELECT t.cust_id, t.start_dt
, ROUND(AVG(case when CTE.dt between t.start_dt - 7 and t.start_dt then CTE.amount end),2) as avg7
, ROUND(AVG(case when CTE.dt between t.start_dt - 30 and t.start_dt then CTE.amount end),2) as avg30
, ROUND(AVG(case when CTE.dt between t.start_dt - 90 and t.start_dt then CTE.amount end),2) as avg90
, ROUND(AVG(case when CTE.dt between t.start_dt - 180 and t.start_dt then CTE.amount end),2) as avg180
FROM table_as_such t
JOIN CTE ON (CTE.cust_id = t.cust_id AND CTE.dt between t.start_dt - 180 and t.start_dt)
GROUP BY t.cust_id, t.start_dt
ORDER BY t.cust_id, t.start_dt;
Sample Data Used:
create table table_as_such (id int not null primary key, cust_id int, start_dt date, end_dt date, amount int, is_current_y_n char(1));
insert into table_as_such values (1,12345,'2018-01-08','2018-07-08',7044,'N');
insert into table_as_such values (2,12345,'2018-07-09','2018-07-10',8142,'N');
insert into table_as_such values (3,12345,'2018-07-11','2018-07-13',7643,'N');
insert into table_as_such values (4,12345,'2018-07-14','2018-07-14',8630,'N');
insert into table_as_such values (5,12345,'2018-07-14','2018-07-19',5597,'N');
insert into table_as_such values (6,12345,'2018-07-20','9999-12-31',5680,'Y');
maybe the temporal feature of Teradata can help you in this case. This is due to the PERIOD datatyp and the function to expand.
check this example for this feature and your intention:
database demo;
create table demoDateExpand (
myID integer
,myUser VARCHAR(100)
,myAmount DECIMAL(10,2)
,startDT DATE
,endDT DATE
) no primary index;
insert into demoDateExpand values (1, 'User01', 2.5, '2018-01-01', '2018-01-05');
insert into demoDateExpand values (2, 'User01', 3.0, '2018-01-08', '2018-01-15');
insert into demoDateExpand values (3, 'User01', 1.5, '2018-01-11', '2018-01-25');
insert into demoDateExpand values (4, 'User02', 2.0, '2018-01-01', '2018-01-15');
insert into demoDateExpand values (5, 'User02', 2.5, '2018-01-05', '2018-01-25');
insert into demoDateExpand values (6, 'User02', 4.5, '2018-01-26', '2018-01-27');
insert into demoDateExpand values (7, 'User03', 1.0, '2018-01-10', '2018-01-15');
insert into demoDateExpand values (8, 'User03', 3.5, '2018-01-16', '2018-01-25');
select myID
,myUser
,myAmount
,startDT
,endDT
,period(startDT, endDT)
from demoDateExpand
;
select myID
,myUser
,myAmount
,BEGIN(myDate)
from demoDateExpand
expand on period(startDT, endDT) AS myDate BY ANCHOR DAY
order by myID, myDate
;
I managed to create my own query with the help of a table with dates:
2017-07-11
2017-07-12
...
My query is:
sel
c.cust_id
,avg(case when c.cal_dt between '2017-07-01' and '2018-01-01' then c.amount end) as avg_180
,avg(case when c.cal_dt between '2017-10-01' and '2018-01-01' then c.amount end) as avg_90
,avg(case when c.cal_dt between '2017-12-01' and '2018-01-01' then c.amount end) as avg_30
,avg(case when c.cal_dt between '2017-12-24' and '2018-01-01' then c.amount end) as avg_7
from
(
sel b.cust_id
,a.cal_dt
,b.amount
from
(
sel *
from CALENDAR_DAILY_TABLE
where cal_dt between '2017-07-01' and '2018-01-01'
) as a
join
(
sel *
from MY_TABLE
where (start_dt > '2017-07-01' or end_dt='9999-12-31')
) as b
on b.start_dt<=a.cal_dt and a.cal_dt<=b.end_dt
) as c
where c.cust_id ='12345'
group by c.cust_id
The result is:
cust_id avg_180 avg_90 avg_30 avg_7
12345 1.34 1.34 1.34 1.34
Thanks!
How can I calculate number of leaves taken by Employee in a month in SQL Server?
Empid Leaveid Fromdate Todate No of days
100 L1 2008-05-10 2008-05-13 3
100 L2 2008-05-20 2008-05-21 1
100 L3 2008-05-25 2008-06-05 12
100 L4 2009-01-20 2009-01-22 2
100 L5 2009-02-14 2009-02-20 6
100 L6 2009-02-28 2009-02-28 1
Use SUM and GROUP BY.
SELECT Empid, SUM([No of Days]) AS Days
FROM leavetable
GROUP BY Empid
Read more here GROUP BY and here SUM.
This would give you:
Empid Days
100 25
Or if you mean count the amount of times an employee has been off use Count.
SELECT Empid, Count(Leaveid) AS LeaveTotal
FROM leavetable
GROUP BY Empid
Read more here COUNT.
This would give you:
Empid LeaveTotal
100 6
CREATE TABLE #EmpLeave (EmpId INT, LeaveID VARCHAR(5), FromDate Date, Todate Date, NoOfDays INT)
INSERT INTO #EmpLeave VALUES
(100, 'L1', '2008-05-10', '2008-05-13', 4),
(100, 'L2', '2008-05-20', '2008-05-21', 2),
(100, 'L3', '2008-05-25', '2008-06-05', 12),
(100, 'L4', '2009-01-20', '2009-01-22', 3),
(100, 'L5', '2009-02-14', '2009-02-20', 7),
(100, 'L6', '2009-02-28', '2009-02-28', 1)
;With CTE_leave AS (
select * from #EmpLeave where Month(FromDate) <> Month(ToDate) )
SELECT a.EMPID,DATENAME(MONTH,FromDate ) Month ,SUM(a.LeaveCount) LeaveTaken
FROM
(
SELECT C.EMPID,C.LeaveID, C.FromDate, DATEDIFF(DD,C.FromDate,EOMonth(FromDate) ) + 1 LeaveCount
FROM CTE_Leave C
UNION
SELECT C.EMPID, C.LeaveID, DATEADD(DD,1, EOMonth(FromDate)) fromDate, DATEDIFF(DD,DATEADD(DD,1, EOMonth(FromDate)), C.ToDate ) + 1 LeaveCount
FROM CTE_Leave C
UNION
SELECT EMPID, LeaveID, FromDate, DATEDIFF(DD, FromDate, Todate) + 1 LeaveCount FROM #EmpLeave where Month(FromDate) = Month(ToDate)
) a
group by a.EMPID, DATENAME(MONTH,FromDate )
#Anchalose find above possible solution. Let us know solves your query.
Please do post schema details along with the desired result also while posting your query, it will help us understand the problem better.
#Matt, please refrain from using cuss words and try to post the solution as per the requirement.