Generate list of matches starting from players list - sql

I have the below scenario:
Input data:
Table t1:
+-------------+
| Teams |
+-------------+
| India |
| Australia |
| England |
| Italy |
+-------------+
Required output:
+-------------+------------+
| Team1 | Team2 |
+-------------+------------+
| India | Australia |
| India | England |
| India | Italy |
| Australia | England |
| Australia | Italy |
| England | Italy |
+-------------+------------+
i.e. the countries (column Team1) who are playing against which country (column Team2).
I tried using full outer join but wasn't able to get distinct values. Can we achieve this through a single sql query?

Do a "half" join on teams not being equal:
select a.team, b.team
from teams a
join teams b on a.team < b.team
See live demo on SQLFiddle.
The use of a.team < b.team rather than a.team != b.team returns only combinations rather than permutations - you get only one side of each join, giving you only distinct combinations.

Related

Postregsql transform one retrieved record into two records based on columns value

I have two Postgresql tables:
Searches:
+----+------------+-----------+-----------+
| id | patient_id | status_a | status_b |
+----+------------+-----------+-----------+
| 1 | 1 | Added | Added |
+----+------------+-----------+-----------+
| 2 | 2 | Added | NULL |
+----+------------+-----------+-----------+
Patients:
+----+------+---------+
| id | name | country |
+----+------+---------+
| 1 | John | England |
+----+------+---------+
| 2 | Tim | France |
+----+------+---------+
I would like to retrieve "all the patients that have the status_a OR the status_b set to 'Added', creating a different record depending on the value of the status columns".
I need to be able to achieve this:
+------------+------+---------+-----------+-----------+
| patient_id | name | country | status_a | status_b |
+------------+------+---------+-----------+-----------+
| 1 | John | England | Added | NULL |
+------------+------+---------+-----------+-----------+
| 1 | John | England | Null | Added |
+------------+------+---------+-----------+-----------+
| 2 | Tim | France | Added | NULL |
+------------+------+---------+-----------+-----------+
Or, even better:
+------------+------+---------+-----------+
| patient_id | name | country | status |
+------------+------+---------+-----------+
| 1 | John | England | A |
+------------+------+---------+-----------+
| 1 | John | England | B |
+------------+------+---------+-----------+
| 2 | Tim | France | A |
+------------+------+---------+-----------+
Any solutions?
You need to pivot the searches table into rows using union.
select p.id as patient_id, p.name, p.country, s.status
from patients p
join (select patient_id, 'A' as status
from searches
where status_a is not null
union
select patient_id, 'B' as status
from searches
where status_b is not null) as s
on s.patient_id = p.id
order by p.id, s.status;
I would recommend a lateral join:
select s.patient_id, p.name, p.country, v.status_a, v.status_b
from searches s cross join lateral
(values (status_a, null),
(null, status_b)
) v(status_a, status_b) join
patients p
on p.id = s.patient_id
where v.status_a is not null or v.status_b is not null;

SQL Query - Iterate based on condition

I have a table of Countries.
I want to write a query to schedule a match between every country and no match should be repeated.I do not want to use for loops.
With a self cross join:
select
t1.name TeamA,
t2.name TeamB
from tablename t1 cross join tablename t2
where t1.name < t2.name
order by t1.name, t2.name
See the demo.
Results:
| TeamA | TeamB |
| ---------- | -------- |
| Bangladesh | China |
| Bangladesh | India |
| Bangladesh | Japan |
| Bangladesh | Pakistan |
| China | India |
| China | Japan |
| China | Pakistan |
| India | Japan |
| India | Pakistan |
| Japan | Pakistan |
What you need is a Cross Join:
SELECT a.name, b.name
FROM test a
CROSS JOIN test b
where a.name < b.name
Here is a demo: https://www.db-fiddle.com/f/8zBxQ3w7N5cmHBJ4PDDV6V/1

Choosing or filtering columns based on ranks of multiple fields in PostgreSQL

