Order the join returning multiple rows - sql

In PostgreSQL 8.4 I have a join always returning 3 rows (they represent 3 players playing the game round rid) -
# SELECT r.rid, c2.pos, c2.money, c2.last_ip, c2.quit,
u.id, u.first_name
FROM pref_rounds r
JOIN pref_cards c1 USING (rid)
JOIN pref_cards c2 USING (rid)
JOIN pref_users u ON u.id = c2.id
WHERE c1.id = 'OK336197153288';
rid | pos | money | last_ip | quit | id | first_name
--------+-----+-------+-----------------+------+-----------------------+------------
165684 | 0 | 14 | 77.91.175.242 | f | OK336197153288 | Елена
165684 | 1 | -2 | 195.177.124.218 | f | OK3982469933 | Константин
165684 | 2 | -14 | 92.243.183.44 | f | MR438331292705069453 | Дмитрий
165711 | 2 | 10 | 77.91.175.242 | f | OK336197153288 | Елена
165711 | 0 | -2 | 195.177.124.218 | f | OK3982469933 | Константин
165711 | 1 | -6 | 92.243.183.44 | f | MR438331292705069453 | Дмитрий
165764 | 1 | 13 | 77.91.175.242 | f | OK336197153288 | Елена
165764 | 2 | -17 | 195.177.124.218 | f | OK3982469933 | Константин
165764 | 0 | 3 | 92.243.183.44 | f | MR438331292705069453 | Дмитрий
Unfortunately they are not sorted by the pos (the 2nd column which is a position at a playing table).
Is there please a way in SQL to sort the above sets of 3 rows so that they always have the pos: 0 1 2 then again 0 1 2?
Or do I have to do it in my PHP script?

Use the following sql query. It will give you the required result.
# SELECT r.rid, c2.pos, c2.money, c2.last_ip, c2.quit,
u.id, u.first_name
FROM pref_rounds r
JOIN pref_cards c1 USING (rid)
JOIN pref_cards c2 USING (rid)
JOIN pref_users u ON u.id = c2.id
WHERE c1.id = 'OK336197153288'
Order by r.rid asc, c2.pos asc
;

Maybe something like this:
SELECT r.rid, c2.pos, c2.money, c2.last_ip, c2.quit,
u.id, u.first_name
FROM pref_rounds r
JOIN pref_cards c1 USING (rid)
JOIN pref_cards c2 USING (rid)
JOIN pref_users u ON u.id = c2.id
WHERE c1.id = 'OK336197153288'
ORDER BY r.rid,c2.pos;
You first need to order by the r.rid then by c2.pos to get them order like you want

Related

Can someone help me figure out if I'm making a mistake in my query?

I'm trying to create a query that returns the names of all people in my database that have less than half of the money of the person with the most money.
These is my query:
select P1.name
from Persons P1 left join
AccountOf A1 on A1.person_id = P1.id left join
BankAccounts B1 on B1.id = A1.account_id
group by name
having SUM(B1.balance) < MAX((select SUM(B1.balance) as b
from AccountOf A1 left join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
LIMIT 1)) * 0.5
This is the result:
+-------+
| name |
+-------+
| Evert |
+-------+
I have the following tables in the database:
+---------+--------+--+
| Persons | | |
+---------+--------+--+
| id | name | |
| 11 | Evert | |
| 12 | Xavi | |
| 13 | Ludwig | |
| 14 | Ziggy | |
+---------+--------+--+
+--------------+---------+
| BankAccounts | |
+--------------+---------+
| id | balance |
| 11 | 525000 |
| 12 | 750000 |
| 13 | 1900000 |
| 14 | 1600000 |
+--------------+---------+
+-----------+-----------+------------+
| AccountOf | | |
+-----------+-----------+------------+
| id | person_id | account_id |
| 301 | 11 | 12 |
| 302 | 13 | 12 |
| 303 | 13 | 14 |
| 304 | 14 | 11 |
| 305 | 14 | 13 |
+-----------+-----------+------------+
What am I missing here? I should get two entries in the result (Evert, Xavi)
I wouldn't approach the logic this way (I would use window functions). But your final having has two levels of aggregation. That shouldn't work. You want:
having SUM(B1.balance) < (select 0.5 * SUM(B1.balance) as b
from AccountOf A1 join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
limit 1
)
I also moved the 0.5 into the subquery and changed the left join to a join -- the tables need to match to get balances.
I would recommend window functions, if your - undisclosed! - database supports them.
You can join and aggregate just once, and then use a window max() to get the top balance. All that is then left to is to filter in an outer query:
select *
fom (
select p.id, p.name, coalesce(sum(balance), 0) balance,
max(sum(balance)) over() max_balance
from persons p
left join accountof ao on ao.person_id = p.id
left join bankaccounts ba on ba.id = ao.account_id
group by p.id, p.name
) t
where balance > max_balance * 0.5

