SQL Creating view from 3 tables - sql

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;

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.

how to create a select statement with 2 foreign keys in one table pointing to two different tables

i have 3 tables and one of the tables (itemtag) has two columns each a foreign key pointing to each of the other 2 tables (item and tag). I'm trying to find out how to create a single select statement against this table containing fk's using values that correspond to one of the other tables.
Table: item
| itemid | name |
| -------- | -------------- |
| 1 | chair |
| 2 | table |
Table: tag
| tagid | name |
| -------- | -------------- |
| 10 | kitchen |
| 11 | bedroom |
Table: itemtag
| itemid (fk to item.itemid) | tagid (fk to tag.tagid) |
| --------------------------- | ------------------------ |
| 1 | 10 |
| 2 | 10 |
| 2 | 11 |
what is the proper way to query the itemtag table for a given name (like chair) or tag (like kitchen) that correlates values in the given item or tag tables? I have this statement that is producing the expected results as far as i can tell but is this really the correct way and/or actually producing accurate output?
select item.name
FROM item, tag, itemtag
WHERE itemtag.itemid = item.itemid
AND itemtag.tagid = tag.tagid
AND tag.name = 'kitchen'
In this example im expecting item names that have a tag of "kitchen".
Looking at your query, It seems It must be producing accurate output.
But you should consider using standard ANSI join as follows:
select item.name, tag.name
FROM itemtag join item on itemtag.itemid = item.itemid
JOIN tag on itemtag.tagid = tag.tagid
WHERE tag.name = 'kitchen'

Supertype & Subtypes and one to one relationship

I have the following supertype/multiple subtypes tables in SQL Server
supertype: Doctor and subtypes: Paediatrician, Orthopedic and Dentist
create table Doctor
(
DoctorID int primary key,
Name varchar(100),
-- add some other common attributes (all of vendor, sponsor, volunteer have) here.
)
create table Paediatrician
(
PaediatricianId int primary key,
DoctorID int foreign key references Doctor(DoctorID)
-- add some other attributes related to Paediatrician here.
)
create table Orthopedic
(
OrthopedicId int primary key,
DoctorID int foreign key references Doctor(DoctorID)
-- add some other attributes related to Orthopedic here.
)
create table Dentist
(
DentistId int primary key,
DoctorID int foreign key references Doctor(DoctorID)
-- add some other attributes related to Dentisthere.
)
My business logic is that a doctor can be either a Paediatrician, Dentist or an Orthopedic. Cannot be more than one of the subtypes. Based on the above design this is not enforced. I can create Doctor with Id = 1 and then go to Dentist and Orthopedictables and assign DoctorId value of 1 in both tables. How do I enforce it so that a doctor can be present at only one table?
I would arrange this bit differently. I would have 3 tables, a Doctor table (like you already have), a Specialist table and a SpecialistAttributes table.
The Doctor table contains all the Doctors' info, easy.
The Specialist Table contains your SpecialistTypeID and SpecialistDescription etc.
Your 3 example specialists would each be a row in this table.
The SpecialistAttributes table contains all the attributes needed for the specialists. In your Doctor table, you have a foreign key to lookup the SpecialistTypeID, so there can be only 1, then the SpecialistType has a number of SpecislaistAttibutes it can link to.
The other benefit of organising your data this way is that of you need to add any specialists roles or attributes, you don't need to change the structure of your database, just add more rows.
Doctor Table
| ID | Name | Specialist_FK |
---------------------------------
| 1 | Smith | 2 |
| 2 | Davies | 3 |
| 3 | Jones | 3 |
Specialist Table
| ID | Speciality |
----------------------
| 1 | Paediatrician |
| 2 | Orthopedic |
| 3 | Dentist |
SpecialistAttribute Table
| ID | SpecialityID+FK | Description | Other |
------------------------------------------------------------
| 1 | 1 | Paediatrician Info 1 | Other Info |
| 2 | 1 | Paediatrician Info 2 | Other Info |
| 3 | 2 | Orthopedic Info 1 | Other Info |
| 4 | 2 | Orthopedic Info 1 | Other Info |
| 5 | 3 | Dentist Info 1 | Other Info |
| 6 | 4 | Dentist Info 1 | Other Info |
There is no inbuild constraints/feature in the SQL server to handle this. You need to write custom login for it. Either in the procedure or Trigger.
You can write a stored procedure which would be responsible to insert in these tables. before insert, it will validate that if doctor id already exists in any of the tables if yes then an error will be custom raised otherwise procedure will insert the record in the respective table.

Join data from two tables without unique key

I have two tables in one database (access file).
Table 1:
| Product Id | Year | Sales |
----------------------------------
| 1144 | 2013 | 100 |
| 20131120 | 2013 | 200 |
| 1144 | 2012 | 333 |
Table 2:
| Product Id | Category |
----------------------------------
| 1144 | Car |
| 20131120 | Motorbike |
The first table (table 1) is the table when user can add new data whenever wants, and the second table is updated once in a while, when new product id appears. I would like to join this table, some kind of relation on the base of Product Id, but I'm not able to use relation due to the lack of unique, primary key, am I right? When I execute SQL query ( SELECT * FROM Table 1) I would like to get the result like this one:
| Product Id | Year | Sales | Category |
-----------------------------------------------
| 1144 | 2013 | 100 | Car |
| 20131120 | 2013 | 200 | Motrobike |
| 1144 | 2012 | 333 | Car |
I know I can join this two tables, but I would like not to do it every time when user add new data to table 1. Do you know any solution how could I do that?
I'm not able to use relation due to the lack of unique, primery key,
am I right?
No, this is not right, you can normally JOIN the two tables. It’s also legal to JOIN two tables or result sets on any columns that have what so called eligible data types.
SELECT
t1.ProductId, t1.year, t1.sales, t2.category
FROM table1 AS t1
INNER JOIN table2 AS t2 ON t1.productId = t2.productId;
but I would like not to do it every time when user add new data to
table 1
You don't need a ProductId in the second table, you need a CategoryId instead, and add a new column CategoryId to the first table too and declare it as a foreign key. Your tables should look like so:
Table1 (Products):
ProductId,
CategoryId,
Year,
Sales.
Table2 (Categories):
CategoryId,
CategoryName.
Then the category is added once to the categories table table2 and the product is added once to the products table table1. And for each product enter a CategoryId for it. This what so called normalization
You should also define ProductId in table1 and CategoryID in table2 as primary keys, and CategoryID in the first table as foreign key.

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.