In one of the tables, I have multiple fields with a rank field against them. All these fields have a common grouping attribute against which I need to find the best ranked column value which can exist in any of the records of the group. For example, let's consider the data below:
+---------+---------------+-----------+-----------------+-------------+----------------------+------------+
| Country | City | City_Rank | Artist | Artist_Rank | Movie | Movie_Rank |
+---------+---------------+-----------+-----------------+-------------+----------------------+------------+
| USA | Las Vegas | 2 | Louis C.K | 2 | Justice League | 3 |
| USA | New York City | 3 | Michael Flynn | 3 | IT | 1 |
| USA | Los Angeles | 1 | Matt Lauer | 1 | Get Out | 2 |
| UK | Leeds | 2 | Jack Maynard | 3 | Beauty and the Beast | 2 |
| UK | Manchester | 3 | Charlie Gard | 1 | Wonder Woman | 1 |
| UK | London | 1 | Shannon Mathews | 2 | Logan | 3 |
+---------+---------------+-----------+-----------------+-------------+----------------------+------------+
Now I need the Rank 1 of City, Artist and Movie Grouped by the Country in the single record. So the expected output is:
+---------+------------------+--------------------+-------------------+
| Country | Best_Ranked_City | Best_Ranked_Artist | Best_Ranked_Movie |
+---------+------------------+--------------------+-------------------+
| USA | Los Angeles | Matt Lauer | IT |
| UK | London | Charlie Gard | Wonder Woman |
+---------+------------------+--------------------+-------------------+
I have many more attributes against which I have the rank field. I can arrive at the desired output by forming multiple datasets of the above with a filtering condition for each ranked field (where rank=1) and then joining these datasets by the group field.
However, this is quite a costly affair due to millions of records in the table, and filtering and joining this dataset multiple times doesn't seem to be the best way to solve this. I have arrived at the ranks for each field using a Rank() windows function by applying some business logic over it.
I would wish further to solve this problem using Window function only if possible.
I have arrived at the ranks for each field using a Rank() windows
function by applying some business logic over it.
I guess that there is some query which calculates ranks and then does a pivot operation in order to generate a summary table shown in the question.
It would be good to eliminate the pivot operation so that the input data geneerated by this query would look something like this:
| country | category | cat_value | rank_value |
|---------|----------|----------------------|------------|
| UK | Artist | Jack Maynard | 3 |
| UK | Artist | Shannon Mathews | 2 |
| UK | Artist | Charlie Gard | 1 |
| UK | City | Leeds | 2 |
| UK | City | Manchester | 3 |
| UK | City | London | 1 |
| UK | Movie | Logan | 3 |
| UK | Movie | Beauty and the Beast | 2 |
| UK | Movie | Wonder Woman | 1 |
| USA | Artist | Louis C.K | 2 |
| USA | Artist | Michael Flynn | 3 |
| USA | Artist | Matt Lauer | 1 |
| USA | City | Las Vegas | 2 |
| USA | City | Los Angeles | 1 |
| USA | City | New York City | 3 |
| USA | Movie | Justice League | 3 |
| USA | Movie | IT | 1 |
| USA | Movie | Get Out | 2 |
If this is not possible, then this resultset can be unpivoted using:
SELECT Country, 'City' as category, City as cat_value, City_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Artist' as category, Artist as cat_value, Artist_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Movie' as category, Movie as cat_value, Movie_Rank as rank_value
FROM Table1
If you unpivot this table, then picking items with rank=1 is very easy, just do:
SELECT * FROM unpivot_table WHERE rank_value = 1
and then another pivot can be done on it's results.
The final query may look like this (live demo: http://sqlfiddle.com/#!17/05e53/5)
With unpivot_me As (
SELECT Country, 'City' as category, City as cat_value, City_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Artist' as category, Artist as cat_value, Artist_Rank as rank_value
FROM Table1
UNION ALL
SELECT Country, 'Movie' as category, Movie as cat_value, Movie_Rank as rank_value
FROM Table1
)
SELECT Country,
Max( case when category = 'City' Then cat_value End) As Best_Ranked_City,
Max( case when category = 'Artist' Then cat_value End) As Best_Ranked_Artist,
Max( case when category = 'Movie' Then cat_value End) As Best_Ranked_Movie
FROM unpivot_me
WHERE rank_value = 1
GROUP BY Country
| country | best_ranked_city | best_ranked_artist | best_ranked_movie |
|---------|------------------|--------------------|-------------------|
| UK | London | Charlie Gard | Wonder Woman |
| USA | Los Angeles | Matt Lauer | IT |
Used the window function max() and within it placed a case condition where the ranks are 1 partitioned by country. This fetched first rank values for the desired columns against all the countries. Later filtered it using one of the ranked field with value 1 (could have filtered using any of the available rank field). Here is the SQL : http://sqlfiddle.com/#!17/05e53/18
With T1 as (
select Country, max(case when City_Rank =1 then City else '' end)
over (partition by Country) as Best_Ranked_City, City_Rank,
max(case when Artist_Rank =1 then Artist else '' end)
over (partition by Country) as Best_Ranked_Artist, max(case when
Movie_Rank =1 then Movie else '' end)
over (partition by Country) as Best_Ranked_Movie
from Table1
)
select Country, Best_Ranked_City, Best_Ranked_Artist, Best_Ranked_Movie
from T1 where city_rank=1;

SQL: calculate sum from IDs and 2 (or more) different tables

I think an example is worth a thousand words.
Table: CONTINENTS:
id | continent
---+----------
1 | Africa
2 | America
3 | Asia
4 | Europe
5 | Oceania
Table: COUNTRIES
id | countries | population | continent_id
---+----------------+------------+-------------
1 | Australia | 24500000 | 5
2 | Brazil | 209300000 | 2
3 | Canada | 36600000 | 2
4 | France | 65000000 | 4
5 | Germany | 82100000 | 4
6 | Italy | 59400000 | 4
7 | Japan | 127500000 | 3
8 | South Africa | 56700000 | 1
9 | South Korea | 51000000 | 3
10 | United Kingdom | 66200000 | 4
11 | United States | 324500000 | 2
And the result I'd like:
id | continent | continent_population
---+-----------+---------------------
1 | Africa | 56700000
2 | America | 570400000
3 | Asia | 178500000
4 | Europe | 272700000
5 | Oceania | 24500000
So yes, I'd like the population of each continent to be the result of the sum of countries, which are in another table. I've tried a lot of queries but nothing worked.
Also, this title probably sucks but I wasn't sure how to word it.
Try this, JOINing the two tables continent_id to id, then summing the population, and grouping by the continent:
SELECT
CONTINENTS.id,
CONTINENTS.continent,
SUM(COUNTRIES.population) as continent_population
FROM CONTINENTS
LEFT JOIN COUNTRIES ON COUNTRIES.continent_id=CONTINENTS.id
GROUP BY CONTINENTS.id, CONTINENTS.continent

Join between two tables

table1 - doctors
+---------+--------+------+
| country | state | doc |
+---------+--------+------+
| india | AP | 20 |
+---------+--------+------+
| india | TN | 30 |
+---------+--------+------+
| india | KA | 10 |
+---------+--------+------+
| US | LA | 30 |
+---------+--------+------+
| US | CA | 10 |
+---------+--------+------+
| US | NY | 50 |
+---------+--------+------+
table2 - engineers
+---------+--------+-------+
| country | state | engg |
+---------+--------+-------+
| india | AP | 100 |
+---------+--------+-------+
| india | TN | 400 |
+---------+--------+-------+
| india | KA | 250 |
+---------+--------+-------+
| US | LA | 140 |
+---------+--------+-------+
| US | CA | 120 |
+---------+--------+-------+
| US | NY | 150 |
+---------+--------+-------+
Desired output:
+---------+------+-------+
| country | doc | engg |
+---------+------+-------+
| india | 60 | 750 |
+---------+------+-------+
| US | 90 | 410 |
+---------+------+-------+
I tried with the below query but am getting more count of docs and engg. Someone please correct me..
select country, sum(a.doc), sum(b.engg)
from table1 a join table2 b on (a.country = b.country)
I think your problem is that you are getting a cross-product of both the tables with these set of values.
Try using:
tableA NATURAL JOIN tableB.
You can use UNION ALL
SELECT
country,
SUM(doc) AS doc,
SUM(engg) AS engg
FROM
(SELECT
country,
doc,
0 AS engg
FROM
doctors
UNION ALL
SELECT
country,
0,
engg
FROM
engineers
) a
GROUP BY
country
You need to group by country.
select a.country, sum(docSum), sum(enggSum) from
(select country, sum(doc) docSum from doctors) a
inner join
(select country, sum(engg) enggSum from engineers)
on a.country = b.country
group by a.country