Postgresql query to get count per months within one year - sql

In MySQL
SELECT y, m, Count(users.created_date)
FROM (
SELECT y, m
FROM
(SELECT YEAR(CURDATE()) y UNION ALL SELECT YEAR(CURDATE())-1) years,
(SELECT 1 m UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months) ym
LEFT JOIN users
ON ym.y = YEAR(FROM_UNIXTIME(users.created_date))
AND ym.m = MONTH(FROM_UNIXTIME(users.created_date))
WHERE
(y=YEAR(CURDATE()) AND m<=MONTH(CURDATE()))
OR
(y<YEAR(CURDATE()) AND m>MONTH(CURDATE()))
GROUP BY y, m;
Mysql Output
+------+----+----------------------+
| y | m | Count(users.created) |
+------+----+----------------------+
| 2012 | 5 | 5595 |
| 2012 | 6 | 4431 |
| 2012 | 7 | 3299 |
| 2012 | 8 | 429 |
| 2012 | 9 | 0 |
| 2012 | 10 | 3698 |
| 2012 | 11 | 6208 |
| 2012 | 12 | 5142 |
| 2013 | 1 | 1196 |
| 2013 | 2 | 10 |
| 2013 | 3 | 0 |
| 2013 | 4 | 0 |
+------+----+----------------------+
** IN POSTGRESQL QUERY**
SELECT to_char(created_date, 'MM'),
count(id)
FROM users
WHERE created_date >
date_trunc('month', CURRENT_DATE) - INTERVAL '1 year'
GROUP BY 1
POSTGRESQL OUTPUT
postgresql output
May I know how to get postgresql output as like mysql output. Need to get results for current month within one year data.

I'm assuming what you're looking for is the rows where count is 0. If that is the case you can use generate_series and a left join on your table with data:
SELECT to_char(i, 'YY'), to_char(i, 'MM'),
count(id)
FROM generate_series(now() - INTERVAL '1 year', now(), '1 month') as i
left join users on (to_char(i, 'YY') = to_char(created_date, 'YY')
and to_char(i, 'MM') = to_char(created_date, 'MM') and login_type = 1)
GROUP BY 1,2;

Related

Create column with timeframe relative to other column in SQL

Suppose I have the following table t_1 where every row represents a day:
+------+------------+-------+
| week | date | val |
+------+------------+-------+
| 1 | 2022-02-07 | 1 | <- Monday
| 1 | 2022-02-08 | 2 |
| 1 | 2022-02-09 | 3 |
| 1 | 2022-02-10 | 4 | <- Thursday
| 1 | 2022-02-11 | 5 |
| 1 | 2022-02-12 | 6 |
| 1 | 2022-02-13 | 7 |
| 2 | 2022-02-14 | 8 | <- Monday
| 2 | 2022-02-15 | 9 |
| 2 | 2022-02-16 | 10 |
| 2 | 2022-02-17 | 11 | <- Thursday
| 2 | 2022-02-18 | 12 |
| 2 | 2022-02-19 | 13 |
| 2 | 2022-02-20 | 14 |
+------+------------+-------+
How can I create the following table t2 from t1?
+------------+------------+-----------+------------+
| date_start | date_end | val_cur. | val_prev |
+------------+------------+-----------+------------+
| 2022-01-14 | 2022-01-17 | 38 | 10 |
+------------+------------+-----------+------------+
Here val_cur is defined as the sum of values of the current timeframe (i.e. the sum of values between date_start and date_end) and val_prev is defined as the sum of values of the previous timeframe (i.e. the current timeframe minus one week).
-- Bigquery Standard SQL
WITH t_1 AS
(SELECT 1 AS week, '2022-02-07' AS date, 1 AS val UNION ALL
SELECT 1, '2022-02-08', 2 UNION ALL
SELECT 1, '2022-02-09', 3 UNION ALL
SELECT 1, '2022-02-10', 4 UNION ALL
SELECT 1, '2022-02-11', 5 UNION ALL
SELECT 1, '2022-02-12', 6 UNION ALL
SELECT 1, '2022-02-13', 7 UNION ALL
SELECT 2, '2022-02-14', 8 UNION ALL
SELECT 2, '2022-02-15', 9 UNION ALL
SELECT 2, '2022-02-16', 10 UNION ALL
SELECT 2, '2022-02-17', 11 UNION ALL
SELECT 2, '2022-02-18', 12 UNION ALL
SELECT 2, '2022-02-19', 13 UNION ALL
SELECT 2, '2022-02-20', 14)
SELECT '2022-02-14' AS date_start, '2022-02-17' AS date_stop, sum(val) AS val_cur
FROM t_1
WHERE date >= '2022-02-14' AND date <= '2022-02-17'
Output:
+-----+------------+------------+---------+
| Row | date_start | date_stop | val_cur |
+-----+------------+------------+---------+
| 1 | 2022-02-14 | 2022-02-17 | 38 |
+-----+------------+------------+---------+
But how do I get the last column?
Consider below approach
with your_table as (
select 1 as week, date '2022-02-07' as date, 1 as val union all
select 1, '2022-02-08', 2 union all
select 1, '2022-02-09', 3 union all
select 1, '2022-02-10', 4 union all
select 1, '2022-02-11', 5 union all
select 1, '2022-02-12', 6 union all
select 1, '2022-02-13', 7 union all
select 2, '2022-02-14', 8 union all
select 2, '2022-02-15', 9 union all
select 2, '2022-02-16', 10 union all
select 2, '2022-02-17', 11 union all
select 2, '2022-02-18', 12 union all
select 2, '2022-02-19', 13 union all
select 2, '2022-02-20', 14
), timeframe as (
select date '2022-02-14' as date_start, date '2022-02-17' as date_stop
)
select date_start, date_stop,
sum(if(date between date_start and date_stop,val, 0)) as val_cur,
sum(if(date between date_start - 7 and date_stop - 7,val, 0)) as val_prev
from your_table, timeframe
group by date_start, date_stop
with output

How to group by month and year?

I have a table PURCHASE with a date column PURCHASEDATE which is in DATE format. I'm trying to get the purchases grouped by month and year. To do so, I try with:
SELECT
TO_CHAR(PURCHASEDATE, 'MM YYYY') AS MONTHYEAR
FROM PURCHASE
GROUP BY TO_CHAR(PURCHASEDATE, 'MM YYYY');
I have also tryied with GROUP BY EXTRACT(MONTH FROM PURCHASEDATE), EXTRACT(YEAR FROM PURCHASEDATE) but neither worked.
I'm rusty with SQL querys :S.
EDIT
Table cloumns
Thanks ahead!
If you have the sample data:
CREATE TABLE purchase ( purchasedate ) AS
SELECT DATE '2019-01-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 5 UNION ALL
SELECT DATE '2019-02-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 3 UNION ALL
SELECT DATE '2020-01-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 3 UNION ALL
SELECT DATE '2020-02-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 2 UNION ALL
SELECT DATE '2020-03-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 4 UNION ALL
SELECT DATE '2020-04-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 1;
Then, you can use your query:
SELECT TO_CHAR(PURCHASEDATE, 'MM YYYY') AS monthyear,
COUNT(*) AS frequency
FROM PURCHASE
GROUP BY
TO_CHAR(PURCHASEDATE, 'MM YYYY');
Which outputs:
MONTHYEAR | FREQUENCY
:-------- | --------:
03 2020 | 4
01 2019 | 5
01 2020 | 3
02 2020 | 2
02 2019 | 3
04 2020 | 1
Or, you can use TRUNC:
SELECT TRUNC(PURCHASEDATE,'MM') AS monthyear,
COUNT(*) AS frequency
FROM PURCHASE
GROUP BY
TRUNC(PURCHASEDATE,'MM');
Which outputs:
MONTHYEAR | FREQUENCY
:------------------ | --------:
2020-03-01 00:00:00 | 4
2020-04-01 00:00:00 | 1
2020-02-01 00:00:00 | 2
2020-01-01 00:00:00 | 3
2019-01-01 00:00:00 | 5
2019-02-01 00:00:00 | 3
Or, you can use EXTRACT:
SELECT EXTRACT( YEAR FROM PURCHASEDATE) AS year,
EXTRACT( MONTH FROM PURCHASEDATE) AS month,
COUNT(*) AS frequency
FROM PURCHASE
GROUP BY
EXTRACT( YEAR FROM PURCHASEDATE),
EXTRACT( MONTH FROM PURCHASEDATE);
Which outputs:
YEAR | MONTH | FREQUENCY
---: | ----: | --------:
2019 | 1 | 5
2020 | 1 | 3
2020 | 2 | 2
2020 | 4 | 1
2019 | 2 | 3
2020 | 3 | 4
db<>fiddle here
Is this what you want?
SELECT
TO_CHAR(PURCHASEDATE, 'MM YYYY') AS MONTHYEAR,
SUM(TOTAL) AS TOTAL
FROM PURCHASE
GROUP BY
TO_CHAR(PURCHASEDATE, 'MM YYYY');

Hierarchical query for mulitiple groups

I'd like to generate every month end date between two dates but I need to do that for multiple groups. Simplified example is here:
select last_day(add_months(date '2015-01-01', level - 1)), 1 gr from dual
connect by level <= 12
union all
select last_day(add_months(date '2015-01-01', level - 1)), 2 gr from dual
connect by level <= 12;
Can it be done in single SQL query without union all as I have many groups.
I know I can do it with PL/SQL but just out of curiosity it is possible to do with single SQL statement?
I'd like query like this one:
with d as (
select date '2015-01-01' start_date, date '2015-12-01' end_date, 1 gr from dual
union all
select date '2015-01-01' start_date, date '2015-12-01' end_date, 2 gr from dual
)
select last_day(add_months(start_date, level - 1)) from d
start with start_date = date '2015-01-01'
connect by level <= months_between(end_date, start_date);
but generating results as first query does not cross join
You can use the PRIOR and SYS_GUID() option
WITH d
AS (SELECT DATE '2015-01-01' start_date,
DATE '2015-12-01' end_date,
1 gr
FROM dual
UNION ALL
SELECT DATE '2015-01-01' start_date,
DATE '2015-12-01' end_date,
2 gr
FROM dual)
SELECT gr,
last_day(add_months(start_date, LEVEL - 1)) AS dt
FROM d
START WITH start_date = DATE '2015-01-01'
CONNECT BY LEVEL <= months_between(end_date, start_date)
AND PRIOR gr = gr
AND PRIOR sys_guid() IS NOT NULL
ORDER BY gr,
dt
| GR | DT |
|----|----------------------|
| 1 | 2015-01-31T00:00:00Z |
| 1 | 2015-02-28T00:00:00Z |
| 1 | 2015-03-31T00:00:00Z |
| 1 | 2015-04-30T00:00:00Z |
| 1 | 2015-05-31T00:00:00Z |
| 1 | 2015-06-30T00:00:00Z |
| 1 | 2015-07-31T00:00:00Z |
| 1 | 2015-08-31T00:00:00Z |
| 1 | 2015-09-30T00:00:00Z |
| 1 | 2015-10-31T00:00:00Z |
| 1 | 2015-11-30T00:00:00Z |
| 2 | 2015-01-31T00:00:00Z |
| 2 | 2015-02-28T00:00:00Z |
| 2 | 2015-03-31T00:00:00Z |
| 2 | 2015-04-30T00:00:00Z |
| 2 | 2015-05-31T00:00:00Z |
| 2 | 2015-06-30T00:00:00Z |
| 2 | 2015-07-31T00:00:00Z |
| 2 | 2015-08-31T00:00:00Z |
| 2 | 2015-09-30T00:00:00Z |
| 2 | 2015-10-31T00:00:00Z |
| 2 | 2015-11-30T00:00:00Z |
Demo
I've found one more way but lateral is also not what I was aiming for. Kaushik's answer is what I was looking for.
with data as (
select date '2015-01-01' start_date, date '2015-12-01' end_date, 1 as gr from dual
union all
select date '2015-01-01' start_date, date '2015-12-01' end_date, 2 as gr from dual
),
data_level as(
select start_Date
,end_date
,gr
,months_between(end_date,start_date) + 1 as lvl
from data)
select g from data_level,
lateral(select last_day(add_months(start_date,level - 1)) g
from dual
connect by level <= lvl
);

postgresql - cumul. sum active customers by month (removing churn)

I want to create a query to get the cumulative sum by month of our active customers. The tricky thing here is that (unfortunately) some customers churn and so I need to remove them from the cumulative sum on the month they leave us.
Here is a sample of my customers table :
customer_id | begin_date | end_date
-----------------------------------------
1 | 15/09/2017 |
2 | 15/09/2017 |
3 | 19/09/2017 |
4 | 23/09/2017 |
5 | 27/09/2017 |
6 | 28/09/2017 | 15/10/2017
7 | 29/09/2017 | 16/10/2017
8 | 04/10/2017 |
9 | 04/10/2017 |
10 | 05/10/2017 |
11 | 07/10/2017 |
12 | 09/10/2017 |
13 | 11/10/2017 |
14 | 12/10/2017 |
15 | 14/10/2017 |
Here is what I am looking to achieve :
month | active customers
-----------------------------------------
2017-09 | 7
2017-10 | 6
I've managed to achieve it with the following query ... However, I'd like to know if there are a better way.
select
"begin_date" as "date",
sum((new_customers.new_customers-COALESCE(churn_customers.churn_customers,0))) OVER (ORDER BY new_customers."begin_date") as active_customers
FROM (
select
date_trunc('month',begin_date)::date as "begin_date",
count(id) as new_customers
from customers
group by 1
) as new_customers
LEFT JOIN(
select
date_trunc('month',end_date)::date as "end_date",
count(id) as churn_customers
from customers
where
end_date is not null
group by 1
) as churn_customers on new_customers."begin_date" = churn_customers."end_date"
order by 1
;
You may use a CTE to compute the total end_dates and then subtract it from the counts of start dates by using a left join
SQL Fiddle
Query 1:
WITH edt
AS (
SELECT to_char(end_date, 'yyyy-mm') AS mon
,count(*) AS ct
FROM customers
WHERE end_date IS NOT NULL
GROUP BY to_char(end_date, 'yyyy-mm')
)
SELECT to_char(c.begin_date, 'yyyy-mm') as month
,COUNT(*) - MAX(COALESCE(ct, 0)) AS active_customers
FROM customers c
LEFT JOIN edt ON to_char(c.begin_date, 'yyyy-mm') = edt.mon
GROUP BY to_char(begin_date, 'yyyy-mm')
ORDER BY month;
Results:
| month | active_customers |
|---------|------------------|
| 2017-09 | 7 |
| 2017-10 | 6 |

All years and months between 2 dates SQL

I have a little question I have a table called project that looks like this:
---------------------------------------
ProjectId | StartDate | EndDate |
---------------------------------------
1 | 01/01/2015 | 31/12/2017|
Is it posible to get all months and years between those dates like this:
--------------------
| Month | Year |
--------------------
1 | 2015 |
2 | 2015 |
3 | 2015 |
4 | 2015 |
5 | 2015 |
6 | 2015 |
7 | 2015 |
8 | 2015 |
9 | 2015 |
10 | 2015 |
11 | 2015 |
12 | 2015 |
1 | 2016 |
2 | 2016 |
3 | 2016 |
4 | 2016 |
. | . |
. | . |
. | . |
12 | 2017 |
Here's a method using PostgreSQL functions generate_series and extract:
SELECT extract(month FROM date) AS month, extract(year FROM date) AS year
FROM (
SELECT generate_series('2015-01-01'::date, '2017-12-31'::date, '1 month'::interval) AS date
) AS date_range
https://www.postgresql.org/docs/current/static/functions-srf.html
https://www.postgresql.org/docs/current/static/functions-datetime.html
You'd need to modify this to use the dates from your table:
SELECT extract(month FROM range) AS month, extract(year FROM range) AS year
FROM (
SELECT generate_series(StartDate, EndDate, '1 month'::interval) AS range
FROM project
WHERE ProjectId = 1
) AS date_range
If your database is sql server, you can run the following code to get the result.
DECLARE #DateStart DATETIME = '2015-01-01'
DECLARE #DateEnd DATETIME = ' 2017-12-31';
WITH Dates AS
(
SELECT DATEADD(DAY, -(DAY(#DateStart) - 1), #DateStart) AS [Date]
UNION ALL
SELECT DATEADD(MONTH, 1, [Date])
FROM Dates
WHERE [Date] < DATEADD(DAY, -(DAY(#DateEnd) - 1), #DateEnd)
)
SELECT
MONTH([Date]) AS [Month],
YEAR([Date]) AS [Year]
FROM Dates;
Hope it will help. If you need more help, you can look at the following link
https://blog.sqlauthority.com/2014/12/22/sql-server-list-the-name-of-the-months-between-date-ranges-part-2/
In Sql server, query for your date type
;with datecte (date)
AS
(
SELECT Convert(date,'01/01/2015',105)
UNION ALL
SELECT DATEADD(month,1,date)
from datecte
where DATEADD(month,1,date)<= (Select Convert(date,'31/12/2017',105))
)
select month(date),YEAR(date) from datecte