how to create column based on other column in sql? - sql

id year
1 2017
1 2018
1 2019
2 2018
2 2019
3 2017
3 2019
8 2017
4 2018
4 2019
I need to create column based on id and year column:
if a id present in 2017 and 2018 (subsequent year) then mark 'P' against 2017.
if a id present in 2018 and 2019 then mark 'P' then mark 'P' against 2017.
if a id present in 2017 but not in subsequent year then mark 'N' against 2017
If there is no data of subsequent year then mark 'N' in the previous year (2019)
output :
id year mark
1 2017 P
1 2018 P
1 2019 N
2 2018 P
2 2019 N
3 2017 N
3 2019 N
8 2017 P
4 2018 P
4 2019 N

You can try Lead() function. but please check output for Id = 8. Ideally it should be 'N'
SELECT *
,CASE WHEN LEAD(Year) OVER (PARTITION BY ID ORDER BY YEAR) - YEAR = 1 THEN 'P' ELSE 'N' END
FROM #Table

Hmmm . . . I'm thinking to generate flags for each year and then apply the logic. Based on the rules you describe:
select t.*,
(case when year in (2017, 2018) and
flag_2017 > 0 and flag_2018 > 0
then 'P'
when year in (2017) and
flag_2018 > 0 and flag_2019 > 0
then 'P'
else 'N'
end) as mark
from (select t.*,
sum(case when year = 2017 then 1 else 0 end) over (partition by id) as flag_2017,
sum(case when year = 2018 then 1 else 0 end) over (partition by id) as flag_2018,
sum(case when year = 2019 then 1 else 0 end) over (partition by id) as flag_2019
from t
) t;
Your sample results don't seem to follow your rules, but some simple variation on this appears to be what you want.

You don't need a physical column. What you want can be expressed as a query, using exists() or lead()
the LEAD() version:
\i tmp.sql
CREATE TABLE years(id integer, zyear integer);
INSERT INTO years (id , zyear ) VALUES
(1, 2017) , (1, 2018) , (1, 2019)
, (2, 2018) , (2, 2019) , (3, 2017)
, (3, 2019) , (8, 2017)
, (4, 2018) , (4, 2019)
;
SELECT id, zyear
, CASE when yy.nxt=yy.zyear+1 THEN 'P' ELSE 'N' END AS flagged
FROM (
SELECT id, zyear
, lead(zyear) OVER (partition by id ORDER BY zyear) AS nxt
FROM years
) yy
;
or, the EXISTS()-version:
SELECT id, zyear
, CASE when yy.xx THEN 'P' ELSE 'N' END AS flagged
FROM (
SELECT id, zyear
, EXISTS ( select * FROM years x where x.id=y.id and x.zyear = y.zyear+1) AS xx
FROM years y
) yy
;
Result: (the same for both versions)
psql:tmp.sql:2: NOTICE: drop cascades to table tmp.years
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 10
id | zyear | flagged
----+-------+---------
1 | 2017 | P
1 | 2018 | P
1 | 2019 | N
2 | 2018 | P
2 | 2019 | N
3 | 2017 | N
3 | 2019 | N
4 | 2018 | P
4 | 2019 | N
8 | 2017 | N
(10 rows)

Related

I want cummulative row for a given input

I have table like below
Months cnt
Jan 2
Feb 3
Mar 5
I want output like below
Months cnt
Jan 2
Feb 2
Feb 3
Mar 2
Mar 3
Mar 5
I tried using below query but not getting the required output
Select distinct months, cnt, level
from (select months, cnt, rownum row_cnt
from tablename)
connect by level <= row_cnt
Order by months, cnt, level
Here's one option which converts month's names into their ordinal number (1 for Jan, 2 for Feb, etc.) and then - using self join - returns the result.
SQL> with test (months, cnt) as
2 (select 'jan', 2 from dual union all
3 select 'feb', 3 from dual union all
4 select 'mar', 5 from dual
5 ),
6 temp as
7 (select
8 months,
9 to_number(to_char(to_date(months, 'mon', 'nls_date_language=english'), 'mm')) mon,
10 cnt
11 from test
12 )
13 select a.months, b.cnt
14 from temp a join temp b on a.mon >= b.mon
15 order by a.mon, b.cnt;
MON CNT
--- ----------
jan 2
feb 2
feb 3
mar 2
mar 3
mar 5
6 rows selected.
SQL>
You need a self join:
select t.months, tt.cnt
from tablename t inner join tablename tt
on extract(month from to_date(t.Months,'MON')) >= extract(month from to_date(tt.Months,'MON'))
order by extract(month from to_date(t.Months,'MON')), tt.cnt
See the demo.
Results:
> MONTHS | CNT
> :----- | --:
> Jan | 2
> Feb | 2
> Feb | 3
> Mar | 2
> Mar | 3
> Mar | 5

