SQL - Using COUNT with alias shorthand - sql

I'm learning SQL and having trouble performing a query that uses COUNT.
I have a movies database with three tables:
Table public.movies:
Column | Type | Collation | Nullable | Default
--------------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('movies_id_seq'::regclass)
title | text | | not null |
release_year | integer | | not null |
runtime | integer | | not null |
rating | text | | not null |
studio_id | integer | | |
Table public.stars:
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('stars_id_seq'::regclass)
first_name | text | | not null |
last_name | text | | |
birth_date | date | | not null |
Indexes:
"stars_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "roles" CONSTRAINT "roles_star_id_fkey" FOREIGN KEY (star_id) REFERENCES stars(id) ON DELETE CASCADE
Table public.roles:
Column | Type | Collation | Nullable | Default
----------+---------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('roles_id_seq'::regclass)
movie_id | integer | | |
star_id | integer | | |
Indexes:
"roles_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"roles_movie_id_fkey" FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE
"roles_star_id_fkey" FOREIGN KEY (star_id) REFERENCES stars(id) ON DELETE CASCADE
I'm trying to select the first and last names of every star along with the number of movies they have been in, and group everything by first and last name (as to not have any duplicate stars). This is what I tried:
SELECT s.first_name, s.last_name, m.COUNT(*)
FROM movies m
JOIN roles r
ON m.id = r.movie_id
JOIN stars s
ON r.star_id = s.id
GROUP BY s.first_name, s.last_name;
I keep getting ERROR: schema "m" does not exist.
I'd appreciate any pointers.

The correct syntax just uses COUNT(*):
SELECT s.first_name, s.last_name, COUNT(*)
FROM movies m JOIN
roles r
ON m.id = r.movie_id JOIN
stars s
ON r.star_id = s.id
GROUP BY s.id, s.first_name, s.last_name;
To avoid duplicates, use the primary key in the GROUP BY.
Note that you don't need movies in the query:
SELECT s.first_name, s.last_name, COUNT(*)
FROM roles r JOIN
stars s
ON r.star_id = s.id
GROUP BY s.id, s.first_name, s.last_name;
Finally, if a star can play more than one role in a movie, you will want to use COUNT(DISTINCT r.movie_id) in the SELECT.

Related

PostgreSQL: Aggregating combinations of names in list

I have a DB structure that looks something like this:
Table "public.person"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | not null |
Table "public.person_name"
Column | Type | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
person | integer | | not null |
name | character varying | | |
Foreign-key constraints:
"person_name_person_fkey" FOREIGN KEY (person) REFERENCES person(id)
Table "public.event"
Column | Type | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
id | integer | | not null |
name | character varying | | |
Table "public.attendee"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
event | integer | | |
person | integer | | |
Foreign-key constraints:
"attendee_event_fkey" FOREIGN KEY (event) REFERENCES public.event(id)
"attendee_person_fkey" FOREIGN KEY (person) REFERENCES person(id)
With some sample data:
person:
id
----
0
1
2
3
person_name:
person | name
--------+-----------
0 | Alex
0 | Alexander
1 | Barbara
1 | Barb
2 | Cecilia
3 | Dave
3 | David
event:
id | name
----+------------
0 | Wedding
1 | Party
2 | Funeral
attendee:
event | person
-------+--------
0 | 0
0 | 1
0 | 2
1 | 1
1 | 2
2 | 2
2 | 3
I'd like to make a select statement that returns all events, with a row for every combination of nicknames that all attendees have, like this:
event_id | event_name | attendee_list
----------+------------+---------------
0 | Wedding | Alex, Barbara, Cecilia
0 | Wedding | Alexander, Barbara, Cecilia
0 | Wedding | Alex, Barb, Cecilia
0 | Wedding | Alexander, Barb, Cecilia
1 | Party | Barbara, Cecilia
1 | Party | Barb, Cecilia
2 | Funeral | Cecilia, Dave
2 | Funeral | Cecilia, David
My initial intuition was to join all of the tables together, group by event, and then use string_agg, but that puts all of everybody's nicknames in the list (of course, since it's aggregating over the whole join). My second attempt was to select the attendee names from a subquery, but subqueries can't return multiple rows. I also tried aggregating using arrays instead, as described here, but you can't aggregate arrays of differing dimensionality. Finally, I tried using some recursive magic as described here, but found it difficult to adapt to my problem, and ultimately couldn't get it to work.
Here's a recursive query that does it. I made a array of the person IDs, and in each stage of the recursion I joined the next ID with the person_name table.
WITH RECURSIVE recur AS (
SELECT
event as event_id,
event.name as event_name,
array_agg(person) as person_id_list,
ARRAY[]::text[] as person_name_list,
1 as index
FROM attendee, event
WHERE attendee.event = event.id
GROUP BY event, event.name
UNION ALL
SELECT
event_id,
event_name,
person_id_list,
person_name_list || person_name.name,
index + 1
FROM recur
JOIN person_name on (person_name.person = recur.person_id_list[recur.index])
WHERE cardinality(recur.person_id_list) >= recur.index
)
SELECT event_id, event_name, array_to_string(person_name_list, ', ') as attendee_list
FROM recur
WHERE cardinality(recur.person_id_list) < recur.index
ORDER BY event_id;
I think I got it figured out with the "recursive magic" linked before. The issue was that my real data was a little more complicated and each attendee had a "position" in the list, which didn't always work with the r.id < t.id constraint. Here's a query that works with the sample data in the question:
with recursive recur as (
select
array[person_name.person] as persons,
array[name] as names,
attendee.event
from person_name
join attendee
on person_name.person=attendee.person
union all
select
persons || t.person,
names || t.name,
attendee.event
from person_name t
join recur r
on t.person != all(r.persons)
join attendee
on t.person=attendee.person
and attendee.event=r.event
)
select event, names
from recur
where cardinality(names)=(
select count(*)
from attendee
where attendee.event=recur.event
);
This does return an additional row for every possible order of attendees as well, but I'm fine with that (and like I said, my real data has a "position" field that would constrain that). If you needed only one ordering, the ordering has to be specified in the data somewhere, so for example adding back that r.id < t.id bit would work.