Can't show all records with the same id while join in oracle xe 11g

I'm getting this message while using this query, is there anything wrong?
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t, transaksi_detail td, operator o
WHERE td.transaksi_id = t.transaksi_id AND o.operator_id = t.operator_id
GROUP BY t.transaksi_id
Updated :
After using the answer from #Barbaros Özhan using this query :
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
INNER JOIN operator o ON ( o.operator_id = t.operator_id )
GROUP BY t.tanggal_transaksi, o.nama_lengkap;
the data is successfully displayed. but, there are few problems that occur, the value of the same operator_id cannot appear more than 1 time. Here is the sample data :
+--------------+-------------+-------------------+
| TRANSAKSI_ID | OPERATOR_ID | TANGGAL_TRANSAKSI |
+--------------+-------------+-------------------+
| 1 | 5 | 09/29/2018 |
| 2 | 3 | 09/29/2018 |
| 3 | 3 | 09/29/2018 |
| 4 | 1 | 09/29/2018 |
| 5 | 1 | 09/29/2018 |
+--------------+-------------+-------------------+
After use the query command, the output is :
+-------------------+------------------+--------+
| TANGGAL_TRANSAKSI | NAMA_LENGKAP | TOTAL |
+-------------------+------------------+--------+
| 09/29/2018 | Lina Harun | 419800 |
| 09/29/2018 | Titro Kusumo | 484000 |
| 09/29/2018 | Muhammad Kusnadi | 402000 |
+-------------------+------------------+--------+
When viewed from the operator table, there are 2 data with the same operator_id that is unreadable
+-------------+------------------+
| OPERATOR_ID | NAMA_LENGKAP |
+-------------+------------------+
| 1 | Muhammad Kusnadi |
| 3 | Lina Harun |
| 5 | Tirto Kusumo |
+-------------+------------------+
You need to include the columns in the SELECT-list t.tanggal_transaksi, o.nama_lengkap, also in the GROUP BY-list but not the others like t.transaksi_id. So, you might use the following without any issue :
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
INNER JOIN operator o ON ( o.operator_id = t.operator_id )
GROUP BY t.tanggal_transaksi, o.nama_lengkap;
Or this one :
SELECT t.transaksi_id, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
GROUP BY t.transaksi_id;
P.S. Prefer using ANSI-92 JOIN standard rather than old-style comma-type JOIN.

SQL - Join with multiple condition