How to upload data from previous year if this year's data is unavailable

TABLE : TEST
Batch Year Value
----------------------
A 2014 11
A 2015 0
A 2016 22
A 2017 0
A 2018 13
B 2015 10
B 2016 0
B 2017 29
B 2018 0
C 2013 24
C 2014 0
D 2015 27
D 2016 0
Herein batchwise suppose data is not there in 2015 it should take data from 2014, similarly if data is unavailable in a particular year for a particular batch then data from the previous year should be allocated to that year.
I tried to put case statement by taking only batch A but the problem is that my query is showing subquery return multiple rows.
SELECT BATCH, YEAR,
(CASE WHEN VALUE = 0 THEN
(SELECT A.VALUE FROM TEST A, TEST B WHERE A.YEAR = B.YEAR-1 AND A.VALUE <> '0') ELSE VALUE END)
FROM TEST;
Required Output table:
Test
Batch Year Value
---------------------
A 2014 11
A 2015 11
A 2016 22
A 2017 22
A 2018 13
B 2015 10
B 2016 10
B 2017 29
B 2018 29
C 2013 24
C 2014 24
D 2015 27
D 2016 27
I used below query to achieve your result,
CREATE TABLE TEST1(Batch VARCHAR(10), Year INT, Value INT)
INSERT INTO TEST1
VALUES('A', 2014,11),
('A', 2015,0 ),
('A', 2016,22),
('A', 2017,0 ),
('A', 2018,13),
('B', 2015,10),
('B', 2016,0 ),
('B', 2017,29),
('B', 2018,0 ),
('C', 2013,24),
('C', 2014,0 ),
('D', 2015,27),
('D', 2016,0 )
SELECT batch, year, CASE WHEN value = 0 then LAG(value) OVER(PARTITION BY batch ORDER BY year) ELSE value END AS value
FROM TEST1
ORDER BY batch, year

Postgresql - How to get value from last record of each month

