PSQL - Group by with sum and a random value from the group - postgresql

I have this table
type | child_id | status
-----------------------------------
1 | a | completed
1 | b | failed
1 | a | completed
1 | c | completed
-----------------------------------
2 | a | failed
2 | b | failed
I want to group the values by type, get the count of different statuses, and also get any value from the child_id, to get such a result.
type | child_id | completed | failed
-----------------------------------------
1 | b | 3 | 1
2 | a | 0 | 2
For type 1, child_id a, b or c is acceptable; and a or b for type 2.
I can use this to all the columns except child_id
SELECT type,
sum(case when status = 'completed' then 1 else 0 end) as completed,
sum(case when status = 'failed' then 1 else 0 end) as failed
from mytable
group by type;
Just need to figure out if there is a way to get the child_id in there as well, without doing a join.

Related

Get dummy columns from different tables

I have three different tables that look like that:
Table 1
| id | city|
|----|-----|
| 1 | A |
| 1 | B |
| 2 | C |
Table 2
| id | city|
|----|-----|
| 2 | B |
| 1 | B |
| 3 | C |
Table 3
| id | city|
|----|-----|
| 1 | A |
| 1 | B |
| 2 | A |
I need to create one column for each table, and the dummies values if it's present.
| id | city| is_tbl_1 | is_tbl_2 | is_tbl_3 |
|----|-----|-----------|-------------|------------|
| 1 | A | 1 | 0 | 1 |
| 1 | B | 1 | 1 | 1 |
| 2 | A | 0 | 0 | 1 |
| 2 | C | 1 | 0 | 0 |
| 2 | B | 0 | 1 | 0 |
| 3 | C | 0 | 1 | 0 |
I have tried to add the columns is_tbl# myself on three different selects, UNION all the three tables and group, but it looks ugly, is there a better way to do it?
You can outer-join the 3 tables on id and city, then group by the id and city, and finally count the number of non-null values of the city columns :
SELECT
COALESCE (t1.id, t2.id, t3.id) AS id
, COALESCE (t1.city, t2.city, t3.city) AS city
, count(*) FILTER (WHERE t1.city IS NOT NULL) AS is_tbl_1
, count(*) FILTER (WHERE t2.city IS NOT NULL) AS is_tbl_2
, count(*) FILTER (WHERE t3.city IS NOT NULL) AS is_tbl_3
FROM
t1 AS t1
FULL OUTER JOIN
t2 AS t2 ON t1.id = t2.id AND t1.city = t2.city
FULL OUTER JOIN
t3 AS t3 ON t1.id = t3.id AND t1.city = t3.city
GROUP BY
1,2
ORDER BY
1,2

Calculate won, tie and lost games in postgresql

I have two tables "matches" and "opponents".
Matches
id | date
---+------------
1 | 2016-03-21 21:00:00
2 | 2016-03-22 09:00:00
...
Opponents
(score is null if not played)
id | match_id | team_id | score
---+----------+---------+------------
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 2 | 3 | 1
4 | 2 | 4 | 1
4 | 3 | 1 |
4 | 3 | 2 |
....
The goal is to create the following table
Team | won | tie | lost | total
-----+-----+-----+------+----------
2 | 1 | 0 | 0 | 1
3 | 0 | 1 | 0 | 1
4 | 0 | 1 | 0 | 1
1 | 0 | 0 | 1 | 1
Postgres v9.5
How do I do this? (Im open to maybe moving the "score" to somewhere else in my model if it makes sense.)
Divide et impera my son
with teams as (
select distinct team_id from opponents
),
teamgames as (
select t.team_id, o.match_id, o.score as team_score, oo.score as opponent_score
from teams t
join opponents o on t.team_id = o.team_id
join opponents oo on (oo.match_id = o.match_id and oo.id != o.id)
),
rankgames as (
select
team_id,
case
when team_score > opponent_score then 1
else 0
end as win,
case
when team_score = opponent_score then 1
else 0
end as tie,
case
when team_score < opponent_score then 1
else 0
end as loss
from teamgames
),
rank as (
select
team_id, sum(win) as win, sum(tie) as tie, sum(loss) as loss,
sum( win * 3 + tie * 1 ) as score
from rankgames
group by team_id
order by score desc
)
select * from rank;
Note1: You probably don't need the first "with" as you probably have already a table with one record per team
Note2: i think you can also achieve the same result with one single query, but in this way steps are clearer

PostgreSQL Query to get pivot result

I have a log table look like this
rpt_id | shipping_id | shop_id | status | create_time
-------------------------------------------------------------
1 | 1 | 600 | 1 | 2013-12-01 01:06:50
2 | 1 | 600 | 0 | 2013-12-01 01:06:55
3 | 1 | 600 | 1 | 2013-12-02 10:00:30
4 | 2 | 600 | 1 | 2013-12-02 10:00:30
5 | 1 | 601 | 1 | 2013-12-02 11:20:10
6 | 2 | 601 | 1 | 2013-12-02 11:20:10
7 | 1 | 601 | 0 | 2013-12-03 09:10:10
8 | 3 | 602 | 1 | 2013-12-03 13:15:58
And I want to use single query to make it look like this
shipping_id | total_activate | total_deactivate
-----------------------------------------------
1 | 2 | 2
2 | 2 | 0
3 | 1 | 0
How should I query this?
Note:
Status = 1 = Activate
Status = 0 = Deactivate
Count total activate / deactivate rule: look at log table above. rpt_id 1 & 3, it has same shop_id, shipping_id and status. It should only count as one. See the result table. Shipping id 1 is only activated by 2 shops, they are shop_id 600 and 601.
Can you guys advice me how to make the query? thanks for the help:D
Try this:
select shipping_id,
sum(case when status=1 then 1 else 0 end) as total_activate,
sum(case when status=0 then 1 else 0 end) as total_deactivate
from (select distinct shipping_id,
shop_id,
status
from test) a
group by shipping_id
order by shipping_id
See it here at fiddle: http://sqlfiddle.com/#!15/f15fd/4
I did not put the date on the query as it is not important for the result.
Yes thanks... I also figured it out already, you can do it this way too.... thx
SELECT
shipping_id,
COUNT(DISTINCT CASE WHEN status = 1 THEN shop_id END) AS total_activate,
COUNT(DISTINCT CASE WHEN status = 0 THEN shop_id END) AS total_deactivate
FROM
test
GROUP BY
shipping_id
ORDER BY
shipping_id

