Aggregate functions are not allowed in GROUP BY - sql

I want to generate a report based on a monthly count of records. In situations where there's none, I want to have 0 return instead of nothing. The issue is, the query below throws an error aggregate functions are not allowed in GROUP BY. Any help will be appreciated.
SELECT sc.name, d.months, COUNT(s.scan_type) AS scan_count FROM
(SELECT CAST('2018-02-21 12:45:44+00' AS TIMESTAMP) - date_trunc('month', CAST('2018-02-21 12:45:44+00' AS TIMESTAMP) - offs) AS months
FROM generate_series(CAST('2018-02-21 12:45:44+00' AS TIMESTAMP), CAST(now() AS TIMESTAMP), INTERVAL '1 month') AS offs ) d
LEFT OUTER JOIN scans s ON d.months = date_trunc('month', s.datetime)
INNER JOIN schools sc ON s.school_id = sc.id
GROUP BY sc.name, d.months, scan_count

Just use a NVL ?
For example
SELECT sc.name, d.months, NVL(COUNT(s.scan_type),COUNT(s.scan_type),0) AS scan_count FROM ...

If I understand correctly, use CROSS JOIN to combine all the dates and schools. Then bring in the original data and aggregate:
SELECT sc.name, d.months, COUNT(s.scan_type) AS scan_count
FROM generate_series(CAST('2018-02-21 12:45:44+00' AS TIMESTAMP),
CAST(now() AS TIMESTAMP),
INTERVAL '1 month'
) d CROSS JOIN
schools sc LEFT OUTER JOIN
scans s
ON d.months = date_trunc('month', s.datetime) AND
s.school_id = sc.id
GROUP BY sc.name, d.months;

Changing INNER JOIN to LEFT OUTER JOIN on schools fixed it. Also, I had to remove scan_count from the GROUP BY clause.
SELECT sc.name, d.months, COUNT(s.scan_type) AS scan_count FROM
(SELECT CAST('2018-02-21 12:45:44+00' AS TIMESTAMP) - date_trunc('month', CAST('2018-02-21 12:45:44+00' AS TIMESTAMP) - offs) AS months
FROM generate_series(CAST('2018-02-21 12:45:44+00' AS TIMESTAMP), CAST(now() AS TIMESTAMP), INTERVAL '1 month') AS offs ) d
LEFT OUTER JOIN scans s ON d.months = date_trunc('month', s.datetime)
LEFT OUTER JOIN schools sc ON s.school_id = sc.id
GROUP BY sc.name, d.months

Related

Show Fiscal Year Dynamically - SQL

