SQL Server query to roll up data - sql

I have the following SQL statement. It joins three tables: Person, Deliverable, and DeliverableActions
select
p.first_name, p. last_name, d.title, da.type
from
Deliverable d
right join
Person p on d.person_responsible_id = p.id
right join
DeliverableAction da on da.DeliverableID = d.id
where
d.date_deadline >= #startDate and
d.date_deadline <= #endDate
order by
d.title
The result is the following:
first_name | last_name | title | type
-----------+-------------+--------------+------
Joe | Kewl | My_Report_1 | 2
Joe | Kewl | My_Report_1 | 3
Joe | Kewl | My_Report_1 | 1
Sly | Foxx | Other_Rep_1 | 1
Sly | Foxx | Other_Rep_1 | 2
My goal result is to get the following table:
first_name | last_name | title | type_1 | type_2 | type_3 | type_4
-----------+------------+--------------+--------+--------+--------+---------
Joe | Kewl | My_report_1 | 1 | 1 | 1 | 0
Sly | Foxx | Other_Rep_1 | 1 | 1 | 0 | 0
Unfortunately I don't know what term to describe what I'm doing. I've searched 'grouping' and 'aggregation', but I'm left without an answer so I am putting it to the community. Thank you in advance for your help.

you can use case based aggregation or you can also use pivot
select p.first_name,
p. last_name,
d.title,
sum(case when da.type = 1 then 1 else 0 end) as type_1,
sum(case when da.type = 2 then 1 else 0 end) as type_2,
sum(case when da.type = 3 then 1 else 0 end) as type_3,
sum(case when da.type = 4 then 1 else 0 end) as type_4,
from Deliverable d
right join Person p on d.person_responsible_id = p.id
right join DeliverableAction da on da.DeliverableID = d.id
where d.date_deadline >= #startDate and
d.date_deadline <= #endDate
group by p.first_name, p.last_name, d.title

select
first_name, last_name, title,
sum(case when type = 1 then 1 else 0 end) as type_1
from
(
select p.first_name, p. last_name, d.title, da.type from Deliverable d
right join Person p on d.person_responsible_id = p.id
right join DeliverableAction da on da.DeliverableID = d.id
where d.date_deadline >= #startDate and
d.date_deadline <= #endDate
) as a
group by first_name, last_name, title

You're looking for PIVOT
If you're using SQL Server 2008+, it has pivot function as described at http://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx
Basically, you write something like (sorry, I just pasted example from the quoted link but that should give you some idea):
-- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;

Related

SQL - Counting one attribute, grouping by another

I have a table staff
staff
pt | ward
P | 1
P | 1
T | 1
P | 2
T | 2
I want to produce a table that counts how many P's and T's there is for each ward like this:
staff
ward | P | T
1 | 2 | 1
2 | 1 | 1
I have tried this
WITH cte(ward, P, T) AS(
SELECT ward,
(SELECT COUNT(PT) FROM staff WHERE PT = 'P' ),
(SELECT COUNT(PT) FROM staff WHERE PT = 'T' ) FROM staff GROUP BY ward)
SELECT * FROM cte
but then I get this table
staff
ward | P | T
1 | 3 | 2
2 | 2 | 2
Any help would be appreciated
Case statements will work here:
SELECT
ward,
SUM(CASE WHEN pt = P THEN 1 ELSE 0 END) AS P,
SUM(CASE WHEN pt = T THEN 1 ELSE 0 END) AS T
FROM
table
GROUP BY
ward
Use conditional aggregation:
select ward, sum( (pt = 'P')::int ) as p, sum ( (pt = 'T')::int ) as t
from t
group by ward;

Sum Group By Column

I have a column (PL.UNITS) that I need to Total at the bottom of the results of a query, is it possible to sum PL.UNITS that is already summed?
Please see query below.
SELECT ID.DUEDATE AS [DUE DATE], CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE, SUM(PL.UNITS) AS UNITS from CLIENTDETAILS CD
INNER JOIN LOCATIONS L ON CD.CLIENTNUMBER = L.CLIENTNUMBER
INNER JOIN ITEMDETAILS ID ON L.LOCNUMBER = ID.LOCNUMBER
INNER JOIN PLANT PL ON ID.CODE = PL.CODE
WHERE L.OWNER = 210 and L.STATUSLIVE = 1 and ID.DUEDATE > '01/01/2017'
GROUP BY ID.DUEDATE, CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE
It's probably best to do this sort of thing in front end development. Nevertheless, here is an example (quick and dirty, but shows the idea) for sql-server:
SELECT COALESCE(a.id, 'total') AS id
, SUM(a.thing) AS thing_summed
FROM (
SELECT '1' id
, 1 thing
UNION
SELECT '2'
, 2 thing
UNION
SELECT '1'
, 3 thing
) AS a
GROUP BY ROLLUP(a.id)
Result:
+-------+--------------+
| id | thing_summed |
+-------+--------------+
| 1 | 4 |
| 2 | 2 |
| total | 6 |
+-------+--------------+