Ranking rows according some number

I have table like this
id | name
----------
1 | A
2 | B
5 | C
100 | D
200 | E
201 | F
202 | G
I need ranking rows from 1 to 3 order by id, that is, I need result:
id | name | ranking
---------------------------
1 | A | 1
2 | B | 2
5 | C | 3
100 | D | 1
200 | E | 2
201 | F | 3
202 | G | 1
How to make this?
P.S.
I am trying:
SELECT id, name, row_number() OVER( order by id RANGE BETWEEN 1 AND 3 ) AS ranking FROM t
This gives syntax error.
RANGE is actually used for something else:
http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS
http://www.postgresql.org/docs/current/static/sql-select.html
Try using a modulus instead:
SELECT id, name, 1 + (row_number() OVER( order by id ) - 1) % 3 AS ranking
FROM t

count() corresponding to max() of different values satisfying some condition

I have the following tables:
user_group
usergrp_id bigint Primary Key
usergrp_name text
user
user_id bigint Primary Key
user_name text
user_usergrp_id bigint
user_loc_id bigint
user_usergrp_id has its corresponding id from the user_group table
user_loc_id has its corresponding id(branch_id) from the branch table.
branch
branch_id bigint Primary Key
branch_name text
branch_type smallint
branch_type By default is set as 1. Although it may contain any value in between 1 and 4.
user_projects
proj_id bigint Primary Key
proj_name text
proj_branch_id smallint
proj_branch_id has its corresponding id(branch_id) from the branch table.
user_approval
appr_id bigint Primary Key
appr_prjt_id bigint
appr_status smallint
appr_approval_by bigint
appr_approval_by has its corresponding id(user_id) from the user table
appr_status may contain different status values like 10,20,30... for a single appr_prjt_id
user_group
usergrp_id | usergrp_name
-------------------------
1 | Admin
2 | Manager
user
user_id | user_name | user_usergrp_id |user_loc_id
---------------------------------------------------
1 | John | 1 | 1
2 | Harry | 2 | 1
branch
branch_id | branch_name | branch_type
-------------------------------------
1 | location1 | 2
2 | location2 | 1
3 | location3 | 4
4 | location4 | 2
5 | location4 | 2
user_projects
proj_id | proj_name | proj_branch_id
------------------------------------
1 | test1 | 1
2 | test2 | 2
3 | test3 | 1
4 | test4 | 3
5 | test5 | 1
6 | test5 | 4
user_approval
appr_id | appr_prjt_id | appr_status | appr_approval_by
-------------------------------------------------------
1 | 1 | 10 | 1
2 | 1 | 20 | 1
3 | 1 | 30 | 1
4 | 2 | 10 | 2
5 | 3 | 10 | 1
6 | 3 | 20 | 2
7 | 4 | 10 | 1
8 | 4 | 20 | 1
Condition: The output must take the MAX() value of appr_status for each appr_prjt_id and count it.
I.e., in the above table appr_prjt_id=1 has 3 different status: 10, 20, 30. Its count must only be shown for status corresponding to 30 in the output (not in the statuses 10 and 20), corresponding to a user group in a particular branch_name. Similarly for each of the other id's in the field appr_prjt_id
SQL Fiddle
Desired Output:
10 | 20 | 30
------> Admin 0 | 1 | 1
|
location1
|
------> Manager 1 | 1 | 0
How can I do that?
SQL Fiddle
SQL Fiddle
select
branch_name, usergrp_name,
sum((appr_status = 10)::integer) "10",
sum((appr_status = 20)::integer) "20",
sum((appr_status = 30)::integer) "30"
from
(
select distinct on (appr_prjt_id)
appr_prjt_id, appr_approval_by, appr_status
from user_approval
order by 1, 3 desc
) ua
inner join
users u on ua.appr_approval_by = u.user_id
inner join
user_group ug on u.user_usergrp_id = ug.usergrp_id
inner join
branch b on u.user_loc_id = b.branch_id
group by branch_name, usergrp_name
order by usergrp_name
The classic solution, that works in most DBMSs is to use a case:
select
branch_name, usergrp_name,
sum(case appr_status when 10 then 1 else 0 end) "10",
But Postgresql has the boolean type and it has a cast to integer (boolean::integer) resulting in 0 or 1 which makes for less verbose code.
In this case it is also possible to do a count in instead of a sum:
select
branch_name, usergrp_name,
count(appr_status = 10 or null) "10",
I indeed prefer the count but I have the impression that it is harder to understand. The trick is to know that count counts anything not null and that a (true or null) is true and a (false or null) is null so it will count whenever the condition is true.