SQL Query - Iterate based on condition - sql

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

Related

How do I join an ARRAY to a COLUMN?

I have two large tables - Table_A and Table_B - that I want to join on the ID field. "ID" in Table_A is a column and "IDs" in Table_B is an array
Table_A:
ID | City |
----+------------+
101 | London |
102 | Paris |
103 | Rome |
104 | Copenhagen |
105 | Amsterdam |
106 | Berlin |
107 | Cardiff |
108 | Lisbon |
Table_B:
Date | Sessions | IDs
------+----------+--------------
06-02 | 1 | [107,102]
06-03 | 1 | [103]
11-12 | 1 | [105,107,103]
27-06 | 1 | [104,108]
31-01 | 1 | [105]
22-04 | 1 | [106,102]
08-07 | 1 | [101,105,108]
02-10 | 1 | [105]
Desirable Output:
Date | Sessions | ID | City
------+----------+-------------+-------------
06-02 | 1 | 107 | Cardiff
| | 102 | Paris
06-03 | 1 | 103 | Rome
11-12 | 1 | 105 | Amsterdam
| | 107 | Cardiff
| | 103 | Rome
27-06 | 1 | 104 | Copenhagen
| | 108 | Lisbon
...
I have tried using inner joins with unnest and union all but nothing is working. Any help would be appreciated.
Something along those lines should yield the result you are looking for
select
date,
sessions,
array_agg(id_un) as id,
array_agg(city) as city
from table_b b, unnest (id) as id_un
left join table_a a on id_un = a.id
group by 1, 2
Consider also below approach
select date, sessions, ids as id,
array(
select city
from b.ids id
left join Table_A
using(id)
) city
from Table_B b
if applied to sample data in your question - output is

Select all values from last date that is shared between rows grouped by a value