SQL SELECT multiple keys/values

I've got a table PERSON_PROPERTIES that resembles the following :
| ID | KEY | VALUE | PERSON_ID |
| 1 | fname | robert | 1 |
| 2 | lname | redford | 1 |
| 3 | fname | robert | 2 |
| 4 | lname | de niro | 2 |
| 5 | fname | shawn | 3 |
| 6 | nname | redford | 3 |
I would like to SELECT (in JPQL or in PSQL) the PERSON_ID that matches the given fname and lname.
I've tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
AND ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but it returns me no value.
I've also tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
OR ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but this way it returns me all values. I don't know how to turn the query properly for it to give me only value 1.
SELECT PERSON_ID
FROM PERSON_PROPERTIES
group by PERSON_ID
having sum(case when key = 'fname' and value = 'robert' then 1 else 0 end) > 0
and sum(case when key = 'lname' and value = 'redford' then 1 else 0 end) > 0
Groupy by the person and select only those having both values.
Another approach would be with subselect (caution, it's MS SQL 2012)
SELECT PERSON_ID
FROM PERSON_PROPERTIES
WHERE [Key] = 'fname' AND value = 'robert'
AND PERSON_ID in
(SELECT PERSON_ID FROM PERSON_PROPERTIES WHERE [Key] = 'lname' AND value = 'redford')
Fiddle Demo
Along with some colleagues we came to this answer :
SELECT p.PERSON_ID
FROM PERSON_PROPERTIES p
WHERE (p.key = 'fname' AND p.value = 'robert')
OR (p.key = 'lname' AND p.value = 'redford')
GROUP BY p.PERSON_ID
HAVING count(*) = 2
What do you think about it?
SELF JOIN also does the trick. DISTINCT for duplicate person_id:
SELECT DISTINCT a.PERSON_ID
FROM PERSON_PROPERTIES a JOIN PERSON_PROPERTIES b ON a.PERSON_ID = b.PERSON_ID
WHERE a.the_key = 'fname' AND a.value = 'robert'
AND b.the_key = 'lname' AND b.value = 'redford';
Demo
OK I will be marking this as the correct answer. The only thing I did was modified it a bit
SELECT Y.*, M.* FROM wp_postmeta as Y JOIN wp_postmeta AS M USING (`post_id`)
WHERE (Y.meta_key = 'agam_post_options_year' AND Y.meta_value = 2013)
AND (M.meta_key = 'agam_post_options_month' AND M.meta_value BETWEEN 0 AND 12 )
GROUP BY Y.meta_value, M.meta_value ORDER BY M.meta_value+0 DESC
So I get that DESC order.. however.. I noticed that it does not duplicates results... I had two posts with the same year and same month... now I don't see it... is there anything there that's preventing this ?

SQL: Querying 2 tables and sort results by values in another table

Is it possible to select all the comments for a single record and order them by the overall ratio of positive:negative votes from another table?
I think I may need to use a subselect but I really am not sure how or where.
2 tables:
table 1 [Comments]
ID | RecordID | Comment
------------------------------
1 | 100001 | blah blah
2 | 100202 | another co
3 | 100054 | lorem ips
table 2 [Ratings]
ID | CommentID | Vote
-------------------------
1 | 1 | 1
2 | 1 | 0
3 | 1 | 1
4 | 3 | 0
5 | 3 | 0
6 | 3 | 0
Please note: 'Vote' : 0 = negative vote; 1 = positive vote
Also, I am using Microsoft SQL Server
Thanks for any help :)
To sort them from positive to negative rating you can do:
SELECT c.id,
c.recordID,
c.comment
FROM comments c
INNER JOIN (
SELECT CommentID,
SUM(CASE WHEN Vote = 0 THEN -1 ELSE 1 END) AS RATING FROM ratings
GROUP BY commentID
) r ON r.commentID = c.id
ORDER BY r.RATING DESC;
sqlfiddle demo
Note that i am using the CASE to give votes with 0 a value of -1, otherwise, 10 negatives and one positive would give a rating of 1.
This query is not taking into account the comments without scores. If you want to have the comments without scores to have 0 rating, you could do:
SELECT c.id,
c.recordID,
c.comment
FROM comments c
LEFT JOIN (
SELECT CommentID,
SUM(CASE WHEN Vote = 0 THEN -1 ELSE 1 END) AS RATING FROM ratings
GROUP BY commentID
) r ON r.commentID = c.id
ORDER BY COALESCE(r.RATING,0) DESC;
EDIT:
To get the ratio try this:
SELECT c.id,
c.recordID,
c.comment,
r.rating
FROM comments c
LEFT JOIN (
SELECT CommentID,
SUM(CASE
WHEN Vote = 1
THEN 1
ELSE 0
END) / (nullif(COUNT(*) * 1.0, 0)) AS RATING
FROM ratings
GROUP BY commentID
) r ON r.commentID = c.id
ORDER BY COALESCE(r.RATING, 0) DESC;
sqlfiddle demo