I'm trying to join my users table with my jobs table based on a mapping table users_jobs:
Here is what the users table looks like:
users
|--------|------------------|
| id | name |
|--------|----------------- |
| 1 | Ozzy Osbourne |
| 2 | Lemmy Kilmister |
| 3 | Ronnie James Dio |
| 4 | Jimmy Page |
|---------------------------|
jobs table looks like this:
|--------|-----------------|
| id | title |
|--------|-----------------|
| 1 | Singer |
| 2 | Guitar Player |
|--------------------------|
And users_jobs table looks like this:
|--------|-------------|-------------|---------------|-------------|
| id | user_id | job_id | column3 | column4 |
|--------|-------------|-------------|---------------|-------------|
| 1 | 1 | 1 | 0 | 1 |
| 2 | 2 | 1 | 1 | 0 |
| 3 | 3 | 1 | 0 | 1 |
| 4 | 4 | 2 | 1 | 0 |
|----------------------|-------------|---------------|-------------|
For example, let's say the ozzy does a query.
Here is what should expect:
|--------|------------------|------------|--------- |
| id | name | column3 | column4 |
|--------|----------------- |------------|----------|
| 1 | Ozzy Osbourne | 0 | 1 |
| 2 | Lemmy Kilmister | 1 | 0 |
| 3 | Ronnie James Dio | 0 | 1 |
|---------------------------|------------|----------|
Basically, he can only see the job in which he is registered (role) and the users included.
I tried to do this:
SELECT u1.*, uj1.colum3, uj1.column4
FROM users AS u1
JOIN users_jobs AS uj1 ON uj1.user_id = 1
JOIN jobs AS j1 ON j1.id = up1.job_id
WHERE uj1.job_id = 1
Any help would be great!
Looks like you need INNER JOIN Try this :
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1;
If you need by certain user_id
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1
and u.id=1;
I found a solution.
Using #stackFan approach adding an EXISTS clause to make sure that the user is in.
SELECT u.id, u.column3 , u.column4
FROM users u
INNER JOIN user_jobs uj on u.id = uj.user_id
INNER JOIN jobs j on j.id = uj.job_id
WHERE uj.job_id = <job-ID>
AND
EXISTS (
SELECT *
FROM users_jobs AS uj
WHERE uj.job_id = <job-ID>
AND uj.user_id = <user-ID>
);
Try LEFT JOIN. It will display all users, whether they have job or not.
SELECT u.id, u.name, uj.colum3, uj.column4
FROM users AS u
LEFT JOIN users_jobs uj ON uj.user_id = u.id
LEFT JOIN jobs j ON j.id = uj.job_id

Reducing number of records in CTE table

