I have following table:
create table test_table
(
employee_id integer,
salary_year integer,
raise_in_salary_perentage decimal(18,2),
annual_salary decimal(18,2)
);
**Test Data is following: **
insert into test_table values ( 1,2016, 0 , 100);
insert into test_table values ( 1,2017, 10, 100);
insert into test_table values ( 1,2018, 10, 100);
insert into test_table values ( 1,2019, 0, 100);
insert into test_table values ( 1,2020, 10, 100);
insert into test_table values ( 2,2016, 10 , 100);
insert into test_table values ( 2,2017, 10, 100);
insert into test_table values ( 2,2018, 0, 100);
insert into test_table values ( 2,2019, 0, 100);
insert into test_table values ( 2,2020, 0, 100);
I am trying to achieve following output:
The cumulative salary should include the running total of annual salary over years for each employee.
There is a percentage of raise every year, so if current year has a raise the cumulative salary will be sum of previous salaries plus the amount received in raise.
I tried to achieve it using following SQL, but results does seems right. Will be thankful for solution.
SELECT *
,sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) AS cummulative_salary
,(
sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
)
) + (
sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
)
) * (
sum(raise_in_salary_perentage) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) / 100
) AS csalary
FROM test_table;
Based on your description, the increase in salary should be cumulative. However, a given year's increase should not affect previous years.
That is not what your desired results show. Based on my interpretation, I think you want:
with recursive cte as (
select employee_id, salary_year, (t.annual_salary * (1 + raise_in_salary_perentage / 100.0))::numeric(18, 2) as annual_salary,
raise_in_salary_perentage,
(t.annual_salary * (1 + raise_in_salary_perentage / 100.0))::numeric(18, 2) as total
from test_table t
where salary_year = 2016
union all
select t.employee_id, t.salary_year, (cte.annual_salary * (1 + t.raise_in_salary_perentage / 100.0))::numeric(18, 2),
t.raise_in_salary_perentage,
(cte.total + cte.annual_salary * (1 + t.raise_in_salary_perentage / 100.0))::numeric(18, 2)
from cte join
test_table t
on t.employee_id = cte.employee_id and t.salary_year = cte.salary_year + 1
)
select *
from cte
order by employee_id, salary_year;
Here is a db<>fiddle.
Related
I am successfully using the code below to generate 20 parent records with 15 children each. How can I modify this code to generate a random number (ie 5-20) random child records for each parent.
CREATE TABLE emp_info
(
empid INTEGER,
empname VARCHAR2(50)
);
CREATE TABLE emp_attendance
(empid INTEGER,
start_date DATE,
end_date DATE
);
-- option with CTE
insert all
when rn = 1 then into emp_info (empid, empname) values (id, name)
when 1 = 1 then into emp_attendance (empid, start_date, end_date)
values (id, d1, d1 + dbms_random.value (0, .75))
with t as (select nvl(max(empid), 0) maxid from emp_info)
select ceil(maxid + level/15) id,
case mod(maxid + level, 15) when 1 then 1 end rn,
dbms_random.string('U', dbms_random.value(3, 15)) name,
trunc(sysdate) + dbms_random.value (1, 30) d1
from t connect by level <= 20 * 15;
-- 20 parent records 15 children each
You can take advantage of the ROW_NUMBER function as follows:
-- see the inline comments for explanation
insert all
when rn = 1 then into emp_info (empid, empname) values (id, name)
when 1 = 1 then into emp_attendance (empid, start_date, end_date)
values (id, d1, d1 + dbms_random.value (0, .75))
select * from
(
with t as (select nvl(max(empid), 0) maxid from emp_info)
select ceil(maxid + level/15) id,
case mod(maxid + level, 15) when 1 then 1 end rn,
dbms_random.string('U', dbms_random.value(3, 15)) name,
trunc(sysdate) + dbms_random.value (1, 30) d1,
case when row_number() over (partition by ceil(maxid + level/15)
order by level) > 5 then
dbms_random.value(5, 20)
else 5 end as random_val -- added this expression as column
from t connect by level <= 20 * 20 -- changed it from 15 to 20
)
where random_val <= 12; -- this is random number less than 20
I´m struggling with this update...
I would like to update the column result from table #b with the column columna_1 resulting from the last row of the select below. The matching filed is id_car.
So if you run the select below, the last row of columna_1 is 42229.56230859 and the only record of #b would be:
id_car = 1
result = 42229.56230859
can anyone help please? thanks!!!
CREATE TABLE #b(
id_car int,
result money
)
INSERT INTO #b VALUES (1,0)
CREATE TABLE #f(
id_car int,
fecha datetime,
sales money
)
INSERT INTO #f VALUES (1,'2010-10-31',1.10912)
INSERT INTO #f VALUES (1,'2010-11-30',1.77227)
INSERT INTO #f VALUES (1,'2010-12-31',0.66944)
INSERT INTO #f VALUES (1,'2011-01-31',0.34591)
INSERT INTO #f VALUES (1,'2011-02-28',1.73468)
INSERT INTO #f VALUES (1,'2011-03-31',1.50102)
INSERT INTO #f VALUES (1,'2011-04-30',0.87270)
INSERT INTO #f VALUES (1,'2011-05-31',1.51555)
;with ctesource as
(
select
id_car, fecha, sales,
sum( log( 1e0 + sales ) ) over ( partition by id_car order by fecha rows unbounded preceding) as LogAssetValue
from
#f
WHERE id_car= 1 and fecha > '2010-10-30'
)
select convert(varchar, fecha, 104) AS fecha1, fecha,
CAST(SUM(exp(LogAssetValue)-1)*100 AS numeric(20, 8)) as columna_1
from ctesource
GROUP BY fecha
order by fecha;
SqlFiddleDemo
;with
ctesource as (
SELECT
id_car, fecha, sales,
sum( log( 1e0 + sales ) ) over ( partition by id_car order by fecha rows unbounded preceding) as LogAssetValue,
row_number() over (order by fecha) as rn
FROM
f
WHERE id_car= 1
and fecha > '2010-10-30'
)
SELECT convert(varchar, fecha, 104) AS fecha1,
fecha,
rn,
CAST(SUM(exp(LogAssetValue)-1)*100 AS numeric(20, 8)) as columna_1
FROM ctesource
GROUP BY fecha, rn
HAVING rn = (SELECT max(rn) from ctesource)
I'm currently working on a sample script which allows me to calculate the sum of the previous two rows and the current row. However, I would like to make the number '2' as a variable. I've tried declaring a variable, or directly casting in the query, yet a syntax error always pops up. Is there a possible solution?
DECLARE #myTable TABLE (myValue INT)
INSERT INTO #myTable ( myValue ) VALUES ( 5)
INSERT INTO #myTable ( myValue ) VALUES ( 6)
INSERT INTO #myTable ( myValue ) VALUES ( 7)
INSERT INTO #myTable ( myValue ) VALUES ( 8)
INSERT INTO #myTable ( myValue ) VALUES ( 9)
INSERT INTO #myTable ( myValue ) VALUES ( 10)
SELECT
SUM(myValue) OVER (ORDER BY myValue
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM #myTable
DECLARE #test VARCHAR = 1
DECLARE #sqlCommand VARCHAR(1000)
DECLARE #myTable TABLE (myValue INT)
INSERT INTO #myTable ( myValue ) VALUES ( 5)
INSERT INTO #myTable ( myValue ) VALUES ( 6)
INSERT INTO #myTable ( myValue ) VALUES ( 7)
INSERT INTO #myTable ( myValue ) VALUES ( 8)
INSERT INTO #myTable ( myValue ) VALUES ( 9)
INSERT INTO #myTable ( myValue ) VALUES ( 10)
SET #sqlCommand = 'SELECT SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN ' + #test + ' PRECEDING AND CURRENT ROW)
FROM #temp'
EXEC (#sqlCommand)
You can try something like this which does not use dynamic SQL.
DECLARE #myTable TABLE (myValue INT)
INSERT INTO #myTable ( myValue ) VALUES ( 5)
INSERT INTO #myTable ( myValue ) VALUES ( 6)
INSERT INTO #myTable ( myValue ) VALUES ( 7)
INSERT INTO #myTable ( myValue ) VALUES ( 8)
INSERT INTO #myTable ( myValue ) VALUES ( 9)
INSERT INTO #myTable ( myValue ) VALUES ( 10)
DECLARE #prev_records INT = 2
;WITH CTE as
(
SELECT ROW_NUMBER() OVER(ORDER BY myValue) rn,myValue FROM #myTable
)
SELECT (SELECT SUM(myValue) FROM CTE t2 WHERE t2.rn BETWEEN (t1.rn - #prev_records) AND t1.rn )
FROM CTE t1
SUM(myValue) OVER() is best option however it does not allow you to pass previous N rows using a variable.
If possible dispersion of the range variable is not very high, you can use simple CASE statement to switch between calculations
DECLARE #myTable TABLE (myValue INT)
-- let's say it could be between 1 and 10
DECLARE #range int = 3;
INSERT INTO #myTable ( myValue ) VALUES ( 5)
INSERT INTO #myTable ( myValue ) VALUES ( 6)
INSERT INTO #myTable ( myValue ) VALUES ( 7)
INSERT INTO #myTable ( myValue ) VALUES ( 8)
INSERT INTO #myTable ( myValue ) VALUES ( 9)
INSERT INTO #myTable ( myValue ) VALUES ( 10)
SELECT
CASE #range
WHEN 1 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
WHEN 2 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
WHEN 3 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
WHEN 4 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 4 PRECEDING AND CURRENT ROW)
WHEN 5 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
WHEN 6 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
WHEN 7 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 7 PRECEDING AND CURRENT ROW)
WHEN 8 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 8 PRECEDING AND CURRENT ROW)
WHEN 9 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 9 PRECEDING AND CURRENT ROW)
WHEN 10 THEN SUM(myValue) OVER (ORDER BY myValue ROWS BETWEEN 10 PRECEDING AND CURRENT ROW)
END
FROM #myTable
I know there are several examples of recursion with CTE and so on, but how can this be accomplished just by using window functions in SQL Server 2012:
CREATE TABLE #temp
(
ID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
Percentage INT NOT NULL
)
DECLARE #Calculated MONEY = 1000
INSERT INTO #temp ( Percentage ) VALUES ( 100 )
INSERT INTO #temp ( Percentage ) VALUES ( 90)
INSERT INTO #temp ( Percentage ) VALUES ( 60)
INSERT INTO #temp ( Percentage ) VALUES ( 50)
INSERT INTO #temp ( Percentage ) VALUES ( 100)
And the result would be a running percentage like so (we are starting with $1000)
id percentage calculated
-- -------- ---------
1 100 1000
2 50 500
3 90 450
4 80 360
5 100 360
So the value for the next row is the percentage multiplied by the calculated value above that row. Can LAG be used on a computed alias?
Thanks,
You need a running product of the percentages instead of always comparing 2 consecutive rows, which is why LEAD and LAG won't work here.
You can use a windowed sum to keep a running product of the percentages against your variable to get your desired calculation:
SELECT
ID,
Expected,
EXP(SUM(LOG(CONVERT(FLOAT, Percentage) / 100)) OVER (ORDER BY ID)) * #Calculated AS Actual
FROM #Temp
Adding this to your sample code (with a column I added for your expected output):
CREATE TABLE #temp
(
ID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
Percentage INT NOT NULL,
Expected MONEY NOT NULL
)
DECLARE #Calculated MONEY = 1000
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 100 , 1000)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 50, 500)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 90, 450)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 80, 360)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 100, 360)
SELECT
ID,
Expected,
EXP(SUM(LOG(CONVERT(FLOAT, Percentage) / 100)) OVER (ORDER BY ID)) * #Calculated AS Actual
FROM #Temp
This will yield your expected output:
ID Expected Actual
----------- --------------------- ----------------------
1 1000.00 1000
2 500.00 500
3 450.00 450
4 360.00 360
5 360.00 360
you can use recursive cte to get the desired result
with cte
as
(
select id, percentage, 1000 as calculated
from #temp
where id =1
union all
select t.id, t.percentage, t.percentage*cte.calculated/100 as calculated
from #temp t
join cte
on t.id = cte.id+1
)
select * from cte
I'm afraid, widow functions won't help here (at least they won't make it simple). The easiest way to achieve your goal is update statement with double assignment:
alter table #temp add VAL decimal
declare #val decimal = 1000
update t set
#val = VAL = #val * Percentage / 100
from (select top 100 percent * from #temp order by id) as t
select * from #temp
I have a table with 2 columns of integers. The first column represents start index and the second column represents end index.
START END
1 8
9 13
14 20
20 25
30 42
42 49
60 67
Simple So far. What I would like to do is group all the records that follow together:
START END
1 25
30 49
60 67
A record can follow by Starting on the same index as the previous end index or by a margin of 1:
START END
1 10
10 20
And
START END
1 10
11 20
will both result in
START END
1 20
I'm using SQL Server 2008 R2.
Any help would be Great
This works for your example, let me know if it doesn't work for other data
create table #Range
(
[Start] INT,
[End] INT
)
insert into #Range ([Start], [End]) Values (1, 8)
insert into #Range ([Start], [End]) Values (9, 13)
insert into #Range ([Start], [End]) Values (14, 20)
insert into #Range ([Start], [End]) Values (20, 25)
insert into #Range ([Start], [End]) Values (30, 42)
insert into #Range ([Start], [End]) Values (42, 49)
insert into #Range ([Start], [End]) Values (60, 67)
;with RangeTable as
(select
t1.[Start],
t1.[End],
row_number() over (order by t1.[Start]) as [Index]
from
#Range t1
where t1.Start not in (select
[End]
from
#Range
Union
select
[End] + 1
from
#Range
)
)
select
t1.[Start],
case
when t2.[Start] is null then
(select max([End])
from #Range)
else
(select max([End])
from #Range
where t2.[Start] > [End])
end as [End]
from
RangeTable t1
left join
RangeTable t2
on
t1.[Index] = t2.[Index]-1
drop table #Range;
Edited to include another version which i think is a bit more reliable, and also works with overlapping ranges
CREATE TABLE #data (start_range INT, end_range INT)
INSERT INTO #data VALUES (1,8)
INSERT INTO #data VALUES (2,15)
INSERT INTO #data VALUES (9,13)
INSERT INTO #data VALUES (14,20)
INSERT INTO #data VALUES (13,26)
INSERT INTO #data VALUES (12,21)
INSERT INTO #data VALUES (9,25)
INSERT INTO #data VALUES (20,25)
INSERT INTO #data VALUES (30,42)
INSERT INTO #data VALUES (42,49)
INSERT INTO #data VALUES (60,67)
;with ranges as
(
SELECT start_range as level
,end_range as end_range
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range) as row
FROM #data
UNION ALL
SELECT
level + 1 as level
,end_range as end_range
,row
From ranges
WHERE level < end_range
)
,ranges2 AS
(
SELECT DISTINCT
level
FROM ranges
)
,ranges3 AS
(
SELECT
level
,row_number() OVER (ORDER BY level) - level as grouping_group
from ranges2
)
SELECT
MIN(level) as start_number
,MAX(level) as end_number
FROM ranges3
GROUP BY grouping_group
ORDER BY start_number ASC
I think this should work - might not be especially efficient on larger sets though...
CREATE TABLE #data (start_range INT, end_range INT)
INSERT INTO #data VALUES (1,8)
INSERT INTO #data VALUES (2,15)
INSERT INTO #data VALUES (9,13)
INSERT INTO #data VALUES (14,20)
INSERT INTO #data VALUES (21,25)
INSERT INTO #data VALUES (30,42)
INSERT INTO #data VALUES (42,49)
INSERT INTO #data VALUES (60,67)
;with overlaps as
(
select *
,end_range - start_range as range
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range ASC) as line_number
from #data
)
,overlaps2 AS
(
SELECT
O1.start_range
,O1.end_range
,O1.line_number
,O1.range
,O2.start_range as next_range
,CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END as overlap
,O1.line_number - DENSE_RANK() OVER (PARTITION BY (CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END) ORDER BY O1.line_number ASC) as overlap_group
FROM overlaps O1
LEFT OUTER JOIN overlaps O2 on O2.line_number = O1.line_number + 1
)
SELECT
MIN(start_range) as range_start
,MAX(end_range) as range_end
,MAX(end_range) - MIN(start_range) as range_span
FROM overlaps2
GROUP BY overlap_group
You could use a number table to solve this problem. Basically, you first expand the ranges, then combine subsequent items in groups.
Here's one implementation:
WITH data (START, [END]) AS (
SELECT 1, 8 UNION ALL
SELECT 9, 13 UNION ALL
SELECT 14, 20 UNION ALL
SELECT 20, 25 UNION ALL
SELECT 30, 42 UNION ALL
SELECT 42, 49 UNION ALL
SELECT 60, 67
),
expanded AS (
SELECT DISTINCT
N = d.START + v.number
FROM data d
INNER JOIN master..spt_values v ON v.number BETWEEN 0 AND d.[END] - d.START
WHERE v.type = 'P'
),
marked AS (
SELECT
N,
SeqID = N - ROW_NUMBER() OVER (ORDER BY N)
FROM expanded
)
SELECT
START = MIN(N),
[END] = MAX(N)
FROM marked
GROUP BY SeqID
This solution uses master..spt_values as a number table, for expanding the initial ranges. But if (all or some of) those ranges may span more than 2048 (subsequent) values, then you should define and use your own number table.