POSTGRES - Combine same columns with different text in one row - sql

I have three tables that i want to combine. I have a table doctor, patient and operation. Doctor represents all cind of doctors. Patient represents a patient and operation holds primaryKeys from patient and doctor.
How do i write a query which will show me chiefDoctor and assistantDocotor and Patient? What did i reach so far?
Show firstname and lastname from one either Chiefdoctor or Assistantdoctor. How do i write to show a table with both Chief and Assistant in it?
select a.vorname|| ' ' || a.nachname AS leitenderArzt, p.firstname || ' ' || p.lastname AS patient
from angestellter a inner join operation o on a.id = o.leitenderarzt
inner join patient p on o.patientident=p.ident
| id | firstname | lastname | patient |
| 1 | ImA | ChiefDoctor1 | p.firstname |
| 3 | ImA | ChiefDoctor3 | p.firstname |
The underlying structure of my database with a representation.
CREATE TABLE doctor
(
id serial NOT NULL,
firstname character varying(255) NOT NULL,
lastname character varying(255) NOT NULL,
CONSTRAINT angestellter_pkey PRIMARY KEY (id),
}
Table doctor
|id | firstname | lastname |
| 1 | ImA | ChiefDoctor1 |
| 2 | ImA | AssistantDoctor |
| 3 | ImA | ChiefDoctor2 |
CREATE TABLE patient
(
ident serial NOT NULL,
firstname character varying(255) NOT NULL,
lastname character varying(255) NOT NULL,
CONSTRAINT patient_pkey PRIMARY KEY (ident),
}
Table patient
| ident | firstname | lastname |
| 1 | Operated | ME |
CREATE TABLE operation
(
id serial NOT NULL,
chiefDoctor integer NOT NULL,
AssistantDoctor integer NOT NULL,
patientident integer NOT NULL,
CONSTRAINT operation_pkey PRIMARY KEY (id),
CONSTRAINT fkoperation539608 FOREIGN KEY (patientident)
REFERENCES patient (ident) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fkoperation745809 FOREIGN KEY (assistantDoctor)
REFERENCES angestellter (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fkoperation949671 FOREIGN KEY (chiefDoctor)
REFERENCES angestellter (id) MATCH SIMPLE
}
Table operation
| id | doctorID | doctorID | patientID |
| 1 | 1 | 2 | 1 |
How do i write a query which will show show who operated a patient with full name? It should look like this.
| id | chiefdoctor | assistantdoctor | patient |
|----| ImA + ChiefDoctor |ImA + AssistantDoctor | Operated + ME|

The first and perhaps most intuitive way of doing this is to join doctor twice:
select o.id
, d1.firstname || ' + ' || d1.lastname as chiefdoctor
, d2.firstname || ' + ' || d2.lastname as assistantdoctor
, p.firstname || ' + ' || p.lastname as patient
from operation o
join doctor d1
on o.chiefDoctor = d1.id
join doctor d2
on o.AssistantDoctor = d2.id
join patient p
on o.patientident = p.ident
You probably would like to trim names as in:
trim(both from d1.firstname) || ' + ' || trim(both from d1.lastname)
but I got the feeling that this was not the major concern you had so inorder to keep the solution shorter, I left that out

Related

SQL - Using COUNT with alias shorthand

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.

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

How to get PostgreSQL to give me MySQL SHOW COLUMNS-like results?

After much mucking about, I'm close (For my sake, I don't care about the type differences). I do, however, want the exact same output format as MySQL. The reason is I'm trying to adapt a MySQL-only tool for use with PostgreSQL. Here's an example output from MySQL (albeit with fewer columns):
mysql> show columns from users;
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(200) | YES | | NULL | |
| institution | varchar(200) | YES | | NULL | |
+-------------+--------------+------+-----+---------+-------+
Here's the table on which I'm testing this:
Table "public.users"
Column | Type | Collation | Nullable | Default
--------------------+------------------------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('users_id_seq'::regclass)
name | character varying(255) | | |
role_id | integer | | |
image_url | character varying(510) | | |
institution | character varying(255) | | |
qualifications | text | | |
cv_url | character varying(510) | | |
specializations | text | | |
text_collaboration | text | | |
Indexes:
"users_pkey" PRIMARY KEY, btree (id) Check constraints:
"users_name_not_null" CHECK (name IS NOT NULL) Foreign-key constraints:
"fk_role_id" FOREIGN KEY (role_id) REFERENCES roles(id) Referenced by:
TABLE "novel_reviews" CONSTRAINT "novels_reviewer_id_fkey" FOREIGN KEY (reviewer_id) REFERENCES users(id)
TABLE "review_translations" CONSTRAINT "review_translations_recorder_id_fkey" FOREIGN KEY (recorder_id) REFERENCES users(id)
Here's the query I have... it's probably poorly done esp. with the GROUP BY part:
SELECT column_name AS "Field"
, data_type AS "Type"
, is_nullable AS "Null"
, CASE WHEN is_primary=true THEN 'PRI' ELSE NULL END AS "Key"
, column_default as "Default"
, CASE WHEN column_default LIKE 'nextval(%' THEN 'auto_increment' ELSE '' END AS "Extra"
FROM
(
SELECT c.column_name
, c.data_type
, c.is_nullable
, tc.constraint_type='PRIMARY KEY' AS is_primary
, c.column_default
FROM information_schema.columns AS c
LEFT JOIN information_schema.constraint_column_usage AS ccu USING (column_name, table_name)
LEFT JOIN information_schema.table_constraints tc USING (constraint_name)
WHERE c.table_name = 'users'
GROUP BY c.column_name
, c.data_type
, c.is_nullable
, is_primary
, c.column_default
) as sq;
Here's the results I'm getting currently. Sorry for the poor formatting.
> Field | Type | Null | Key | Default | Extra
> --------------------+-------------------+------+-----+-----------------------------------+----------------
> | cv_url | character varying | YES | |
> | id | integer | NO | | nextval('users_id_seq'::regclass) | auto_increment
> | id | integer | NO | PRI | nextval('users_id_seq'::regclass) | auto_increment
> | image_url | character varying | YES | |
> | institution | character varying | YES | |
> | name | character varying | YES | |
> | qualifications | text | YES | |
> | role_id | integer | YES | |
> | specializations | text | YES | |
> |
> | (10 rows)
I can't figure how to get the second occurrence of id to go away, the one emanating from the non-primarykey constraint. I can't wrap my head around how to drop that. I tried doing WHERE is_primary_key is NULL or is_primary_key=TRUE but that drops the Name field as well, which is joined to a constraint which is also not a primary key.
What I'd like is to get all columns from the table, (each only once) and the string "PRI" if the field is a primary key.
Help! I'm in a bit over my head. Thanks.
It is the query you need :
SELECT *
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
Figured it out after a lot of banging my head against the wall. First I made a view:
CREATE VIEW table_column_constraints as (SELECT c.table_schema, c.table_name, c.column_name
, c.data_type
, c.is_nullable
, tc.constraint_type
, c.column_default
FROM information_schema.columns AS c
LEFT JOIN information_schema.constraint_column_usage AS ccu USING (column_name, table_name)
LEFT JOIN information_schema.table_constraints tc ON tc.constraint_name=ccu.constraint_name WHERE c.table_schema='public');
Then, I did a de-duplication technique of comparing the table to itself:
SELECT column_name as "Field"
, data_type AS "Type"
, is_nullable AS "Null"
, CASE WHEN constraint_type='PRIMARY KEY' THEN 'PRI' ELSE NULL END AS "Key"
, column_default AS "Default", CASE WHEN column_default LIKE 'nextval(%' THEN 'auto_increment' ELSE '' END AS "Extra"
FROM table_column_constraints as given WHERE given.table_name = 'users'
AND NOT EXISTS (SELECT * FROM table_column_constraints other WHERE other.column_name=given.column_name AND given.constraint_type!='PRIMARY KEY' AND other.constraint_type='PRIMARY KEY');
To get the following results:
Field | Type | Null | Key | Default | Extra
--------------------+-------------------+------+-----+-----------------------------------+----------------
name | character varying | YES | | |
id | integer | NO | PRI | nextval('users_id_seq'::regclass) | auto_increment
image_url | character varying | YES | | |
institution | character varying | YES | | |
qualifications | text | YES | | |
cv_url | character varying | YES | | |
specializations | text | YES | | |
text_collaboration | text | YES | | |
role_id | integer | YES | | |
(9 rows)
I was inspired by https://stackoverflow.com/a/45065229/1151229 on a question called "Selecting rows ordered by some column and distinct on another"

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.