In PostgreSQL 9.5.4 I keep player infos from various social networks:
# TABLE words_social;
sid | social | female | given | family | photo | place | stamp | uid
-------+--------+--------+---------+--------+-------+-------+------------+-----
aaaaa | 1 | 0 | Abcde1 | | | | 1470237061 | 1
aaaaa | 2 | 0 | Abcde2 | | | | 1477053188 | 1
aaaaa | 3 | 0 | Abcde3 | | | | 1477053330 | 1
kkkkk | 3 | 0 | Klmnop3 | | | | 1477053810 | 2
kkkkk | 4 | 0 | Klmnop4 | | | | 1477053857 | 2
ggggg | 2 | 0 | Ghijk2 | | | | 1477053456 | 3
ggggg | 3 | 0 | Ghijk3 | | | | 1477053645 | 3
ggggg | 4 | 0 | Ghijk4 | | | | 1477053670 | 3
xxxxx | 4 | 0 | Xyzok | | | | 1470237393 | 4
(9 rows)
The 1, 2, 3, 4 values in column social mean "Facebook", "Twitter", etc.
For a player I can always select her most recent info by:
# select * from words_social s1 WHERE stamp =
(SELECT max(stamp) FROM words_social s2 WHERE s1.uid = s2.uid);
sid | social | female | given | family | photo | place | stamp | uid
-------+--------+--------+---------+--------+-------+-------+------------+-----
aaaaa | 3 | 0 | Abcde3 | | | | 1477053330 | 1
kkkkk | 4 | 0 | Klmnop4 | | | | 1477053857 | 2
ggggg | 4 | 0 | Ghijk4 | | | | 1477053670 | 3
xxxxx | 4 | 0 | Xyzok | | | | 1470237393 | 4
(4 rows)
Then there is another table storing current games (I have omitted some columns below):
# select gid, created, finished, player1, player2 from words_games;
gid | created | finished | player1 | player2
-----+-------------------------------+----------+---------+---------
1 | 2016-10-21 14:51:12.624507+02 | | 4 | 1
2 | 2016-10-21 14:51:22.631507+02 | | 3 |
(2 rows)
Whenever a user (for example with uid 1) connects to the server, I send her the games she is taking part in:
# select gid, created, finished, player1, player2 from words_games where player1 = 1
union select gid, created, finished, player2, player1 from words_games where player2 = 1;
gid | created | finished | player1 | player2
-----+-------------------------------+----------+---------+---------
1 | 2016-10-21 14:51:12.624507+02 | | 4 | 1
(1 row)
My problem: to the above UNION SELECT statement I need to add user infos from words_social table - so that I can display user photos and names above the game board in my 2-player game.
So I try this with CTE (and add the i.given column with the user first name):
# with user_infos AS (select * from words_social s1 WHERE stamp =
(SELECT max(stamp) FROM words_social s2 WHERE s1.uid = s2.uid))
select g.gid, g.created, g.finished, g.player1, g.player2, i.given from words_games g join user_infos i on (g.player1=i.uid) where g.player1 = 1
union select g.gid, g.created, g.finished, g.player2, g.player1, i.given from words_games g join user_infos i on (g.player2=i.uid) where g.player2 = 1;
gid | created | finished | player1 | player2 | given
-----+-------------------------------+----------+---------+---------+--------
1 | 2016-10-21 14:51:12.624507+02 | | 1 | 4 | Abcde3
(1 row)
This works well, but I still have the following problem -
I am worried that the CTE-table user_infos will get very large, once my game has many players.
How to rewrite the query, so that user_infos only holds relevant records?
I can not just perform
# with user_infos AS (select * from words_social s1 WHERE stamp =
(SELECT max(stamp) FROM words_social s2 WHERE s1.uid = s2.uid))
AND s1.uid = 1
...
because I also need the infos (given and family names, photo) of the game opponents.
user_infos query can be rewritten and used as following:
with user_infos as (
select row_number() over (partition by uid order by stamp desc), * from words_social
)
select g.gid, g.created, g.finished, g.player1, g.player2, i.given from words_games g
join user_infos i on g.player1=i.uid and i.row_number = 1 and g.player1 = 1
union select g.gid, g.created, g.finished, g.player2, g.player1, i.given from words_games g
join user_infos i on g.player2=i.uid and i.row_number =1 and g.player2 = 1;
You should wrap it the other way.
Start from the word_games, then make your join with the words_social table.
Also you could use dinstinct on (postgres specific) function to avoid a second table lookup.
So in the end:
with game_finder as (
select g.gid, g.player1, g.player2
from words_games g where g.player1 = 1
union
select g.gid,g.player2, g.player1
from words_games g where g.player2 = 1),
player1_infos as (
select distinct on (uid)
gf.gid,
uid,
social,
given
from words_social ws
inner join game_finder gf on gf.player1 = ws.uid
ORDER BY uid, stamp DESC
),
player2_infos as (
select gf.gid,
uid,
social,
given
from words_social ws
inner join game_finder gf on gf.player2 = ws.uid
ORDER BY uid, stamp DESC
)
select *
from game_finder gf
left outer join player1_infos p1 on gf.gid = p1.gid
left outer join player2_infos p2 on gf.gid = p2.gid;

Counting rows in multiple tables