I have created an Oracle SQL query that produces current year data:
SELECT A.ITEMNO, A.DESCRIP, SUM(RD.QTYRETURNED) AS "TOTAL QTY RETURNED"
FROM RMA R LEFT OUTER JOIN RMA_DETAIL RD ON R.ID=RD.RMA_ID
LEFT OUTER JOIN ARINVT A ON RD.ARINVT_ID=A.ID
LEFT OUTER JOIN ARCUSTO C ON C.ID = A.ARCUSTO_ID
WHERE ((R."CLOSED" IS NULL ) OR ((R.CLOSED='N' OR R.CLOSED='Y'))) AND
EXTRACT(YEAR FROM R.RMA_DATE) = EXTRACT(YEAR FROM SYSDATE)
GROUP BY A.ITEMNO, A.DESCRIP
ORDER BY "TOTAL QTY RETURNED" DESC
I am needed to create a query that returns our fiscal year (12/1 - 11/30) so I created this:
SELECT A.ITEMNO, A.DESCRIP, SUM(RD.QTYRETURNED) AS "TOTAL QTY RETURNED"
FROM RMA R LEFT OUTER JOIN RMA_DETAIL RD ON R.ID=RD.RMA_ID
LEFT OUTER JOIN ARINVT A ON RD.ARINVT_ID=A.ID
LEFT OUTER JOIN ARCUSTO C ON C.ID = A.ARCUSTO_ID
WHERE ((R."CLOSED" IS NULL ) OR ((R.CLOSED='N' OR R.CLOSED='Y'))) AND
R.RMA_DATE >= ADD_MONTHS(TRUNC(SYSDATE, 'MONTH'), -7) AND R.RMA_DATE <
ADD_MONTHS(TRUNC(SYSDATE, 'MONTH'), 5)
GROUP BY A.ITEMNO, A.DESCRIP
ORDER BY "TOTAL QTY RETURNED" DESC
These are queries that are used to create BI Dashboards. The issue with the fiscal year query is that when the sysdate month changes, then so will the data. What is the cleanest way to go about doing this? I'm just beginning with SQL, so any links to learning articles or explanations will go a long way. TIA
You can try a query like the one below. It calculates the "begin" and "end" dates based on what month you are currently in
SELECT A.ITEMNO, A.DESCRIP, SUM (RD.QTYRETURNED) AS "TOTAL QTY RETURNED"
FROM RMA R
LEFT OUTER JOIN RMA_DETAIL RD ON R.ID = RD.RMA_ID
LEFT OUTER JOIN ARINVT A ON RD.ARINVT_ID = A.ID
LEFT OUTER JOIN ARCUSTO C ON C.ID = A.ARCUSTO_ID
WHERE ((R."CLOSED" IS NULL) OR ((R.CLOSED = 'N' OR R.CLOSED = 'Y')))
AND R.RMA_DATE >=
TO_DATE (
CASE EXTRACT (MONTH FROM SYSDATE)
WHEN 12 THEN TO_CHAR (SYSDATE, 'YYYY')
ELSE TO_CHAR (ADD_MONTHS (SYSDATE, -12), 'YYYY')
END
|| '-12-01',
'YYYY-MM-DD')
AND R.RMA_DATE <
TO_DATE (
CASE EXTRACT (MONTH FROM SYSDATE)
WHEN 12 THEN TO_CHAR (ADD_MONTHS (SYSDATE, 12), 'YYYY')
ELSE TO_CHAR (SYSDATE, 'YYYY')
END
|| '-12-01',
'YYYY-MM-DD')
GROUP BY A.ITEMNO, A.DESCRIP
ORDER BY "TOTAL QTY RETURNED" DESC

How to apply filter using a joined table

I'm trying to apply a filter to my query (accounts.provider = 'z') using the accounts table. The query I have at the moment is not applying the filter correctly, the full list of payments is being added up, regardless of the provider condition. The reason why I'm using table x to join the accounts table is because table t doesn't have the account_id column to allow me to join it with the accounts table.
This is my current query
SELECT
distinct on (x.day) x.day,
coalesce(pending_payments,0)
from
(( SELECT day::date
FROM generate_series(timestamp '2017-03-13', current_date + interval '1 week', interval '1 day') day
) d
left JOIN (
SELECT date_trunc('day', payment_date)::date AS day,
sum(case when payment_amount > 0
and description not ilike '%credit%'
and state = 'pending'
then payment_amount end) as pending_payments
FROM payments
GROUP BY 1
) t USING (day) inner join payments on payments.payment_date = t.day) x
inner join accounts on accounts.id = x.account_id and accounts.provider = 'z'
where day <= current_date + interval '1 week'
and day >= current_date - interval'6 months'
ORDER BY x.day desc
Thanks for your help
Updated query based on suggestions in the comments but it's not producing the right outcome (see comments).
SELECT
distinct on (t.day) t.day as day,
coalesce(pending_payments,0)
from
( SELECT day::date
FROM generate_series(timestamp '2017-03-13', current_date + interval '1 week', interval '1 day') day
) d
left JOIN (
SELECT date_trunc('day', t.payment_date)::date AS day,
sum(case when t.payment_amount > 0
and t.description not ilike '%credit%'
and t.state = 'success'
then t.payment_amount end) as pending_payments
FROM payments t
inner join payments p on p.payment_date = date_trunc('day', t.payment_date)::date
inner join accounts on accounts.id = p.account_id and accounts.provider = 'z'
where date_trunc('day', t.payment_date)::date <= current_date + interval '1 week'
and date_trunc('day', t.payment_date)::date >= current_date - interval'1 months'
GROUP BY 1
) t USING (day)
ORDER BY day desc
You are calculating the pending_payments (In sub-query) before applying the accounts.provider = 'z' condition.
You should replace this code:
....
....
left JOIN (
SELECT date_trunc('day', payment_date)::date AS day,
sum(case when payment_amount > 0
and description not ilike '%credit%'
and state = 'pending'
then payment_amount end) as pending_payments
FROM payments
GROUP BY 1
) t USING (day) inner join payments on payments.payment_date = t.day) x
inner join accounts on accounts.id = x.account_id and accounts.provider = 'z'
....
....
with
....
....
left JOIN (
SELECT date_trunc('day', t.payment_date)::date AS day,
sum(case when t.payment_amount > 0
and t.description not ilike '%credit%'
and t.state = 'pending'
then t.payment_amount end) as pending_payments
FROM payments t
inner join payments p on p.payment_date = date_trunc('day', t.payment_date)::date
inner join accounts on accounts.id = p.account_id and accounts.provider = 'z'
GROUP BY 1
) t
....
....