Find in raw sql by join table

Given 3 tables. I need to build SQL query to find two actors who cast together the most and list the titles
of those movies. Sort alphabetically
https://www.db-fiddle.com/f/r2Y9CpH8n7MHTeBaqEHe9S/0
Table film_actor
Column | Type | Modifiers
------------+-----------------------------+----------
actor_id | smallint | not null
film_id | smallint | not null
...
Table actor
Column | Type | Modifiers
------------+-----------------------------+----------
actor_id | integer | not null
first_name | character varying(45) | not null
last_name | character varying(45) | not null
...
Table film
Column | Type | Modifiers
------------+-----------------------------+----------
film_id | integer | not null
title | character varying(255) | not null
...
The desired output:
first_actor | second_actor | title
------------+--------------+--------------------
John Doe | Jane Doe | The Best Movie Ever
...
Input data and expected results would be helpful. Also, please tag DB you're using. You can try below code and see if it works:
SELECT (a.first_name || ' ' || a.last_name) AS First_Actor,
(b.first_name || ' ' || b.last_name) AS Second_Actor,
c.title
FROM actor a
JOIN
(SELECT a.actor_id AS first_actor,
b.actor_id AS second_actor,
a.film_id
FROM film_actor a
JOIN film_actor b ON a.film_id = b.film_id
AND a.actor_id < b.actor_id) ab ON a.actor_id = ab.first_actor
JOIN actor b ON b.actor_id = ab.second_actor
JOIN film c ON c.film_id = ab.film_id

Using CTE and aggregate functions with UPDATE