I have a mysql database that is tracking hockey stats. What I'd like to do is in one query get the number of goals and assists scored by each player as well as the number of games that they've played in. I'm using Zend Framework and the query that I've build is this:
SELECT `p`.*,
`pxt`.`jersey_number`,
count(pxg.player_x_game_id) AS `games`,
count(goals.scoring_id) AS `goals`,
count(assists.scoring_id) AS `assists`
FROM `players` AS `p`
INNER JOIN `players_x_teams` AS `pxt` ON p.player_id = pxt.player_id
INNER JOIN `teams_x_seasons` AS `txs` ON pxt.team_id = txs.team_id
INNER JOIN `seasons` AS `s` ON txs.season_id = s.season_id
INNER JOIN `games` AS `g` ON g.season_id = s.season_id
INNER JOIN `players_x_games` AS `pxg` ON pxg.game_id = g.game_id
AND pxg.player_id = p.player_id
LEFT JOIN `scoring` AS `goals` ON goals.game_id = g.game_id
AND goals.scorer_id = p.player_id
LEFT JOIN `scoring` AS `assists` ON assists.game_id = g.game_id
AND (assists.assist1_id = p.player_id OR assists.assist2_id = p.player_id)
WHERE (pxt.team_id = 1)
AND (txs.season_id = '23')
AND (pxt.date_added <= s.end_date OR pxt.date_added is null)
AND (pxt.date_removed >= s.start_date OR pxt.date_removed is null)
GROUP BY `p`.`player_id`
This query returns me data, but my counts are off.
+-----------+---------------+-------+-------+---------+
| player_id | jersey_number | games | goals | assists |
+-----------+---------------+-------+-------+---------+
| 2 | 3 | 7 | 1 | 3 |
| 3 | 19 | 6 | 1 | 0 |
| 8 | 8 | 7 | 3 | 2 |
| 9 | 11 | 13 | 10 | 8 |
| 11 | 96 | 6 | 1 | 3 |
| 12 | 14 | 6 | 0 | 3 |
| 13 | 7 | 6 | 0 | 1 |
| 115 | 39 | 9 | 6 | 2 |
| 142 | 68 | 6 | 0 | 1 |
| 143 | 30 | 6 | 0 | 0 |
| 150 | 41 | 11 | 11 | 5 |
| 185 | 17 | 6 | 6 | 3 |
| 225 | 97 | 4 | 1 | 3 |
+-----------+---------------+-------+-------+---------+
In this dataset the most games that should be present are 6, but as you can see I'm getting extras. If I adjust my query to remove the goals and assists fields my games count comes out correct. In fact if I only select one of my counted rows I always get the correct counts, but once I add a second or third count my numbers start to get skewed. What am I doing wrong?
Since you are doing multiple joins which may each match multiple rows and carry over to the next join, you'll need to add distinct in your count. Try this:
SELECT `p`.*,
`pxt`.`jersey_number`,
count(distinct pxg.player_x_game_id) AS `games`,
count(distinct goals.scoring_id) AS `goals`,
count(distinct assists.scoring_id) AS `assists`
FROM `players` AS `p`
INNER JOIN `players_x_teams` AS `pxt` ON p.player_id = pxt.player_id
INNER JOIN `teams_x_seasons` AS `txs` ON pxt.team_id = txs.team_id
INNER JOIN `seasons` AS `s` ON txs.season_id = s.season_id
INNER JOIN `games` AS `g` ON g.season_id = s.season_id
INNER JOIN `players_x_games` AS `pxg` ON pxg.game_id = g.game_id
AND pxg.player_id = p.player_id
LEFT JOIN `scoring` AS `goals` ON goals.game_id = g.game_id
AND goals.scorer_id = p.player_id
LEFT JOIN `scoring` AS `assists` ON assists.game_id = g.game_id
AND (assists.assist1_id = p.player_id OR assists.assist2_id = p.player_id)
WHERE (pxt.team_id = 1)
AND (txs.season_id = '23')
AND (pxt.date_added <= s.end_date OR pxt.date_added is null)
AND (pxt.date_removed >= s.start_date OR pxt.date_removed is null)
GROUP BY `p`.`player_id`
Maybe you need count(DISTINCT pxg.player_x_game_id)...? Looks like there might be duplicates in that humungous megajoin (which I admit I haven't actually taken time to fully reproduce!-)...