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

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.

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

Correct SQL Statement for duplicating items with special scenario

All check items in the picture must be shown.
Here is my sql statement.
SELECT DISTINCT
c.code || ' - ' || c.description || ' - ' || s.num AS SearchColumn,
s.num AS Num,
c.code AS Code,
c.product AS Product,
f.faciltyname AS FacilityName,
f.facilityid AS FacilityID,
c.description AS Description,
c.Number AS Num
FROM Facility f
JOIN Code c ON f.codeid = c.codeid
JOIN Structure s ON c.structureid = s.structureid
WHERE searchcolumn > ""
AND ((c.product = 'PROD1' OR c.product IS NULL ))
ORDER BY c.code ASC
they were only duplicating from the code, the num and the productid (which also have special scenario) but actually it has different facilityid and facility name which is needed

SQL Server: Get the most recent Instance of date record that's added within 90 days

So I have this code where it gets me these columns/data and the tables they belong to:
StudentName -- ST
stunum -- ST
ssn -- ST
Campus -- CA
SchoolStatus -- SS
Program -- AE
AYStart -- FS
AYEnd -- FS
AYStatus -- PS
StaffName -- SF
fastudentayid -- AU
DateAdded -- AU
This is my code
select rtrim(st.lastname) +', '+rtrim(st.FirstName) as StudentName,
st.StuNum,
st.SSN as SSN,
ca.Descrip as Campus,
ss.Descrip as SchoolStatus,
ae.adProgramDescrip as Program,
convert(varchar(10),fs.StartDate,101) as AYStart,
convert(varchar(10),fs.EndDate,101) as AYEnd,
ps.Descrip as AYStatus,
rtrim(sf.lastname) +', '+rtrim(sf.FirstName) as StaffName,
fs.faStudentAyID as faStudentAyID,
convert(varchar(10), MAX(af.DateAdded),101) as DateAdded
from stuTbl (nolock) as ST
join CpsTbl (nolock) as CA
on ca.CpsID = st.CpsID
join scStatTbl (nolock) as SS
on ss.ScStatTblID = st.ScStatTblID
join AdEnTbl (nolock) as AE
on ae.stuTblID = st.stuTblID
join faStAy (nolock) as FS
on fs.AdEnTblID = ae.AdEnTblID
join FaPStat (nolock) as PS
on ps.FaPStatID = fs.FaPStatID
join (select RecordID, ColumnName, NewVal, UserID, DateAdded, MAX(DateAdded) as MDA
from syA_FaPStatTbl
where ColumnName = 'Package Status'
and (NewVal = '38'
or NewVal = '40'
or NewVal = '43'
or NewVal = '67'
or NewVal = '68')
and DateAdded between getDate()-90 and getDate()
group by RecordID, ColumnName, NewVal, UserID, DateAdded) as AF
on af.RecordID = fs.faStAyID
join StaffTbl (nolock) as SF
on af.UserID = sf.StaffTblID
where (ps.Descrip = 'Submitted'
or ps.Descrip = 'Resubmitted'
or ps.Descrip = 'Pell Submitted'
or ps.Descrip = 'Aid Submitted'
or ps.Descrip = 'Aid Resubmitted')
and af.DateAdded between getDate()-90 and getDate()
Group by st.lastname, st.FirstName,
st.StuNum,
st.SSN,
ca.Descrip,
ss.Descrip,
ae.adProgramDescrip,
fs.StartDate,
fs.EndDate,
ps.Descrip,
sf.lastname, sf.FirstName,
fs.faStudentAyID
I am getting what I need as far as the data goes in that I am not duplicating entries. My problem is if someone alters the status at the client end software, it pulls both instances where the status was switched to Submitted as long as the change is within 90 days of today's date.
Here's a sample data -- notice Crytal Ball's record? Someone updated the status and submitted it on 5/11 and again on 6/5/2018. I just need the latest record no matter how many times the status is updated regardless of how many times it happened the past 90 days, which in this case is the 6/5/2018 one.
Jones, Mary || 124926 || xxx-xx-xxxx || Seattle || Active || MCA Prog || 05/28/2018 || 12/23/2018 || Submitted || Doe, John || 1763799 || 06/06/2018
Doe, Dawn || 126954 || xxx-xx-xxxx || Online || Ready to Start || MBC Prog || 05/28/2018 || 12/23/2018 || Resubmitted || Jones, Bob, || 1760731 || 06/06/2018
Ball, Crystal || 12399 || xxx-xx-xxxx || Chattanooga || Active || MCA Dipl || 07/02/2018 || 02/10/2019 || Submitted || Jones, Jenny || 1734032 || 05/11/2018
Ball, Crystal || 12399 || xxx-xx-xxxx || Chattanooga || Active || MCA Dipl || 07/02/2018 || 02/10/2019 || Resubmitted || Tavares, John || 1734032 || 06/05/2018
Barnes, Matt || 11817 || xxx-xx-xxxx || Online || Drop || 4 yr BSAH Mgt || 04/23/2018 || 11/18/2018 || Submitted || Doe, Luis || 1759782 || 04/27/2018
EDIT:
- I've tried putting Top 1 on the JOIN (Select) portion of the code and it doesn't let me pull any records at all.
All you need to do is use a row_number() to get the right data like below
select
StudentName,StuNum,SSN,Campus,
SchoolStatus,Program,AYStart,AYEnd,AYStatus,
StaffName,faStudentAyID,DateAdded
from
(select *,
row_number() over( partition by fs.faStudentAyID order by DateAdded) as rn
from
(
select rtrim(st.lastname) +', '+rtrim(st.FirstName) as StudentName,
st.StuNum,
st.SSN as SSN,
ca.Descrip as Campus,
ss.Descrip as SchoolStatus,
ae.adProgramDescrip as Program,
convert(varchar(10),fs.StartDate,101) as AYStart,
convert(varchar(10),fs.EndDate,101) as AYEnd,
ps.Descrip as AYStatus,
rtrim(sf.lastname) +', '+rtrim(sf.FirstName) as StaffName,
fs.faStudentAyID as faStudentAyID,
convert(varchar(10), MAX(af.DateAdded),101) as DateAdded
from stuTbl (nolock) as ST
join CpsTbl (nolock) as CA
on ca.CpsID = st.CpsID
join scStatTbl (nolock) as SS
on ss.ScStatTblID = st.ScStatTblID
join AdEnTbl (nolock) as AE
on ae.stuTblID = st.stuTblID
join faStAy (nolock) as FS
on fs.AdEnTblID = ae.AdEnTblID
join FaPStat (nolock) as PS
on ps.FaPStatID = fs.FaPStatID
join (select RecordID, ColumnName, NewVal, UserID, DateAdded, MAX(DateAdded) as MDA
from syA_FaPStatTbl
where ColumnName = 'Package Status'
and (NewVal = '38'
or NewVal = '40'
or NewVal = '43'
or NewVal = '67'
or NewVal = '68')
and DateAdded between getDate()-90 and getDate()
group by RecordID, ColumnName, NewVal, UserID, DateAdded) as AF
on af.RecordID = fs.faStAyID
join StaffTbl (nolock) as SF
on af.UserID = sf.StaffTblID
where (ps.Descrip = 'Submitted'
or ps.Descrip = 'Resubmitted'
or ps.Descrip = 'Pell Submitted'
or ps.Descrip = 'Aid Submitted'
or ps.Descrip = 'Aid Resubmitted')
and af.DateAdded between getDate()-90 and getDate()
Group by st.lastname, st.FirstName,
st.StuNum,
st.SSN,
ca.Descrip,
ss.Descrip,
ae.adProgramDescrip,
fs.StartDate,
fs.EndDate,
ps.Descrip,
sf.lastname, sf.FirstName,
fs.faStudentAyID
) t
)t
where rn=1

