How to find the number of matches a player has played? - postgresql

I have two tables namely match and player. I am trying to find the total number of matches played by each player by adding no_of_wins and no_of_loses columns.
player:
id | name
----|----
1 | Suhas
2 | Srivats
3 | James
4 | Watson
match:
id | winner | loser
----|--------|-------
1 | 1 | 2
2 | 1 | 3
3 | 1 | 4
4 | 2 | 4
5 | 4 | 3
6 | 3 | 2
I tried the following SQL command:
select p.id, p.name, count(m.winner) as no_of_wins,count(m.loser) as no_of_loses from player as p left join match as m on p.id=m.winner group by p.id order by p.id;
This command shows the wrong output for the number of loses.
id | name | no_of_wins | no_of_loses
----|---------|------------|-------------
1 | Suhas | 3 | 3
2 | Srivats | 1 | 1
3 | James | 1 | 1
4 | Watson | 1 | 1
Kindly help.

Calculate aggregated numbers of wins and loses for a player in two queries and (full) join them by a player id:
select
name,
coalesce(wins, 0) as no_of_wins,
coalesce(loses, 0) as no_of_loses,
coalesce(wins, 0) + coalesce(loses, 0) as total
from (
select winner as id, count(*) as wins
from match
group by 1
) w
full join (
select loser as id, count(*) as loses
from match
group by 1
) l using (id)
full join player using(id)
order by id;
name | no_of_wins | no_of_loses | total
---------+------------+-------------+-------
Suhas | 3 | 0 | 3
Srivats | 1 | 2 | 3
James | 1 | 2 | 3
Watson | 1 | 2 | 3
(4 rows)

Your query will cause an error because you didn't add p.name to the GROUP BY clause.
You'll have to join match twice, because these are two independent joins:
SELECT p.id,
p.name,
COALESCE(w.wins, 0) no_of_wins,
COALESCE(l.losses, 0) no_of_losses
FROM player p
LEFT JOIN
(SELECT winner id,
count(*) wins
FROM match
GROUP BY winner
) w
USING (id)
LEFT JOIN
(SELECT loser id,
count(*) losses
FROM match
GROUP BY loser
) l
USING (id);

Related

Reverse column values after grouping

I have a table as follows
id | group | value
------+-------------+----------
1 | 1 | 2
2 | 1 | 4
3 | 1 | 3
4 | 2 | 2
5 | 2 | 9
6 | 2 | 5
I want to group the rows by 'group' with the order of 'id' and create a new column that reverses the 'value' column as follows
id | group | value | reversedvalue
------+-------------+---------+---------
1 | 1 | 2 | 3
2 | 1 | 4 | 4
3 | 1 | 3 | 2
4 | 2 | 2 | 5
5 | 2 | 9 | 9
6 | 2 | 5 | 2
Try the following:
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n
FROM your_table
) q1
JOIN
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n
FROM your_table
) q2
ON q1.group_id=q2.group_id AND q1.n=q2.n
You also can use CTE:
WITH cte AS(
SELECT *,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n1,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n2
FROM your_table
)
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
cte q1
JOIN
cte q2
ON q1.group_id=q2.group_id AND q1.n1=q2.n2;

Select row by id and it's nearest rows sorted by some value. PostgreSQL

