Aggregate function as condition - sql

I'm having problem trying to use aggregate function result as a condition. Basically, I need to select rows that have "View count" more than 3. Here is code that works:
SELECT b.BranchNo AS "Branch Number",
p.PropertyNo || ', ' || p.PostCode || ', ' || p.City || ', ' || p.Street AS "Object address" , count(v.ViewDate) as "View count"
FROM Branch b INNER JOIN PropertyForRent p ON b.BranchNo=p.PropertyBranchNo
INNER JOIN Viewing v ON p.PropertyNo=v.ViewPropertyNo WHERE v.ViewDate>='2014-01-01'
GROUP BY b.BranchNo, p.PropertyNo;
I was trying to use something like that:
HAVING count(v.ViewDate)>=3
But that obviously didn't work. Is there a way of making such condition without using a subquery?

It looks like the fields in your select don't match up with the group by, but other than that, the "condition based on aggregate" is exactly what the having clause is for. For example, this query should work:
SELECT
b.BranchNo AS "Branch Number",
p.PropertyNo || ', ' || p.PostCode || ', ' || p.City || ', ' || p.Street AS "Object address" ,
count(v.ViewDate) as "View count"
FROM Branch b INNER JOIN PropertyForRent p ON b.BranchNo=p.PropertyBranchNo
INNER JOIN Viewing v ON p.PropertyNo=v.ViewPropertyNo WHERE v.ViewDate>='2014-01-01'
GROUP BY b.BranchNo, p.PropertyNo, p.PostCode, p.City, p.Street
HAVING count(v.ViewDate) >= 3;

You need to do grouping in Viewing table sub-query to get the counts. Then join to that.
SELECT b.BranchNo AS [Branch Number],
p.PropertyNo + ', ' + p.PostCode + ', ' + p.City + ', ' + p.Street AS [Object address],
v.ViewCount
FROM Branch b
INNER JOIN PropertyForRent p ON b.BranchNo=p.PropertyBranchNo
INNER JOIN (SELECT ViewPropertyNo, COUNT(*) as ViewCount
FROM Viewing
WHERE v.ViewDate>='2014-01-01'
GROUP BY ViewPropertyNo
) AS v ON p.PropertyNo = v.ViewPropertyNo
WHERE v.ViewCount >= 3;

Related

Use of NATURAL JOIN

According by the task I'm doing I need to change INNER JOIN to NATURAL JOIN and get the same result as this result of inner join
I got that result with this query:
SELECT person_order.order_date,
person.name || ' (' || 'age:' || person.age || ')' AS person_information
FROM person_order
INNER JOIN person ON person_order.person_id = person.id
ORDER BY 1,
2;
Here is what my tables look like my tables
I'm trying to use subquery in a FROM statement but now result is differ with previous query different rusult
SELECT pers_ord.order_date,
person.name || ' (' || 'age:' || person.age || ')' AS person_information
FROM (
SELECT order_date
FROM person_order
) AS pers_ord
NATURAL JOIN person
ORDER BY 1,
2;
Could you try this:
SELECT pers_ord.order_date,
person.name || ' (' || 'age:' || person.age || ')' AS person_information
FROM (
SELECT order_date, person_id AS id
FROM person_order
) AS pers_ord
NATURAL JOIN person
ORDER BY 1,
2;
Looking at examples here you can see the columns on which the join is performed need to be the same. And in your person_order table the person_id column is not matching the id in the person table.

Split postgresql table column into multiple columns