Join two of the same tables to another table and output the info of the (same) table in the same row

Sorry for the bad/long title but I don't know how else to put it.
What I want to do is join to 'A' tables and join it to the 'B' table where both 'A' have a foreign key in common and display info from both 'A' tables in the same row while preventing duplicates such as the example in the pic:
I know the query is just doing it's job, but is there a way to prevent 'duplicates' by comparing between the rows before output?
Here's what I tried, I know it may be bad performance-wise and there may be better ways but this is for a mini-project with a small DB, where performance shouldn't really matter:
SELECT w.emp_id AS emp1_id, w2.emp_id AS emp2_id,
e.fname || ' ' || e.lname AS emp1_name, e1.fname || ' ' || e1.lname AS emp2_name,
e.jobtitle AS emp1_jobtitle, e1.jobtitle AS emp2_jobtitle, e2.fname || ' ' || e2.lname AS cs_name
FROM work_on w
LEFT JOIN work_on w2
on w.emp_id != w2.emp_id and w.ticket_id = w2.ticket_id
LEFT JOIN employee e
on w.emp_id = e.emp_id
LEFT JOIN employee e1
on w2.emp_id = e1.emp_id
LEFT JOIN ticket t
on t.ticket_id = w.ticket_id
LEFT JOIN customer_problem p
on p.problem_id = t.problem_id
LEFT JOIN employee e2
on e2.emp_id = p.emp_id
WHERE e2.emp_id = 20 and p.submit_date >= '2018-04-08'
and p.submit_date <= '2018-04-11' and e1.emp_id != e.emp_id
ORDER BY w.emp_id;
My tables:
Employee: | Work_On: | Ticket: | Problem
----------+------------+--------------+------------
emp_id work_id ticket_id problem_id
fname emp_id problem_id emp_id
lname ticket_id
In this case I'm trying to combine two Employee on Work_On where they have the Ticket in common and another Employee which connects to the ticket via the Problem table.
Here is one option using least/greatest:
SELECT DISTINCT
LEAST(w.emp_id, w2.emp_id) AS emp1_id,
GREATEST(w.emp_id, w2.emp_id) AS emp2_id,
LEAST(e.fname || ' ' || e.lname, e1.fname || ' ' || e1.lname) AS emp1_name,
GREATEST(e.fname || ' ' || e.lname, e1.fname || ' ' || e1.lname) AS emp2_name,
LEAST(e.jobtitle, e1.jobtitle) AS emp1_jobtitle,
GREATEST(e.jobtitle, e1.jobtitle) AS emp2_jobtitle,
e2.fname || ' ' || e2.lname AS cs_name
FROM work_on w
LEFT JOIN work_on w2
ON w.emp_id != w2.emp_id AND w.ticket_id = w2.ticket_id
LEFT JOIN employee e
ON w.emp_id = e.emp_id
LEFT JOIN employee e1
ON w2.emp_id = e1.emp_id
LEFT JOIN ticket t
ON t.ticket_id = w.ticket_id
LEFT JOIN customer_problem p
ON p.problem_id = t.problem_id
LEFT JOIN employee e2
ON e2.emp_id = p.emp_id
WHERE
e2.emp_id = 20 AND
p.submit_date >= '2018-04-08' AND
p.submit_date <= '2018-04-11' AND
e1.emp_id != e.emp_id
ORDER BY w.emp_id;
To see why the least/greatest trick works, consider the following two records/columns:
emp1_id | emp2_id
2 | 15
15 | 2
It should be clear that while these records are distinct now, if we instead choose the least id followed by the greatest id, they appear identical:
LEAST(emp_id1, emp_id2) | GREATEST(emp_id1, emp_id2)
2 | 15
2 | 15
Then, using SELECT DISTINCT removes one of the two duplicate rows.