sql how to transform data vertically

I have a 3 datbles Dealer, payment_type and dealer_payment_type
Dealer : dealer_id , dealer_name, dealer_address
1 | test | 123 test lane
2 | abc | abc lane
3 | def | def lane
Payment_type : paymenttype_id , paytype
1 | CHECK
2 | WIRE
3 | CREDIT
Dealer_Payment_type : DPT_id , dealer_id , payment_type_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 2
5 | 2 | 3
6 | 3 | 1
7 | 3 | 2
I have to write a query to get payment type info for each dealer , query needs to return data like this:
dealer_id , dealer_name , paytype
1 | test | check,wire,credit
2 | abc | wire,credit
3 | def | check,wire
OR
dealer_id , dealer_name , check , wire , credit
1 | test | true | true | true
2 | abc | false | true | true
3 | def | true | false | true
You did not specify what version of Oracle you are using.
If you are using Oracle 11g, then you can use the following.
To get the values into a single column, then you can use LISTAGG:
select d.dealer_id,
d.dealer_name,
listagg(p.paytype, ',') within group (order by d.dealer_id) as paytype
from dealer d
left join Dealer_Payment_type dp
on d.dealer_id = dp.dealer_id
left join payment_type p
on dp.payment_type_id = p.paymenttype_id
group by d.dealer_id, d.dealer_name;
See SQL Fiddle with demo
To get the values in separate columns, then you can use PIVOT:
select dealer_id, dealer_name,
coalesce("Check", 'false') "Check",
coalesce("Wire", 'false') "Wire",
coalesce("Credit", 'false') "Credit"
from
(
select d.dealer_id,
d.dealer_name,
p.paytype,
'true' flag
from dealer d
left join Dealer_Payment_type dp
on d.dealer_id = dp.dealer_id
left join payment_type p
on dp.payment_type_id = p.paymenttype_id
)
pivot
(
max(flag)
for paytype in ('CHECK' as "Check", 'WIRE' as "Wire", 'CREDIT' as "Credit")
)
See SQL Fiddle with Demo.
If you are not using Oracle 11g, then you can use wm_concat() to concatenate the values into a single row:
select d.dealer_id,
d.dealer_name,
wm_concat(p.paytype) as paytype
from dealer d
left join Dealer_Payment_type dp
on d.dealer_id = dp.dealer_id
left join payment_type p
on dp.payment_type_id = p.paymenttype_id
group by d.dealer_id, d.dealer_name;
To create the separate columns, then you can use an aggregate function with a CASE:
select dealer_id, dealer_name,
max(case when paytype = 'CHECK' then flag else 'false' end) "Check",
max(case when paytype = 'WIRE' then flag else 'false' end) "Wire",
max(case when paytype = 'CREDIT' then flag else 'false' end) "Credit"
from
(
select d.dealer_id,
d.dealer_name,
p.paytype,
'true' flag
from dealer d
left join Dealer_Payment_type dp
on d.dealer_id = dp.dealer_id
left join payment_type p
on dp.payment_type_id = p.paymenttype_id
)
group by dealer_id, dealer_name;
See SQL Fiddle with Demo