I have a Postgresql table with a list of values for countries over time, and their continents. Values can be NULL. I’d like to get the sum for each continent over time, up to the latest date each continent has data for.
This is my table (view on DB Fiddle):
| continent | country | date | value | id |
| --------- | ------- | ---------- | ----- | --- |
| Europe | Germany | 2020-05-25 | 10 | 1 |
| Europe | Germany | 2020-05-26 | 11 | 2 |
| Europe | Germany | 2020-05-27 | 12 | 3 |
| Europe | Germany | 2020-05-28 | 13 | 4 |
| Europe | Italy | 2020-05-25 | 20 | 5 |
| Europe | Italy | 2020-05-26 | 21 | 6 |
| Europe | Italy | 2020-05-27 | 22 | 7 |
| Europe | Italy | 2020-05-28 | 23 | 8 |
| Europe | France | 2020-05-25 | 30 | 9 |
| Europe | France | 2020-05-26 | 31 | 10 |
| Europe | France | 2020-05-27 | 32 | 11 |
| Europe | France | 2020-05-28 | NULL | 12 |
| Africa | Congo | 2020-05-25 | 40 | 13 |
| Africa | Congo | 2020-05-26 | 41 | 14 |
| Africa | Congo | 2020-05-27 | NULL | 15 |
And this is what I’d like to get back. Note that the Europe includes data up to the 27th, because France has no data for the 28th, and Africa up to the 26th, because that’s the last date its countries have data for.
| continent | date | value |
| --------- | ---------- | ----- |
| Europe | 2020-05-27 | 66 |
| Africa | 2020-05-26 | 41 |
| Europe | 2020-05-26 | 63 |
| Africa | 2020-05-25 | 40 |
| Europe | 2020-05-25 | 60 |
I managed to almost get there by including the number of countries per continent that have data on each date.
SELECT
countries.continent,
countries.date,
SUM(countries.value) AS value,
COUNT(countries.country) AS countries_count
FROM
countries
WHERE
countries.value IS NOT NULL
GROUP BY
countries.continent,
countries.date
ORDER BY
countries.date DESC,
countries.continent;
| continent | date | value | countries_count |
| --------- | ---------- | ----- | --------------- |
| Europe | 2020-05-28 | 36 | 2 |
| Europe | 2020-05-27 | 66 | 3 |
| Africa | 2020-05-26 | 41 | 1 |
| Europe | 2020-05-26 | 63 | 3 |
| Africa | 2020-05-25 | 40 | 1 |
| Europe | 2020-05-25 | 60 | 3 |
I also managed to get the number of countries per continent.
SELECT
countries.continent,
COUNT(DISTINCT countries.country) as number_of_countries
FROM
countries
GROUP BY
countries.continent;
| continent | number_of_countries |
| --------- | ------------------- |
| Africa | 1 |
| Europe | 3 |
I’m stuck on how to combine the two queries to filter out rows that haven’t got the full number of countries for the continent (e. g. select rows where countries_count is 3 for Europe and 1 for Africa.
This is the end result I’d like to get back:
| continent | date | value |
| --------- | ---------- | ----- |
| Europe | 2020-05-27 | 66 |
| Africa | 2020-05-26 | 41 |
| Europe | 2020-05-26 | 63 |
| Africa | 2020-05-25 | 40 |
| Europe | 2020-05-25 | 60 |
Or maybe there’s a completely different way to go about this?
View on DB Fiddle
You can compare the number of countries on the continent to the number available on each date -- and then just use dates where the two match ("complete data").
Unfortunately, Postgres does not support count(distinct) as a window function. But you can do:
SELECT c.continent, c.date,
SUM(c.value) AS value,
COUNT(c.country) AS countries_count
FROM (SELECT c.*,
COUNT(*) OVER (PARTITION BY continent, date) as num_on_date
FROM countries c
WHERE value IS NOT NULL
) c JOIN
(SELECT continent, COUNT(DISTINCT country) as num_countries
FROM countries
GROUP BY continent
) cc
ON cc.continent = c.continent
WHERE num_on_date = num_countries
GROUP BY c.continent, c.date
ORDER BY c.date DESC, c.continent;
Here is a db<>fiddle.
You can also do this with a filter in the HAVING clause:
SELECT c.continent, c.date,
SUM(c.value) AS value,
COUNT(c.country) AS countries_count
FROM countries c
WHERE value IS NOT NULL
GROUP BY c.continent, c.date
HAVING COUNT(*) = (SELECT COUNT(DISTINCT c2.country)
FROM countries c2
WHERE c2.continent = c.continent
)
ORDER BY c.date DESC, c.continent;
This does the aggregation and then only keeps the rows where the number of rows matches the number of countries.
You can use NOT IN within your WHERE Clause :
SELECT
c.continent,
c.date,
SUM(c.value) AS value,
COUNT(DISTINCT c.country) AS countries_count
FROM countries c
WHERE date NOT IN
( SELECT date
FROM countries
WHERE value IS NULL )
GROUP BY c.continent, c.date
ORDER BY c.date DESC, c.continent;
You can filter with a having clause to exclude groups where any country is null
SELECT
continent,
date,
SUM(value) AS value
FROM countries
GROUP BY continent, date
HAVING BOOL_AND(value is not null)
ORDER BY date DESC, continent
With SUM() window function:
select distinct c.continent, c.date,
sum(c.value) over (partition by c.continent, c.date) "value"
from countries c
where not exists (
select 1 from countries
where continent = c.continent and date = c.date and value is null
)
order by c.date desc, c.continent;
See the demo.
Results:
| continent | date | value |
| --------- | ------------------------ | ----- |
| Europe | 2020-05-27T00:00:00.000Z | 66 |
| Africa | 2020-05-26T00:00:00.000Z | 41 |
| Europe | 2020-05-26T00:00:00.000Z | 63 |
| Africa | 2020-05-25T00:00:00.000Z | 40 |
| Europe | 2020-05-25T00:00:00.000Z | 60 |

Generate list of matches starting from players list

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.

Select rows with the same attribute1 and different attribute2

I have a table
+----+------+---------+
| ID | CODE | COUNTRY |
+----+------+---------+
| 1 | 05 | France |
| 2 | 05 | France |
| 3 | 06 | Germany |
| 4 | 07 | France |
| 5 | 07 | Italy |
+----+------+---------+
and I need to select rows with the same code but different country.
So the result should be:
+------+---------+
| CODE | COUNTRY |
+------+---------+
| 07 | France |
| 07 | Italy |
+------+---------+
I tried
SELECT t1.code AS code, t1.country AS country
FROM countries AS t1, countries AS t2
WHERE t1.code = t2.code
AND t1.country <> t2.country;
and it works for the table in the example above.
But if the table looks like this:
+----+------+---------+
| ID | CODE | COUNTRY |
+----+------+---------+
| 1 | 05 | France |
| 2 | 05 | France |
| 3 | 06 | Germany |
| 4 | 07 | France |
| 5 | 07 | Italy |
| 6 | 07 | Italy |
+----+------+---------+
the result is:
+------+---------+
| CODE | COUNTRY |
+------+---------+
| 07 | Italy |
| 07 | Italy |
| 07 | France |
| 07 | France |
+------+---------+
but should be the same as above.
(I work with MS Access, so the query should work on Access)
Just add distinct
SELECT DISTINCT t1.code AS code, t1.country AS country
FROM countries AS t1, countries AS t2
WHERE t1.code = t2.code
AND t1.country <> t2.country;
You can use MIN and MAX to spot Codes where the Country is different, then use that to select all rows:
SELECT * FROM countries WHERE Code IN (
SELECT Code
FROM countries
GROUP BY Code
HAVING MIN(Country) < MAX(Country)
)

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