Ok so this is the partial query, working 100% as we speak. There's a comment as you can see where the query of the 4 columns is supposed to go.
SELECT DISTINCT
p.id AS "Project ID",
p.title AS "Project Title",
p.summary AS "Project Summary",
to_char(p.expected_start_date, 'YYYY-MM-dd') AS "Expected Start Date",
to_char(p.expected_end_date, 'YYYY-MM-dd') AS "Expected End Date",
to_char(p.actual_start_date, 'YYYY-MM-dd') AS "Actual Start Date",
to_char(p.actual_end_date, 'YYYY-MM-dd') AS "Actual End Date",
d.name AS "Center/Department",
/* THIS IS WHERE THE Query has to go.*/
SELECT DISTINCT string_agg(distinct gd.name ||' ('||gs.name||')', ' | ') AS "GRANT NAME AND STATUS"
from grant_detail gd
JOIN grant_status gs on gd.status_id = gs.id
JOIN project_budget pb ON gd.id = pb.grant_id
WHERE pb.project_id = p.id
group by p.id),
SELECT DISTINCT string_agg(distinct o.name, ', ') AS "FUNDER"
FROM organization o
JOIN organization_type ot ON ot.id = o.type_id
JOIN grant_detail gt ON gt.organization_id = o.id
JOIN project_budget pb ON pb.grant_id = gt.id
WHERE pb.project_id = p.id
AND ot.name = 'Funder'),
SELECT string_agg(pc.name, ', ') AS "Categories"
FROM project_project_categories ppc
JOIN project_category pc ON ppc.project_category_id = pc.id
WHERE ppc.project_id = p.id),
( /*========= Student Researcher Full Name (+email) =========*/
SELECT DISTINCT string_agg(distinct s.first_name || ' ' ||
s.last_name ||' ('||
s.email ||')', ', ')
AS "Student Researcher"
FROM project_stakeholder ps
JOIN stakeholder s ON s.id = ps.stakeholder_id
JOIN project_role pr ON ps.role_id = pr.id
WHERE pr.name = 'Student Researcher'
AND ps.project_id = p.id
GROUP BY p.id)
FROM
/*CONTEXT*/
project p
LEFT JOIN innovation_category c2 ON p.innovation_category_id = c2.id
LEFT JOIN department d ON p.department_id = d.id
JOIN project_health ph ON p.health_id = ph.id
JOIN project_status ps ON p.status_id = ps.id
-- WHERE
-- -- p.actual_start_date <= '__beforeActualStartDate'
-- -- AND p.actual_end_date >= '__afterActualEndDate'
-- -- AND p.expected_start_date <= '__beforeStartDate'
-- -- AND p.expected_end_date >= '__afterEndDate'
ORDER BY
p.title
UPDATE: For Phelipe:
You can see where I've commented my code in the place where the four columns need to be que
Here is a fully dynamic solution that can work with a variable list of roles.
This solution relies on the creation of a composite type role_type which includes the list of roles and which can be called at the runtime :
CREATE OR REPLACE PROCEDURE role_type () LANGUAGE plpgsql AS
$$
DECLARE
role_list text ;
BEGIN
SELECT string_agg(r_name || ' text', ',' ORDER BY r_id)
INTO role_list
FROM role ;
EXECUTE 'DROP TYPE IF EXISTS role_type' ;
EXECUTE 'CREATE TYPE role_type AS (' || role_list || ')' ;
END ;
$$
After calling the procedure role_type(), the result is provided by a simple query using the jsonb_object_agg and jsonb_populate_record standard functions :
CALL role_type () ;
SELECT a.p_id AS "Project id"
, a.p_title AS "Project Title"
, (jsonb_populate_record(NULL :: role_type, jsonb_object_agg(lower(a.r_name), (a.usr_list)))).*
FROM
( SELECT p.p_id
, p.p_title
, r.r_name
, string_agg(u.fname || ' ' || u.lname, ', ') AS usr_list
FROM project_usr AS pu
INNER JOIN project AS p
ON p.p_id = pu.project_id
INNER JOIN usr AS u
ON u.usr_id = pu.usr_id
INNER JOIN role AS r
ON r.r_id = pu.role_id
GROUP BY p.p_id, p.p_title, r.r_name
) AS a
GROUP BY a.p_id, a.p_title
ORDER BY a.p_id
Finally, instead of calling the role_type() procedure each time before executing the query, it can be called by trigger when the role list is changing :
CREATE OR REPLACE FUNCTION role_after_insert_update ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
CALL role_type () ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER role_after_insert_update AFTER INSERT OR UPDATE ON role
FOR EACH ROW EXECUTE FUNCTION role_after_insert_update() ;
CREATE OR REPLACE FUNCTION role_after_delete ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
CALL role_type () ;
RETURN OLD ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER role_after_delete AFTER DELETE ON role
FOR EACH ROW EXECUTE FUNCTION role_after_delete() ;
see the full test result in dbfiddle
you can do this but it supposes that role table is always the same with same id
select pu.project_id,
case r.r_id when 1 then u.fname||' '||u.lname else '' end as Teacher,
case r.r_id when 2 then u.fname||' '||u.lname else '' end as Student,
case r.r_id when 3 then u.fname||' '||u.lname else '' end as Volunteer
from project_usr pu join role r on r.r_id = pu.role_id
join usr u on pu.usr_id = u.usr_id
Result here
Crosstab
You could use crosstab too but the problem will be the same, you have to know the roles
select * from crosstab($$
select pu.project_id,r.r_name, u.fname||' '||u.lname
from project_usr pu join role r on r.r_id = pu.role_id
join usr u on pu.usr_id = u.usr_id
$$,'select r_name from role')
as final_result (project_id integer, Teacher varchar,Student varchar,Volunteer varchar)
Result here
Maybe something like this (not tested, not enough informations)
with r as (
select * from crosstab($$
select pu.project_id,r.r_name, u.fname||' '||u.lname
from project_usr pu join role r on r.r_id = pu.role_id
join usr u on pu.usr_id = u.usr_id
$$,'select r_name from role')
as final_result (project_id integer, Teacher varchar,Student varchar,Volunteer varchar)
)
SELECT DISTINCT
p.id AS "Project ID",
p.title AS "Project Title",
p.summary AS "Project Summary",
to_char(p.expected_start_date, 'YYYY-MM-dd') AS "Expected Start Date",
to_char(p.expected_end_date, 'YYYY-MM-dd') AS "Expected End Date",
to_char(p.actual_start_date, 'YYYY-MM-dd') AS "Actual Start Date",
to_char(p.actual_end_date, 'YYYY-MM-dd') AS "Actual End Date",
d.name AS "Center/Department",
/* THIS IS WHERE THE Query has to go.*/
r.teacher,
r.student,
r.volunteer,
SELECT DISTINCT string_agg(distinct gd.name ||' ('||gs.name||')', ' | ') AS "GRANT NAME AND STATUS"
from grant_detail gd
JOIN grant_status gs on gd.status_id = gs.id
JOIN project_budget pb ON gd.id = pb.grant_id
WHERE pb.project_id = p.id
group by p.id),
SELECT DISTINCT string_agg(distinct o.name, ', ') AS "FUNDER"
FROM organization o
JOIN organization_type ot ON ot.id = o.type_id
JOIN grant_detail gt ON gt.organization_id = o.id
JOIN project_budget pb ON pb.grant_id = gt.id
WHERE pb.project_id = p.id
AND ot.name = 'Funder'),
SELECT string_agg(pc.name, ', ') AS "Categories"
FROM project_project_categories ppc
JOIN project_category pc ON ppc.project_category_id = pc.id
WHERE ppc.project_id = p.id),
( /*========= Student Researcher Full Name (+email) =========*/
SELECT DISTINCT string_agg(distinct s.first_name || ' ' ||
s.last_name ||' ('||
s.email ||')', ', ')
AS "Student Researcher"
FROM project_stakeholder ps
JOIN stakeholder s ON s.id = ps.stakeholder_id
JOIN project_role pr ON ps.role_id = pr.id
WHERE pr.name = 'Student Researcher'
AND ps.project_id = p.id
GROUP BY p.id)
FROM
/*CONTEXT*/
project p
LEFT JOIN innovation_category c2 ON p.innovation_category_id = c2.id
LEFT JOIN department d ON p.department_id = d.id
JOIN project_health ph ON p.health_id = ph.id
JOIN project_status ps ON p.status_id = ps.id
/* don't know the key */
join r on ...
ORDER BY
p.title