Select all clients that have made a reservation each month in the previous year

I have to make an sql query that shows all the info of the clients that have made a reservation every single month in the last year, I thought about using a Group by Month and then count the number of groups.
Like this: (I didn't know how to count the number of groups)
Select *
From Cliente
inner join Persona
on Cliente.CEDULA = Persona.CEDULA
inner join Reserva
on Cliente.cedula = Reserva.CEDULA
Group BY DATEPART(MONTH, Reserva.FECHAINICIO);
Assuming that cedula identifies the client, you do not need all the joins. Then the key is the date arithmetic part and aggregation:
select r.cedula
from Reserva r
where r.fechainicio >= trunc(sysdate, 'YYYY') - interval '1' year and
f.fechainicio < trunc(sysdate, 'YYYY')
group by r.cedula
having count(distinct trunc(r.fechainicio, 'MM')) = 12;
Do you search for count()?
Select DATEPART(MONTH, Reserva.FECHAINICIO), count(*)
From Cliente
inner join Persona
on Cliente.CEDULA = Persona.CEDULA
inner join Reserva
on Cliente.cedula = Reserva.CEDULA
Group BY DATEPART(MONTH, Reserva.FECHAINICIO);
Try with bellow query, Put relevant column names for client.id_column, date_column
Select Month, COUNT(<<client.id_column>>) TotalCount
From Cliente
inner join Persona
on Cliente.CEDULA = Persona.CEDULA
inner join Reserva
on Cliente.cedula = Reserva.CEDULA
where Reserva.<<date_column>> >= '2017-01-01'
AND Reserva.<<date_column>> <= '2017-12-31'
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Reserva.<<date_column>>), 0), as Month;

storing result of a query based on the date in a rolling monthly table