I have chapters table like this:
id | title | sort_number | book_id
1 | 'Chap 1' | 3 | 1
5 | 'Chap 2' | 6 | 1
8 | 'About ' | 1 | 1
9 | 'Chap 3' | 9 | 1
10 | 'Attack' | 1 | 2
Id is unique, sort_number is unique for same book(book_id)
1)How can load all data (3 rows) for 3 chapters (current, next and prev) sorted by sort_number if i have only current chapter id?
2)How can i load current chapter data (1 row) and only id's of next, prev if they exist?
This can be done using window functions
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter,
lead(id) over w as next_chapter
from chapters
window w as (partition by book_id order by sort_number);
With your sample data that returns:
id | title | sort_number | book_id | prev_chapter | next_chapter
---+--------+-------------+---------+--------------+-------------
8 | About | 1 | 1 | | 1
1 | Chap 1 | 3 | 1 | 8 | 5
5 | Chap 2 | 6 | 1 | 1 | 9
9 | Chap 3 | 9 | 1 | 5 |
10 | Attack | 1 | 2 | |
The above query can now be used to answer both your questions:
1)
select id, title, sort_number, book_id
from (
select id, title, sort_number, book_id,
--first_value(id) over w as first_chapter,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where 1 in (id, prev_chapter_id, next_chapter_id)
2)
select *
from (
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where id = 1

Postgresql get total matches by player

I've got the following Postgres query:
SELECT p_id as player_id, name as player_name
FROM Players
LEFT OUTER JOIN matches
ON Players.p_id = matches.player1 or Players.p_id = matches.player2
;
and it returns the following
player_id | player_name
-----------+-------------------
1 | Twilight Sparkle
1 | Twilight Sparkle
2 | Fluttershy
3 | Applejack
3 | Applejack
4 | Pinkie Pie
5 | "Rarity
5 | "Rarity
6 | Rainbow Dash
7 | Princess Celestia
7 | Princess Celestia
8 | Princess Luna
How can I end up with a table of unique p_id's with each one's name and the total of rows that p_id is in?
player_id | player_name | total_matches
-----------+-------------------+------
1 | Twilight Sparkle | 2
2 | Fluttershy | 1
3 | Applejack | 1
4 | Pinkie Pie | 1
5 | "Rarity | 2
6 | Rainbow Dash | 1
7 | Princess Celestia | 2
8 | Princess Luna | 1
You can achieve it using the group clause:
SELECT p_id as player_id, name as player_name, count(*) as total_matches
FROM Players
LEFT OUTER JOIN matches
ON Players.p_id = matches.player1 or Players.p_id = matches.player2
GROUP BY name
;

Join tables and count instances of different values

user
---------------------------
| ID | Name |
---------------------------
| 1 | Jim Rice |
| 2 | Wade Boggs |
| 3 | Bill Buckner |
---------------------------
at_bats
----------------------
| ID | User | Bases |
----------------------
| 1 | 1 | 2 |
| 2 | 2 | 1 |
| 3 | 1 | 2 |
| 4 | 3 | 0 |
| 5 | 1 | 3 |
----------------------
What I want my query to do is get the count of the different base values in a join table like:
count_of_hits
---------------------
| ID | 1B | 2B | 3B |
---------------------
| 1 | 0 | 2 | 1 |
| 2 | 1 | 0 | 0 |
| 3 | 0 | 0 | 0 |
---------------------
I had a query where I was able to get the bases individually, but not them all unless I did some complicated Joins and I'd imagine there is a better way. This was the foundational query though:
SELECT id, COUNT(ab.*)
FROM user
LEFT OUTER JOIN (SELECT * FROM at_bats WHERE at_bats.bases=2) ab ON ab.user=user.id
PostgreSQL 9.4+ provides a much cleaner way to do this:
SELECT
users,
count(*) FILTER (WHERE bases=1) As B1,
count(*) FILTER (WHERE bases=2) As B2,
count(*) FILTER (WHERE bases=3) As B3,
FROM at_bats
GROUP BY users
ORDER BY users;
I think the following query would solve your problem. However, I am not sure if it is the best approach:
select distinct a.users, coalesce(b.B1, 0) As B1, coalesce(c.B2, 0) As B2 ,coalesce(d.B3, 0) As B3
FROM at_bats a
LEFT JOIN (SELECT users, count(bases) As B1 FROM at_bats WHERE bases = 1 GROUP BY users) as b ON a.users=b.users
LEFT JOIN (SELECT users, count(bases) As B2 FROM at_bats WHERE bases = 2 GROUP BY users) as c ON a.users=c.users
LEFT JOIN (SELECT users, count(bases) As B3 FROM at_bats WHERE bases = 3 GROUP BY users) as d ON a.users=d.users
Order by users
the coalesce() function is just to replace the nulls with zeros. I hope this query helps you :D
UPDATE 1
I found a better way to do it, look to the following:
SELECT users,
count(case bases when 1 then 1 else null end) As B1,
count(case bases when 2 then 1 else null end) As B2,
count(case bases when 3 then 1 else null end) As B3
FROM at_bats
GROUP BY users
ORDER BY users;
It it is more efficient compared to my first query. You can check the performance by using EXPLAIN ANALYSE before the query.
Thanks to Guffa from this post: https://stackoverflow.com/a/1400115/4453190

Postgresql : Filtering duplicate pair

I am asking this from mobile, so apologies for bad formatting. For the following table.
Table players
| ID | name |matches_won|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
| 1 | bob | 3 |
| 2 | Paul | 2 |
| 3 | John | 4 |
| 4 | Jim | 1 |
| 5 | hal | 0 |
| 6 | fin | 0 |
I want to pair two players together in a query. Who have a similar or near similar the number of matches won. So the query should display the following result.
| ID | NAME | ID | NAME |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
| 3 | John | 1 | bob |
| 2 | paul | 4 | Jim |
| 5 | hal | 6 | fin |
Until now I have tried this query. But it gives repeat pairs.
Select player1.ID,player1.name,player2.ID,player2.name
From player as player1,
player as player2
Where
player1.matches_won >= player2.matches_won
And player1.ID ! = player2.ID;
The query will pair the player with the most won matches with everyone of the other players. While I only want one player to appear only once in the result. With the player who is nearest to his wins.
I have tried sub queries. But I don't know how to go about it, since it only returns one result. Also aggregates don't work in the where clause. So I am not sure how to achieve this.
An easier way, IMHO, to achieve this would be to order the players by their number of wins, divide these ranks by two to create matches and self join. CTEs (with expressions) allow you to do this relatively elegantly:
WITH wins AS (
SELECT id, name, ROW_NUMNBER() OVER (ORDER BY matches_won DESC) AS rn
FROM players
)
SELECT w1.id, w1.name, w2.id, w2.name
FROM (SELECT id, name, rn / 2 AS rn
FROM wins
WHERE rn % 2 = 1) w1
LEFT JOIN (SELECT id, name, (rn - 1) / 2 AS rn
FROM wins
WHERE rn % 2 = 0) w2 ON w1.rn = w2.rn
Add row numbers in descending order by won matches to the table and join odd row numbers with adjacent even row numbers:
with players as (
select *, row_number() over (order by matches_won desc) rn
from player)
select a.id, a.name, b.id, b.name
from players a
join players b
on a.rn = b.rn- 1
where a.rn % 2 = 1
id | name | id | name
----+------+----+------
3 | John | 1 | bob
2 | Paul | 4 | Jim
5 | hal | 6 | fin
(3 rows)