How to unite several tables in a one so the names of the columns became the row names?

for instance I have
SELECT customer_id, first_name || ', ' || last_name || ', ' || email as "customer's info"
FROM customer
WHERE customer_id = 5
;
SELECT count(i.film_id) AS "num.of films rented" FROM payment p
JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
WHERE r.rental_date >= ('2014-01-01'::date)
AND r.rental_date <= ('2017-05-03'::date)
AND p.customer_id = 5
;
I want in output
metric1 | metric2
----------------------------
customer's info | blalalalal
num.of films rented | blalalalal
I try smth like, but nothing
SELECT * FROM crosstab(
SELECT first_name || ', ' || last_name || ', ' || email
FROM customer WHERE customer_id = 5,
SELECT count(i.film_id) FROM payment p
JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
WHERE r.rental_date >= ('2014-01-01'::date)
AND r.rental_date <= ('2017-05-03'::date))
AS ('fjfjf' TEXT, 'fjfjf' int );
Could you help me?
I dont know how to do it in postgress
Thanks a lot
I would UNION ALL the two queries together - but remember to CAST the count value as a string, as you need matching data types to UNION:
SELECT
'customer''s info' AS "name"
, first_name || ', ' || last_name || ', ' || email AS "value"
FROM customer c
UNION ALL
'num.of films rented' AS "name"
, COUNT(i.film_id)::VARCHAR(5) AS "value"
FROM payment p
JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
WHERE r.rental_date >= ('2014-01-01'::date)
AND r.rental_date <= ('2017-05-03'::date)
WHERE customer_id = 5
;
It is unclear to me why inventory is in the second join.
SELECT 'customer''s info' as metric1,
first_name || ', ' || last_name || ', ' || email as metric2
FROM customer
WHERE customer_id = 5
UNION ALL
SELECT 'num.of films rented' as metric1, count(i.film_id)::text AS metric2
FROM payment p JOIN
rental r
ON p.rental_id = r.rental_id
WHERE r.rental_date >= '2014-01-01'::date AND
r.rental_date <= '2017-05-03'::date AND
p.customer_id = 5;
You could also combine this into a single query if you are just trying to get the results in a single result set:
SELECT (first_name || ', ' || last_name || ', ' || email) as customer_info,
count(i.film_id) as num_films
FROM payment p JOIN
rental r
ON p.rental_id = r.rental_id JOIN
customer c
ON c.customer_id = p.customer_id
WHERE r.rental_date >= '2014-01-01'::date AND
r.rental_date <= '2017-05-03'::date AND
c.customer_id = 5
GROUP BY c.customer_id;
(This puts the values in one row with two columns.) Using a subquery, the results can be easily unpivoted.