In PostgreSQL 10.6 I am trying to store results of 2 aggregate function calls into the avg_score and avg_time columns of the words_users table, but unfortunately get the syntax error:
WITH last_week_moves AS (
SELECT
m.gid,
m.uid,
m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played) AS diff
FROM words_moves m
JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
)
UPDATE words_users SET
avg_score = (SELECT ROUND(AVG(score), 1) FROM words_moves WHERE uid = 5),
avg_time = TO_CHAR(AVG(diff), 'HH24:MI')
FROM last_week_moves
WHERE uid = 5
GROUP BY uid;
(I am using the hardcoded uid = 5 in the above statement, but in the real life the latter is wrapped inside of a PL/PgSQL stored function and is using a parameter uid = in_uid).
ERROR: 42601: syntax error at or near "GROUP"
LINE 15: GROUP BY uid
^
LOCATION: scanner_yyerror, scan.l:1128
The database seems to be unhappy with the GROUP BY, but I need it for AVG(diff), because the CTE delivers the times between moves always for 2 players in a game:
SELECT
m.gid,
m.uid,
m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played) AS diff
FROM words_moves m
JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week';
gid | uid | diff
-------+-------+-----------------------
50399 | 774 | ¤
50608 | 8977 | ¤
50608 | 5 | 00:39:48.121149
50608 | 8977 | 00:09:46.221235
50608 | 5 | 01:35:23.524209
50608 | 8977 | 09:26:40.794061
50697 | 5 | ¤
50697 | 10322 | 02:13:16.502079
50697 | 5 | 01:47:44.681788
50697 | 10322 | 00:01:31.597973
50697 | 5 | 12:11:24.54716
50697 | 10322 | 12:01:15.078243
50697 | 5 | 11:52:39.60056
50697 | 10322 | 00:11:30.491137
50697 | 5 | 00:14:53.612513
50697 | 10322 | 01:45:23.940957
...
52469 | 5 | 02:46:29.768655
52469 | 8550 | 01:16:45.169882
52469 | 5 | 08:38:00.691552
(59 rows)
Does anybody please know, how to change my UPDATE query?
Below are the 3 tables in question:
# \d words_users
Table "public.words_users"
Column | Type | Collation | Nullable | Default
---------------+--------------------------+-----------+----------+------------------------------------------
uid | integer | | not null | nextval('words_users_uid_seq'::regclass)
created | timestamp with time zone | | not null |
visited | timestamp with time zone | | not null |
ip | inet | | not null |
fcm | text | | |
apns | text | | |
adm | text | | |
motto | text | | |
vip_until | timestamp with time zone | | |
grand_until | timestamp with time zone | | |
banned_until | timestamp with time zone | | |
banned_reason | text | | |
elo | integer | | not null |
medals | integer | | not null |
coins | integer | | not null |
avg_score | double precision | | |
avg_time | text | | |
Indexes:
"words_users_pkey" PRIMARY KEY, btree (uid)
Check constraints:
"words_users_banned_reason_check" CHECK (length(banned_reason) > 0)
"words_users_elo_check" CHECK (elo >= 0)
"words_users_medals_check" CHECK (medals >= 0)
Referenced by:
TABLE "words_chat" CONSTRAINT "words_chat_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_games" CONSTRAINT "words_games_player1_fkey" FOREIGN KEY (player1) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_games" CONSTRAINT "words_games_player2_fkey" FOREIGN KEY (player2) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_moves" CONSTRAINT "words_moves_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_reviews" CONSTRAINT "words_reviews_author_fkey" FOREIGN KEY (author) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_reviews" CONSTRAINT "words_reviews_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_scores" CONSTRAINT "words_scores_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_social" CONSTRAINT "words_social_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
TABLE "words_stats" CONSTRAINT "words_stats_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
# \d words_moves
Table "public.words_moves"
Column | Type | Collation | Nullable | Default
---------+--------------------------+-----------+----------+------------------------------------------
mid | bigint | | not null | nextval('words_moves_mid_seq'::regclass)
action | text | | not null |
gid | integer | | not null |
uid | integer | | not null |
played | timestamp with time zone | | not null |
tiles | jsonb | | |
score | integer | | |
letters | text | | |
hand | text | | |
puzzle | boolean | | not null | false
Indexes:
"words_moves_pkey" PRIMARY KEY, btree (mid)
"words_moves_gid_played_idx" btree (gid, played DESC)
"words_moves_uid_idx" btree (uid)
Check constraints:
"words_moves_score_check" CHECK (score >= 0)
Foreign-key constraints:
"words_moves_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
"words_moves_uid_fkey" FOREIGN KEY (uid) REFERENCES words_users(uid) ON DELETE CASCADE
Referenced by:
TABLE "words_scores" CONSTRAINT "words_scores_mid_fkey" FOREIGN KEY (mid) REFERENCES words_moves(mid) ON DELETE CASCADE
# \d words_games
Table "public.words_games"
Column | Type | Collation | Nullable | Default
----------+--------------------------+-----------+----------+------------------------------------------
gid | integer | | not null | nextval('words_games_gid_seq'::regclass)
created | timestamp with time zone | | not null |
finished | timestamp with time zone | | |
player1 | integer | | not null |
player2 | integer | | |
played1 | timestamp with time zone | | |
played2 | timestamp with time zone | | |
state1 | text | | |
state2 | text | | |
reason | text | | |
hint1 | text | | |
hint2 | text | | |
score1 | integer | | not null |
score2 | integer | | not null |
chat1 | integer | | not null |
chat2 | integer | | not null |
hand1 | character(1)[] | | not null |
hand2 | character(1)[] | | not null |
pile | character(1)[] | | not null |
letters | character(1)[] | | not null |
values | integer[] | | not null |
bid | integer | | not null |
friendly | boolean | | |
Indexes:
"words_games_pkey" PRIMARY KEY, btree (gid)
"words_games_player1_coalesce_idx" btree (player1, COALESCE(finished, 'infinity'::timestamp with time zone))
"words_games_player2_coalesce_idx" btree (player2, COALESCE(finished, 'infinity'::timestamp with time zone))
Check constraints:
"words_games_chat1_check" CHECK (chat1 >= 0)
"words_games_chat2_check" CHECK (chat2 >= 0)
"words_games_check" CHECK (player1 <> player2)
"words_games_score1_check" CHECK (score1 >= 0)
"words_games_score2_check" CHECK (score2 >= 0)
Foreign-key constraints:
"words_games_bid_fkey" FOREIGN KEY (bid) REFERENCES words_boards(bid) ON DELETE CASCADE
"words_games_player1_fkey" FOREIGN KEY (player1) REFERENCES words_users(uid) ON DELETE CASCADE
"words_games_player2_fkey" FOREIGN KEY (player2) REFERENCES words_users(uid) ON DELETE CASCADE
Referenced by:
TABLE "words_chat" CONSTRAINT "words_chat_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
TABLE "words_moves" CONSTRAINT "words_moves_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
TABLE "words_scores" CONSTRAINT "words_scores_gid_fkey" FOREIGN KEY (gid) REFERENCES words_games(gid) ON DELETE CASCADE
UPDATE:
As suggested by #lau I have tried moving the AVG into the CTE, but get another syntax error:
WITH last_week_moves AS (
SELECT
m.gid,
m.uid,
TO_CHAR(AVG(m.played - LAG(m.played) OVER(PARTITION BY m.gid ORDER BY played)), 'HH24:MI') AS diff
FROM words_moves m
JOIN words_games g ON (m.gid = g.gid AND 5 IN (g.player1, g.player2))
WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
GROUP BY uid
)
UPDATE words_users SET
avg_score = (SELECT ROUND(AVG(score), 1) FROM words_moves WHERE uid = 5),
avg_time = diff
FROM last_week_moves
WHERE uid = 5;
ERROR: 42803: aggregate function calls cannot contain window function calls
LINE 5: TO_CHAR(AVG(m.played - LAG(m.played)...
^
LOCATION: check_agg_arguments_walker, parse_agg.c:728
You seem to want:
WITH last_week_moves AS (
SELECT m.uid,
(MAX(m.played) - MIN(m.played)) / NULLIF(COUNT(*) - 1, 0) as avg_diff,
AVG(score) as avg_score
FROM words_moves m JOIN
words_games g
ON m.gid = g.gid AND 5 IN (g.player1, g.player2)
WHERE m.played > CURRENT_TIMESTAMP - INTERVAL '1 week'
GROUP BY m.uid
)
UPDATE words_users wu
SET avg_score = lwm.avg_score,
avg_time = TO_CHAR(avg_diff, 'HH24:MI')
FROM last_week_moves lwm
WHERE wu.uid = lwm.uid AND
wu.uid = 5;
Note that this simplifies the calculate of the diff calculation, so lag() is not needed.
You can see that these are equivalent:
value diff
1
4 3
9 5
The average of diff is obviously 4. This is ((4 - 1) + (9 - 4)) / 2. You see that the "4"s cancel, so it is really (9 - 1) / 2. This observation generalizes.

SQL Creating view from 3 tables

I have three tables: Products, Attributes and AttributesDefinitions.
Products:
+----+------+
| Id | Name |
+----+------+
| 1 | Shoe1|
+----+------+
| 2 | Shoe2|
+----+------+
| 3 | Shoe3|
+----+------+
AttributesDefinition:
+----+---------+
| Id | Name |
+----+---------+
| 1 | Color |
| 2 | Type |
| 3 | Destiny |
+----+---------+
Attributes:
+----+--------------+--------------+-----------+
| Id | Value | DefinitionId | ProductId |
+----+--------------+--------------+-----------+
| 1 | Brown | 1 | 2 |
| 2 | Yellow | 1 | 1 |
| 3 | Sport | 3 | 1 |
| 4 | Jelly shoes | 2 | 1 |
| 5 | Normal shoes | 2 | 2 |
+----+--------------+--------------+-----------+
In AttributesDefinitions I have wanted attributes definitions.
In Attributes I have attributes and their values.
Each Product has many attributes, but only 1 of each type (attribute definition).
My task is to make a view containing list of products and all their attributes values.
It should look like this:
ProductsWithAttributesView:
+---------+--------+--------------+---------+
| Product | Color | Type | Destiny |
+---------+--------+--------------+---------+
| Shoe1 | Yellow | Jelly shoes | Sport |
| Shoe2 | Brown | Normal shoes | NULL |
| Shoe3 | NULL | NULL | NULL |
+---------+--------+--------------+---------+
The purpose of this is getting list of products on B2B platform and being able to filter them by values of attributes.
Any help how can I achieve that ?
I use code from CaitLAN Jenner to present my solution, but I`m not sure if you can make view which will dynamically adapt when new category is added.
CREATE TABLE Products
(
Id INT PRIMARY KEY,
Name VARCHAR(255)
);
INSERT INTO Products (Id,Name)
VALUES (1,'Car'),(2,'Motorcycle'),(3,'Bicycle')
CREATE TABLE AttributesDefinition
(
Id INT PRIMARY KEY,
Name VARCHAR(255)
);
INSERT INTO AttributesDefinition (Id,Name)
VALUES (1,'Number of wheels'),(2,'People'),(3,'Engine')
CREATE TABLE Attributes
(
Id INT,
Name VARCHAR(255),
Value VARCHAR(255),
DefinitionId INT FOREIGN KEY REFERENCES AttributesDefinition (Id),
ProductId INT FOREIGN KEY REFERENCES Products (Id)
);
INSERT INTO Attributes (Id, Name, Value, DefinitionId, ProductId)
VALUES (1,'Number of wheels','4',1,1),
(2,'Number of wheels','2',1,2),
(3,'Number of wheels','2',1,3),
(4,'People','4',2,1),
(5,'People','2',2,2),
(6,'People','1',2,3),
(7,'Engine','V6',3,1),
(8,'Engine','V2',3,2)
CREATE VIEW ProductsWithAttributesView AS
SELECT
products.Name as 'Products',
atr1.Value As 'Number of wheels',
atr2.Value As 'People',
atr3.Value As 'Engine'
FROM Products AS products
LEFT JOIN Attributes AS atr1 ON atr1.ProductId = products.Id AND atr1.DefinitionId = 1
LEFT JOIN Attributes AS atr2 ON atr2.ProductId = products.Id AND atr2.DefinitionId = 2
LEFT JOIN Attributes AS atr3 ON atr3.ProductId = products.Id AND atr3.DefinitionId = 3
Result
Products | Number of wheels | People | Engine
Car | 4 | 4 | V6
Motorcycle| 2 | 2 | V2
Bicycle | 2 | 1 | NULL
Edit. This one with pivot:
CREATE VIEW ProductsWithAttributesView2 AS
WITH pivot_data AS
(
SELECT products.Name as Products, -- Grouping Column
def.Name as AtrName, -- Spreading Column
Value -- Aggregate Column
FROM Attributes atr
INNER JOIN Products products ON atr.ProductId = products.Id
INNER JOIN AttributesDefinition def ON def.Id = atr.DefinitionId
)
SELECT Products, [Number of wheels],[People],[Engine]
FROM pivot_data
PIVOT (max(value) FOR AtrName IN ([Number of wheels],[People],[Engine])) AS p;
As I stated in my comment, I strongly disagree with the schema you've described because it appears to me that the Products table should have either a one-to-many or many-to-many relationship with Attributes. In other words, one Product can have many Attributes, and many Products can share the same Attribute. Additionally, because of the design you've mentioned and what your end goal is (a Product with an arbitrary-length list of attributes as columns) what your asking for may not be possible. You might could achieve those results with a carefully crafted PIVOT statement within the View definition. First let me know if the code below works.
With all of that said, if I make the assumption that you are using T-SQL, then this code will create the tables you've described and create a View based performing a JOIN on the tables. Hopefully this pushes you in the correct direction for moving forward.
CREATE TABLE Products
(
Id INT PRIMARY KEY,
Name VARCHAR(255)
);
CREATE TABLE AttributesDefinition
(
Id INT PRIMARY KEY,
Name VARCHAR(255)
);
CREATE TABLE Attributes
(
Id INT,
Name VARCHAR(255),
Value VARCHAR(255),
DefinitionId INT FOREIGN KEY REFERENCES AttributesDefinition (Id),
ProductId INT FOREIGN KEY REFERENCES Products (Id)
);
CREATE VIEW ProductsWithAttributesView AS
SELECT p.Name AS Products
,ad.Name AS AttributeDefinition
,a.Name AS AttributeName
,a.Value AS AttributeValue
FROM Products p
INNER JOIN Attributes a ON p.Id = a.ProductId
INNER JOIN AttributesDefinition ad ON ad.Id = a.DefinitionId;
SELECT * FROM ProductsWithAttributesView;

MySQL Query: get all matches from the matches table along with the name of the team from the teams table given that when …

I have the following table structures:
matches:
+-------------------+---------------------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------------------+------+-----+-------------------+-----------------------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| creator_id | bigint(20) | NO | | NULL | |
| mode | enum('versus','freeplay') | NO | | NULL | |
| name | varchar(100) | NO | | NULL | |
| team_1_id | varchar(100) | NO | | NULL | |
| team_2_id | varchar(100) | NO | | NULL | |
+-------------------+---------------------------+------+-----+-------------------+-----------------------------+
teams:
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| creator_id | bigint(20) | NO | MUL | NULL | |
| name | varchar(100) | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
I need a query where we get all matches from the matches table along with the name of the team from the teams table given that when mode is "versus" the name of the team is taken from the teams table but when the mode is "freeplay" the name of the team is team_1_id or team_2_id themselves (they can hold strings, that is why they are varchar instead of int) without going to the teams table.
Use:
SELECT m.id,
m.creator_id,
m.mode,
m.name,
m.team_1_id,
m.team_2_id
FROM MATCHES m
WHERE m.mode = 'freeplay'
UNION ALL
SELECT m.id,
m.creator_id,
m.mode,
m.name,
t1.name,
t2.name
FROM MATCHES m
LEFT JOIN TEAMS t1 ON t1.id = m.team_1_id
LEFT JOIN TEAMS t2 ON t2.id = m.team_2_id
WHERE m.mode = 'versus'
SELECT CASE mode WHEN 'versus' THEN t1.name
ELSE team_1_id END AS name FROM matches
LEFT JOIN teams t1 ON t1.id=team_1_id
Do the same for team 2 and add where clause to suit your need
First, I would suggest you change your table structure slightly. Instead of using team_X_id for a name or an id, use it only for an id. Add an additional column for team_X_name that you can put the string in. That way you can define foreign keys and have the correct datatype. Set the team_X_id field to null and team_X_name to the team name when in freeplay mode, and set the team_X_id to the team id in versus mode.
That said, this should do what you want:
SELECT mode,
IF(team_1.id IS NULL, team_1_id, team_1.name),
IF(team_2.id IS NULL, team_2_id, team_2.name),
FROM matches
LEFT JOIN teams AS team_1 ON (matches.team_id_1=team_1.id)
LEFT JOIN teams AS team_2 ON (matches.team_id_2=team_2.id);
edit:
Actually, perhaps I misunderstood the design. If you are saying mode 'freeplay' flag means neither team_X_id will be an actual team id then you need a slightly different query:
SELECT mode,
IF(mode = 'freeplay' OR team_1.id IS NULL, team_1_id, team_1.name),
IF(mode = 'freeplay' OR team_2.id IS NULL, team_2_id, team_2.name),
FROM matches
LEFT JOIN teams AS team_1 ON (matches.team_id_1=team_1.id)
LEFT JOIN teams AS team_2 ON (matches.team_id_2=team_2.id);
But I would strongly suggest improving your DB design.