PostgreSQL: using where clause to change select value - postgresql

I'm wanting to change a select value depending on the where clause. Is something like the following possible in any way?
SELECT b
FROM table
WHERE
IF date = '2016-03-24'
b = 1
ELSE IF date > date '2016-03-24' - 7 AND date < '2016-03-24'
b = 2
ELSE
b = 1
ORDER BY date
LIMIT 1

Use CASE WHEN:
SELECT CASE WHEN date = '2016-03-24' THEN 1
WHEN date > '2016-03-17' AND date < '2016-03-24' THEN 2
ELSE 1
END AS b
FROM table
ORDER BY b
LIMIT 1

Related

Prior six months of data per unique user from an imputed date - PostgreSQL

I am looking to obtain the last six (6) months of data for unique users in a dataset based on another date (represented by a flag, and will be called the index date).
The "index date" is essentially a flag captured in the data. 1 being the flag is present, 0 meaning it is not.
So if User 1 had a flag (index date) presented on July 1 2020 - I would want their history 6 months prior to July 1 2020 (so all the way to January 1 2020) and whatever is present in their data during that timeframe.
EXAMPLE DATA TABLE:
User
Date
Flag (Index Date)
User 1
10-01-2019
0
User 1
12-15-2019
0
User 1
12-21-20219
0
User 1
03-01-2020
0
User 1
05-15-2020
0
User 1
07-01-2020
1
User 1
07-15-2020
0
User 1
08-01-2020
0
EXAMPLE OUTPUT:
User
Date
Flag (Index Date)
User 1
03-01-2020
NULL
User 1
05-15-2020
NULL
User 1
07-01-2020
1
User 2
02-24-2020
NULL
User 2
03-12-2020
NULL
User 2
04-28-2020
1
You can try this query:
with cte as
(select user_,date_ from test where flag=1
)
select
t.*,
case when t.flag=1 then t.date_ end "Flag (Index Date)"
from test t
inner join cte on t.user_=cte.user_
where t.date_ between cte.date_ - interval '6 month' and cte.date_
order by user_,date_
DEMO
you can do it without cte as well:
select *
from test t1
where exists (
select 1 from test t2
where t2.flag = 1
and t1.user = t2.user
and t1.date between t2.date - interval '6 month' and t2.date
)
order by user, date
it's assumed you only have only one flag = 1 per user in your data

T-SQL: Aggregate based on condition

there is a table:
start | end | zone
------|-----|-----
1 | 5 | 3
3 | 6 | 2
1 | 3 | 1
4 | 7 | 4
Start and end represent a range. I'd like to get a zone for value of 4, like
declare #value as int = 4
select zone from table where #value >= start and #value <= end
, and I obtain
3
2
4
, but I need only one zone from predefined range, for example 2 or 3 and 3 has priority, so if 2 and 3 are present, I'd like to get 3. So, zone 3 is desired result in my example.
How can I solve it?
If it's hard coded you can do something like this:
DECLARE #Value int = 4;
SELECT TOP 1 zone
FROM table
WHERE zone IN (3,2)
AND #Value >= start
AND #Value <= [end]
ORDER BY CASE WHEN zone = 3 THEN 0 ELSE 1 END
See a live demo on rextester
Update
SELECT TOP 1 CASE WHEN zone IN (3,2) THEN zone ELSE NULL END AS zone
FROM #T
WHERE #Value >= start
AND #Value <= [end]
ORDER BY CASE WHEN zone = 3 THEN 0 ELSE 1 END
Live demo for updated version
Update 2
Even though you wrote in your comment that you found a solution thanks to my post, I still want to post a solution here for future readers that might need it:
;WITH CTE AS
(
SELECT TOP 1 zone
FROM #T
WHERE zone IN (3,2)
AND #Value >= start
AND #Value <= [end]
ORDER BY CASE WHEN zone = 3 THEN 0 ELSE 1 END
)
SELECT zone
FROM CTE
UNION ALL
SELECT NULL
WHERE NOT EXISTS(
SELECT 1
FROM CTE
)

PostgreSQL - time series interpolation

I have a table "performances" with columns "date" and "count".
However, the rows are sparse i.e. there are many days for which there is no row, which implicitly means that the count = 0.
Is there a query I can do that when run on this:
date count
2016-7-15 3
2016-7-12 1
2016-7-11 2
Would give me this:
date count
2016-7-15 3
2016-7-14 0
2016-7-13 0
2016-7-12 1
2016-7-11 2
?
You can use generate_series() and a left join:
with q as (<your query here>)
select s.dte, coalesce(q.count, 0) as count
from (select generate_series(min(q.date), max(q.date), interval '1 day') as dte
from q
) s left join
q
on s.dte = q.date;

Column of counts for time intervals