I have a view like this:
Year | Month | Week | Category | Value |
2017 | 1 | 1 | A | 1
2017 | 1 | 1 | B | 2
2017 | 1 | 1 | C | 3
2017 | 1 | 2 | A | 4
2017 | 1 | 2 | B | 5
2017 | 1 | 2 | C | 6
2017 | 1 | 3 | A | 7
2017 | 1 | 3 | B | 8
2017 | 1 | 3 | C | 9
2017 | 1 | 4 | A | 10
2017 | 1 | 4 | B | 11
2017 | 1 | 4 | C | 12
2017 | 2 | 5 | A | 1
2017 | 2 | 5 | B | 2
2017 | 2 | 5 | C | 3
2017 | 2 | 6 | A | 4
2017 | 2 | 6 | B | 5
2017 | 2 | 6 | C | 6
2017 | 2 | 7 | A | 7
2017 | 2 | 7 | B | 8
2017 | 2 | 7 | C | 9
2017 | 2 | 8 | A | 10
2017 | 2 | 8 | B | 11
2017 | 2 | 8 | C | 12
And I need to make a new view which needs to show average of value column (let's call it avg_val) and the value from the max week of the month (max_val_of_month). Ex: max week of january is 4, so the value of category A is 10. Or something like this to be clear:
Year | Month | Category | avg_val | max_val_of_month
2017 | 1 | A | 5.5 | 10
2017 | 1 | B | 6.5 | 11
2017 | 1 | C | 7.5 | 12
2017 | 2 | A | 5.5 | 10
2017 | 2 | B | 6.5 | 11
2017 | 2 | C | 7.5 | 12
I have use window function, over partition by year, month, category to get the avg value. But how can I get the value of the max week of each month?
Assuming that you need a month average and a value for the max week not the max value per month
SELECT year, month, category, avg_val, value max_week_val
FROM (
SELECT *,
AVG(value) OVER (PARTITION BY year, month, category) avg_val,
ROW_NUMBER() OVER (PARTITION BY year, month, category ORDER BY week DESC) rn
FROM view1
) q
WHERE rn = 1
ORDER BY year, month, category
or more verbose version without window functions
SELECT q.year, q.month, q.category, q.avg_val, v.value max_week_val
FROM (
SELECT year, month, category, avg(value) avg_val, MAX(week) max_week
FROM view1
GROUP BY year, month, category
) q JOIN view1 v
ON q.year = v.year
AND q.month = v.month
AND q.category = v.category
AND q.max_week = v.week
ORDER BY year, month, category
Here is a dbfiddle demo for both queries
And here is my NEW version.
My thanks to #peterm for pointing me about the prior false value of val_from_max_week_of_month. So, I corrected it:
SELECT
a.Year,
a.Month,
a.Category,
max(a.Week) AS max_week,
AVG(a.Value) AS avg_val,
(
SELECT b.Value
FROM decades AS b
WHERE
b.Year = a.Year AND
b.Month = a.Month AND
b.Week = max(a.Week) AND
b.Category = a.Category
) AS val_from_max_week_of_month
FROM decades AS a
GROUP BY
a.Year,
a.Month,
a.Category
;
The new results:
First, you might need to check, how do you handle the first week in January. If 1st of January are not a Monday, there are several interpretations & not every one of them will fit the solutions here. You'll either need to use:
the ISO week concept, ie. the week column should hold the ISO week & the year column should hold the ISO year (week-year, rather). Note: in this concept, 1st of January actually sometimes belongs to the previous year
use your own concept, where the first week of the year is "split" into two if 1st of January is not a Monday.
Note: the solutions below will not work if (in your table) the first week of January can be 52 or 53.
Given that avg_val is just a simple aggregation, while max_val_of_month can be calculated with typical greatest-n-per-group queries. It has a lot of possible solutions in PostgreSQL, with varying performance. Fortunately, your query will naturally have an easily determined selectivity: you'll always need (approx.) a quarter of your data.
Usual winners (in performance) are:
(These are not surprise though, as these 2 should perform more and more as you need more portion of the original data.)
array_agg() with order by variant:
select year, month, category, avg(value) avg_val,
(array_agg(value order by week desc))[1] max_val_of_month
from table_name
group by year, month, category;
distinct on variant:
select distinct on (year, month, category) year, month, category,
avg(value) over (partition by year, month, category) avg_val,
value max_val_of_month
from table_name
order by year, month, category, week desc;
The pure window function variant is not that bad either:
row_number() variant:
select year, month, category, avg_val, max_val_of_month
from (select year, month, category, value max_val_of_month,
avg(value) over (partition by year, month, category) avg_val,
row_number() over (partition by year, month, category order by week desc) rn
from table_name) w
where rn = 1;
But the LATERAL variant is only viable with an index:
LATERAL variant:
create index idx_table_name_year_month_category_week_desc
on table_name(year, month, category, week desc);
select year, month, category,
avg(value) avg_val,
max_val_of_month
from table_name t
cross join lateral (select value max_val_of_month
from table_name
where (year, month, category) = (t.year, t.month, t.category)
order by week desc
limit 1) m
group by year, month, category, max_val_of_month;
But most of the solutions above can actually utilize this index, not just this last one.
Without the index: http://rextester.com/WNEL86809
With the index: http://rextester.com/TYUA52054
with data (yr, mnth, wk, cat, val) as
(
-- begin test data
select 2017 , 1 , 1 , 'A' , 1 from dual union all
select 2017 , 1 , 1 , 'B' , 2 from dual union all
select 2017 , 1 , 1 , 'C' , 3 from dual union all
select 2017 , 1 , 2 , 'A' , 4 from dual union all
select 2017 , 1 , 2 , 'B' , 5 from dual union all
select 2017 , 1 , 2 , 'C' , 6 from dual union all
select 2017 , 1 , 3 , 'A' , 7 from dual union all
select 2017 , 1 , 3 , 'B' , 8 from dual union all
select 2017 , 1 , 3 , 'C' , 9 from dual union all
select 2017 , 1 , 4 , 'A' , 10 from dual union all
select 2017 , 1 , 4 , 'B' , 11 from dual union all
select 2017 , 1 , 4 , 'C' , 12 from dual union all
select 2017 , 2 , 5 , 'A' , 1 from dual union all
select 2017 , 2 , 5 , 'B' , 2 from dual union all
select 2017 , 2 , 5 , 'C' , 3 from dual union all
select 2017 , 2 , 6 , 'A' , 4 from dual union all
select 2017 , 2 , 6 , 'B' , 5 from dual union all
select 2017 , 2 , 6 , 'C' , 6 from dual union all
select 2017 , 2 , 7 , 'A' , 7 from dual union all
select 2017 , 2 , 8 , 'A' , 10 from dual union all
select 2017 , 2 , 8 , 'B' , 11 from dual union all
select 2017 , 2 , 7 , 'B' , 8 from dual union all
select 2017 , 2 , 7 , 'C' , 9 from dual union all
select 2018 , 2 , 7 , 'C' , 9 from dual union all
select 2017 , 2 , 8 , 'C' , 12 from dual
-- end test data
)
select * from
(
select
-- data.*: all columns of the data table
data.*,
-- avrg: partition by a combination of year,month and category to work out -
-- the avg for each category in a month of a year
avg(val) over (partition by yr, mnth, cat) avrg,
-- mwk: partition by year and month to work out -
-- the max week of a month in a year
max(wk) over (partition by yr, mnth) mwk
from
data
)
-- as OP's interest is in the max week of each month of a year, -
-- "wk" column value is matched against
-- the derived column "mwk"
where wk = mwk
order by yr,mnth,cat;

How to get calculate percentage increase or decrease in single sql query?

I have a table with following structure
Event Id | Year
-------------------
1xxxxx | 2014
2xxxxx | 2014
3xxxxx | 2014
4xxxxx | 2014
5xxxxx | 2014
6xxxxx | 2015
7xxxxx | 2015
8xxxxx | 2015
I need to find the percentage increase of number of events happened in 2015 compared to 2014. I need to find it using a single SQL query. How can I achieve this?
For example if we take the number of events happened in 2014 it is equal to 5 and the same is 3 in 2015. So the percentage increase of events in 2015 compared to 2014 is ((3-5)*100)/5 = -40.0 %.
Here is generic statement which is not limited to only 2014 and 2015:
CREATE TABLE test (id INT, year int);
Insert into test values
(1, 2014),
(2, 2014),
(3, 2014),
(4, 2014),
(5, 2014),
(6, 2015),
(7, 2015),
(8, 2015),
(9, 2016)
;with cte as(
select year y, count(*) c from test
group by year)
select c1.y,
ca.y,
(c1.c - ca.c)*100.0/ca.c inc,
(ca.c - c1.c)*100.0/c1.c dec
from cte c1
cross apply(select top 1 * from cte c2 where c2.y < c1.y order by c2.y desc)ca
Output:
y y inc dec
2015 2014 -40 66.666666666666
2016 2015 -66.666666666666 200
Fiddle http://sqlfiddle.com/#!6/9e1cf/3
if I understand correctly, you can do this with conditional aggregation:
select sum(case when year = 2014 then 1 else 0 end) as ev_2014,
sum(case when year = 2015 then 1 else 0 end) as ev_2015,
(sum(case when year = 2015 then 100.0 else 0 end)
sum(case when year = 2014 then 1.0 end)
) - 100.0 as percent_change
from table t;

getting mutlitple row for a Oracle SQL query

I have a table as
userid cycleid ratings
1 13 5
1 14 6
1 15 7
I have to display data as
userid 2011 2012 2013
1 5 6 7
as you can see cycleid 13 is for year 2011, cycleid 14 for year 2012 and cycleid 15 for year 2013
MY QUERY
SELECT PER.USERID,
(SELECT max(PER1.RATING) FROM PERFRATINGS PER1 WHERE PER1.CYCLEID = 13) as 2011,
(SELECT max(PER2.RATING) FROM PERFRATINGS PER2 WHERE PER2.CYCLEID = 14) as 2012,
(SELECT max(PER3.RATING) FROM PERFRATINGS PER3 WHERE PER3.CYCLEID = 15) as 2013
FROM PERFRATINGS PER
Where PER.USERID = 1
gives multiple row (3 times)
userid 2011 2012 2013
1 5 6 7
1 5 6 7
1 5 6 7
I want everything in single row.
I suggest to use pivot
select * from
(select userid, cycleid, ratings from perfratings)
pivot
(max(ratings) for (cycleid) in (
12 as 2012,
13 as 2013,
14 as 2014
))
You could fix your query using group by or distinct. A more efficient version would use pivot (if available in your version of Oracle) or conditional aggregation:
SELECT PER.USERID,
max(case when PER1.CYCLEID = 13 then PER1.RATING end) as 2011,
max(case when PER1.CYCLEID = 14 then PER1.RATING end) as 2012,
max(case when PER1.CYCLEID = 15 then PER1.RATING end) as 2013
FROM PERFRATINGS PER
Where PER.USERID = 1
GROUP BY PER.USERID;
In a sense, the group by is redundant when you are looking only at one user. However, you can remove the where and see the results for all users.