I am trying to store the result of a query into a monthly table (last 30 days) based on dates.
Example for February 1st 2018: gives me a count of 310
SELECT *
FROM properties p
INNER JOIN orders o
ON o.property_id = p.id
WHERE o.type = 'Order::PromotedListing'
AND o.expired_at::DATE > '2018-02-01'
AND o.created_at::DATE <= '2018-02-01'
ORDER BY o.updated_at
For February 2nd 2018: gives me a count of 307
SELECT *
FROM properties p
INNER JOIN orders o
ON o.property_id = p.id
WHERE o.type = 'Order::PromotedListing'
AND o.expired_at::DATE > '2018-02-02'
AND o.created_at::DATE <= '2018-02-02'
ORDER BY o.updated_at
and so on.
I want to store these counts for last 30 days based on dates in a temp table/CTE. Something like this -
day, count
2018-02-01, 310
2018-02-02, 307
...
...
so I came up with this query but it is not doing what I am trying to do.
WITH monthly_dates AS (
SELECT d.date as day
FROM generate_series(current_date - interval '30 day',
current_date,
'1 day') AS d
),
featured_listings AS (
SELECT o.expired_at::date, o.created_at::date, o.updated_at::date
FROM properties p
INNER JOIN orders o
ON o.property_id = p.id
WHERE o.type = 'Order::PromotedListing'
)
SELECT m.day, COUNT(*)
FROM monthly_dates AS m
LEFT JOIN featured_listings AS f
ON m.day = f.updated_at
WHERE f.expired_at > m.day
AND f.created_at <= m.day
GROUP BY 1
ORDER BY 1;
Any input on accomplishing this task will be appreciated.
You seem to want:
SELECT g.dte, count(o.property_id)
FROM generate_series('2018-02-01'::date, '2018-02-28'::date, interval '1 day'
) g(dte) INNER JOIN
orders o
ON o.expired_at::DATE > g.dte AND o.created_at::DATE <= o.gte
GROUP BY g.dte
ORDER BY g.dte;
I don't think you need the properties table for this query.

Postgres SQL Allow COUNT(*) to be zero when filtering by date

This query will get me a COUNT of books within each library. Books are attached to libraries via the shelf table.
The query works fine without the date filtering. It shows me all the counts including library id's with associated counts of zero.
Once I bring in the created_date_time filtering it no longer will include counts of zero.
I want to know counts of books created in the last 30 days GROUPed by Library but also show Library's with 0 counts
SELECT l.id as "library_id", COUNT(b.id) AS "book_count"
FROM shelf s
LEFT JOIN book b ON s.id = b.shelf_id
LEFT JOIN library l ON l.id = s.library_id
WHERE b.created_date_time >= current_date - interval '30' day
AND b.created_date_time < current_date
GROUP BY l.id
If you want to group by library.id, don't use LEFT JOIN on library: no good can come out of that.
The easiest way of doing this, is to add your predicate to the join condition, instead of WHERE:
SELECT l.id as "library_id", COUNT(b.id) AS "book_count"
FROM library l
LEFT JOIN shelf s ON l.id = s.library_id
LEFT JOIN book b ON s.id = b.shelf_id
AND b.created_date_time >= current_date - interval '30 days'
AND b.created_date_time < current_date
GROUP BY l.id
You could also use a conditional aggregate:
SELECT l.id as "library_id",
COUNT(b.id)
FILTER (WHERE b.created_date_time >= current_date - interval '30 days'
AND b.created_date_time < current_date)
AS "book_count"
FROM library l
LEFT JOIN shelf s ON l.id = s.library_id
LEFT JOIN book b ON s.id = b.shelf_id
GROUP BY l.id
Note: SUM returns NULL when there are no rows (instead of zero, which is what COUNT returns in those situations).
As the Horse mentioned, for a quick fix you could move the date restriction from the WHERE clause to the ON clause:
SELECT l.id as "library_id", COUNT(b.id) AS "book_count"
FROM shelf s
LEFT JOIN book b
ON s.id = b.shelf_id
LEFT JOIN library l
ON l.id = s.library_id AND
b.created_date_time >= current_date - interval '30' day AND
b.created_date_time < current_date
GROUP BY l.id
Note that another way of handling this would be to remove the date criteria from the WHERE clause and instead use conditional aggregation when tallying the book count:
SELECT l.id as "library_id",
SUM(CASE WHEN b.created_date_time >= current_date - interval '30' day AND
b.created_date_time < current_date
THEN 1 ELSE 0 END) AS "book_count"
FROM shelf s
LEFT JOIN book b
ON s.id = b.shelf_id
LEFT JOIN library l
ON l.id = s.library_id
GROUP BY l.id