I want to get a table that constructs a column that tracks how many times an id appears in a given week. If the id appears once it is given a 1, if it appears twice it is given a 2, but if it appears more than two times it is given a 0.
id date
a 2015-11-10
a 2015-11-25
a 2015-11-09
b 2015-11-10
b 2015-11-09
a 2015-11-05
b 2015-11-23
b 2015-11-28
b 2015-12-04
a 2015-11-10
b 2015-12-04
a 2015-12-07
a 2015-12-09
c 2015-11-30
a 2015-12-06
c 2015-10-31
c 2015-11-04
b 2015-12-01
a 2015-10-30
a 2015-12-14
the one week intervals are given as follows
1 - 2015-10-30 to 2015-11-05
2 - 2015-11-06 to 2015-11-12
3 - 2015-11-13 to 2015-11-19
4 - 2015-11-20 to 2015-11-26
5 - 2015-11-27 to 2015-12-03
6 - 2015-12-04 to 2015-12-10
7 - 2015-12-11 to 2015-12-17
The table should look like this.
id interval count
a 1 2
b 1 0
c 1 2
a 2 0
b 2 2
c 2 0
a 3 0
b 3 0
c 3 0
a 4 1
b 4 1
c 4 0
a 5 0
b 5 2
c 5 1
a 6 0
b 6 2
c 6 0
a 7 1
b 7 0
c 7 0
The interval column doesn't have to be there, I simply added it for clarity.
I am new to sql and am unsure how to break the dates into intervals. The only thing I have is grouping by date and counting.
Select id ,date, count (*) as frequency
from data_1
group by id, date having frequency <= 2;
Looking at just the data you provided, this does the trick:
SELECT v.id,
i.interval,
coalesce((CASE WHEN sub.cnt < 3 THEN sub.cnt ELSE 0 END), 0) AS count
FROM (VALUES('a'), ('b'), ('c')) v(id)
CROSS JOIN generate_series(1, 7) i(interval)
LEFT JOIN (
SELECT id, ((date - '2015-10-30')/7 + 1)::int AS interval, count(*) AS cnt
FROM my_table
GROUP BY 1, 2) sub USING (id, interval)
ORDER BY 2, 1;
A few words of explanation:
You have three id values which are here recreated with a VALUES clause. If you have many more or don't know beforehand which id's to enumerate, you can always replace the VALUES clause with a sub-query.
You provide a specific date range over 7 weeks. Since you might have weeks where a certain id is not present you need to generate a series of the interval values and CROSS JOIN that to the id values above. This yields the 21 rows you are looking for.
Then you calculate the occurrences of ids in intervals. You can subtract a date from another date which will give you the number of days in between. So subtract the date of the row from the earliest date, divide that by 7 to get the interval period, add 1 to make the interval 1-based and convert to integer. You can then convert counts of > 2 to 0 and NULL to 0 with a combination of CASE and coalesce().
The query outputs the interval too, otherwise you will have no clue what the data refers to. Optionally, you can turn this into a column which shows the date range of the interval.
More flexible solution
If you have more ids and a larger date range, you can use the below version which first determines the distinct ids and the date range. Note that the interval is now 0-based to make calculations easier. Not that it matters much because instead of the interval number, the corresponding date range is displayed.
WITH mi AS (
SELECT min(date) AS min, ((max(date) - min(date))/7)::int AS intv FROM my_table)
SELECT v.id,
to_char((mi.min + i.intv * 7)::timestamp, 'YYYY-mm-dd') || ' - ' ||
to_char((mi.min + i.intv * 7 + 6)::timestamp, 'YYYY-mm-dd') AS period,
coalesce((CASE WHEN sub.cnt < 3 THEN sub.cnt ELSE 0 END), 0) AS count
FROM mi,
(SELECT DISTINCT id FROM my_table) v
CROSS JOIN LATERAL generate_series(0, mi.intv) i(intv)
LEFT JOIN LATERAL (
SELECT id, ((date - mi.min)/7)::int AS intv, count(*) AS cnt
FROM my_table
GROUP BY 1, 2) sub USING (id, intv)
ORDER BY 2, 1;
SQLFiddle with both solutions.
Assuming you have a table of all users, this will do the trick.
select
users.id,
interval_table.id,
CASE
WHEN count(log_table.user_id)>2 THEN 0
ELSE count(log_table.user_id)
END
from users
cross join interval_table
left outer join log_table
on users.id = log_table.user_id
and log_table.event_date >= interval_table.start_interval
and log_table.event_date < interval_table.stop_interval
group by users.id, interval_table.id
order by interval_table.id, users.id
Check it out: http://sqlfiddle.com/#!15/1a822/21

T SQL use case when in parameter

I'm trying to set the variable value with a case statement to determine the financial year, depending on the month but I get a Null value returned for the financial year:
declare
#Costcentre varchar(50)
,#dt date
,#dty int
,#dtm int
select #Costcentre = 'CAM'
SELECT #dt = '2012-09-30'
select #dtm = DATEPART(month,#dt)
select
#dty = case when #dtm between 4 and 12 then DATEPART(year,#dt) + 1 end
,#dty = case when #dtm between 1 and 3 then DATEPART(year,#dt) end
select #dty
You only need to assign #dty once:
select #dty = case
when #dtm between 4 and 12 then DATEPART(year,#dt) + 1
when #dtm between 1 and 3 then DATEPART(year,#dt)
end
Otherwise, you're just overwriting #dty if #dtm isn't between 1 and 3.