Postgresql: Subquery in FROM must have an alias - with multiple joins

I get the following error:
ERROR: subquery in FROM must have an alias
LINE 11: (SELECT "domiciles"."id" AS id,
^
HINT: For example, FROM (SELECT ...) [AS] foo.
with the following SQL query:
SELECT "domiciles".*
FROM "domiciles"
LEFT OUTER JOIN
(SELECT "domiciles"."id" AS id,
string_agg("locations"."name"::text, ' ') AS name
FROM "domiciles"
INNER JOIN "locations" ON "locations"."id" = "domiciles"."place_id"
AND "locations"."type" IN ('Place')
GROUP BY "domiciles"."id") place ON place.id = "domiciles"."id"
LEFT OUTER JOIN
(SELECT "domiciles"."id" AS id,
string_agg("accounts"."email"::text, ' ') AS email
FROM "domiciles"
INNER JOIN "accounts" ON "accounts"."id" = "domiciles"."user_id"
AND "accounts"."type" IN ('User')
GROUP BY "domiciles"."id") user ON user.id = "domiciles"."id"
WHERE "domiciles"."deleted_at" IS NULL
I have tried to add AS 'some_text' in many parts of the query, but I cannot solve this problem. Any idea?
Here the complete SQL query to have an idea what I want to do:
SELECT "domiciles".*,
((ts_rank((to_tsvector('german', unaccent(coalesce("domiciles"."is_default"::text, ''))) || to_tsvector('german', unaccent(coalesce("domiciles"."created_at"::text, ''))) || to_tsvector('german', unaccent(coalesce("domiciles"."updated_at"::text, ''))) || to_tsvector('german', unaccent(coalesce(place.name::text, ''))) || to_tsvector('german', unaccent(coalesce(user.email::text, ''))) || to_tsvector('german', unaccent(coalesce(owner.email::text, '')))), (to_tsquery('german', ''' ' || unaccent('abc') || ' ''' || ':*')), 0))) AS pg_search_rank
FROM "domiciles"
LEFT OUTER JOIN
(SELECT "domiciles"."id" AS id,
string_agg("locations"."name"::text, ' ') AS name
FROM "domiciles"
INNER JOIN "locations" ON "locations"."id" = "domiciles"."place_id"
AND "locations"."type" IN ('Place')
AND "locations"."deleted_at" IS NULL
GROUP BY "domiciles"."id") place ON place.id = "domiciles"."id"
LEFT OUTER JOIN
(SELECT "domiciles"."id" AS id,
string_agg("accounts"."email"::text, ' ') AS email
FROM "domiciles"
INNER JOIN "accounts" ON "accounts"."id" = "domiciles"."user_id"
AND "accounts"."type" IN ('User')
AND "accounts"."deleted_at" IS NULL
GROUP BY "domiciles"."id") user ON user.id = "domiciles"."id"
LEFT OUTER JOIN
(SELECT "domiciles"."id" AS id,
string_agg("accounts"."email"::text, ' ') AS email
FROM "domiciles"
INNER JOIN "locations" ON "locations"."id" = "domiciles"."place_id"
AND "locations"."type" IN ('Place')
AND "locations"."deleted_at" IS NULL
INNER JOIN "accounts" ON "accounts"."id" = "locations"."user_id"
AND "accounts"."type" IN ('User')
AND "accounts"."deleted_at" IS NULL
GROUP BY "domiciles"."id") owner ON owner.id = "domiciles"."id"
WHERE "domiciles"."deleted_at" IS NULL
AND "domiciles"."user_id" = $1
AND (((to_tsvector('german', unaccent(coalesce("domiciles"."is_default"::text, ''))) || to_tsvector('german', unaccent(coalesce("domiciles"."created_at"::text, ''))) || to_tsvector('german', unaccent(coalesce("domiciles"."updated_at"::text, ''))) || to_tsvector('german', unaccent(coalesce(place.name::text, ''))) || to_tsvector('german', unaccent(coalesce(user.email::text, ''))) || to_tsvector('german', unaccent(coalesce(owner.email::text, '')))) ## (to_tsquery('german', ''' ' || unaccent('abc') || ' ''' || ':*'))))
Found the problem! The following sentences:
place ON place.id
coalesce(place.name::text, '')
should be
"place" ON "place"."id"
coalesce("place"."name"::text, '')
and this for all sentences: place, owner, user, id

Sql: Select count(*) from (select ...)

I have a sql select command with grouping and I want to get the number of total rows. How do I achieve that?
My sql command:
select p.UserName, p.FirstName + ' ' + p.LastName as [FullName]
,count(b.billid) as [Count], sum(b.PercentRials) as [Sum] from Bills b
inner join UserProfiles p on b.PayerUserName=p.UserName
where b.Successful=1
group by p.UserName, p.FirstName + ' ' + p.LastName
I have tried these with no luck:
select count(*) from (select ...)
and
select count(select ...)
EDIT
this is the complete sql statement that I want to run:
select count(*) from ( select p.UserName, p.FirstName + ' ' + p.LastName as [FullName]
,count(b.billid) as [Count], sum(b.PercentRials) as [Sum] from Bills b
inner join UserProfiles p on b.PayerUserName=p.UserName
where b.Successful=1
group by p.UserName, p.FirstName + ' ' + p.LastName)
and I get this error on the last line:
Incorrect syntax near ')'.
SELECT COUNT(*)
FROM
(
select p.UserName, p.FirstName + ' ' + p.LastName as [FullName]
,count(b.billid) as [Count], sum(b.PercentRials) as [Sum] from Bills b
inner join UserProfiles p on b.PayerUserName=p.UserName
where b.Successful=1
group by p.UserName, p.FirstName + ' ' + p.LastName --<-- Removed the extra comma here
) A --<-- Use an Alias here
As I expected from your shown attempt you were missing an Alias
select count(*)
from (select ...) Q --<-- This sub-query in From clause needs an Alias
Edit
If you only need to know the rows returned by this query and you are executing this query anyway somwhere in your code you could simply make use of ##ROWCOUNT function. Something like....
SELECT ...... --<-- Your Query
SELECT ##ROWCOUNT --<-- This will return the number of rows returned
-- by the previous query
You are missing an alias after your subquery
select count(*) from (select ...) v
You can also try to return total count using existing SQL, without using subquery
select p.UserName,
p.FirstName + ' ' + p.LastName as [FullName],
count(b.billid) as [Count],
sum(b.PercentRials) as [Sum],
COUNT(*) over () [TotalCount] ------- total count here
from Bills b
inner join UserProfiles p on b.PayerUserName=p.UserName
where b.Successful=1
group by p.UserName, p.FirstName + ' ' + p.LastName
Try this code:
SELECT COUNT(*)
FROM (
SELECT p.UserName
,p.FirstName + ' ' + p.LastName AS [FullName]
,COUNT(b.billid) AS [Count]
,SUM(b.PercentRials) AS [Sum]
FROM Bills b
INNER JOIN UserProfiles p
ON b.PayerUserName = p.UserName
WHERE b.Successful = 1
GROUP BY p.UserName
,p.FirstName + ' ' + p.LastName
) a
based on your edit. You were missing derived tabled alias.
If you look at FROM clause syntax you will see
| derived_table [ AS ] table_alias [ ( column_alias [ ,...n ] ) ]
When a derived table, rowset or table-valued function, or operator
clause (such as PIVOT or UNPIVOT) is used, the required table_alias at
the end of the clause is the associated table name for all columns,
including grouping columns, returned.
http://technet.microsoft.com/en-us/library/ms177634.aspx
If you have a unique column name in there, you can count that. For example I'm assuming UserName is unique here.
select count(query.UserName) from (
select p.UserName, p.FirstName + ' ' + p.LastName as [FullName]
,count(b.billid) as [Count], sum(b.PercentRials) as [Sum] from Bills b
inner join UserProfiles p on b.PayerUserName=p.UserName
where b.Successful=1
group by p.UserName, p.FirstName + ' ' + p.LastName) as query
COUNT() - aggregate function
SELECT SQL_CALC_FOUND_ROWS * FROM ...;
SELECT FOUND_ROWS(); // next query !!!!