How do I get average through ‏multiple tables - sql

I got homework to get ‏average tags of user in album (user_id = x) in the folowing tabels:
>>> CREATE TABLE USERS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL);
>>> CREATE TABLE ALBUMS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL, CREATION_DATE TEXT NOT NULL,
USER_ID INTEGER REFERENCES USERS(USER_ID) NOT NULL);
>>> CREATE TABLE PICTURES (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL,
LOCATION TEXT NOT NULL,
CREATION_DATE TEXT NOT NULL,
ALBUM_ID INTEGER REFERENCES ALBUMS(ALBUM_ID) NOT NULL);
>>> CREATE TABLE TAGS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
PICTURE_ID INTEGER REFERENCES PICTURES(PICTURE_ID) NOT NULL,
USER_ID INTEGER REFERENCES USERS(USER_ID) NOT NULL);";
explenetion:
Each tag is a row in TAGS and it has picture_id, each picture has album_id and each album has user_id, basically i need to count how many times the user is tagged in each album and find the average times that the user is tagged in an album.
I can use this using only: SELECT ? FROM, AVG(), COUNT(), JOIN (INNER, LEFT, RIGHT, FULL JOIN), ON, IN, AND, OR, LIKE, , NOT, (=, != , >, <), IS, DISTINCT, ORDER BY(ASC/DESC), LIMT, OFFSET, and WHERE that means i cannot use GROUP BY
i tried this
SELECT * FROM TAGS INNER JOIN PICTURES ON tags.picture_id = PICTURES.Id where album_id IN (select id from ALBUMS where user_id = x) AND user_id = x;
but it only gives my a table that has all the tags of the user
How can i get the avg tags per album of (user_id = x), is this even possible?

First count how many times the user is tagged in each album and then get the average of these counters:
select
avg(counter) averagetags
from (
select count(t.user_id) counter
from albums a
inner join pictures p on p.album_id = a.id
inner join tags t on t.picture_id = p.id
where t.user_id = ?
group by a.id
)

Related

How do I select data from a table consisting of a foreign key constraint?

I have two tables, User & Playlist
Table User:
CREATE TABLE IF NOT EXISTS User (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(26) NOT Null UNIQUE,
email TEXT NOT Null UNIQUE,
password VARCHAR(100)
)
Table Playlist:
CREATE TABLE IF NOT EXISTS Playlist (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
image BLOB,
private NUMERIC,
playlistOwner VARCHAR(26),
FOREIGN KEY(playlistOwner) REFERENCES User(id)
)
Playlist table:
What I have tried with:
SELECT * FROM Playlist
JOIN Playlist
ON 10 = Playlist.playlistOwner
WHERE playlistOwner = ald;
Which gives the error:
Presumably, you intend something like this:
SELECT *
FROM Playlist pl JOIN
User u
ON pl.playListOwner = u.id
WHERE u.id = 10;
This is just a guess, but your question mentions two tables and the query references only one of them. Plus, a self-join doesn't seem particularly useful.
First, if you want the column playlistOwner of the table Playlist as foreign key that references the column id of the table User you should define with the same data type as the referenced column.
So the correct definition should be playlistOwner INTEGER:
CREATE TABLE IF NOT EXISTS Playlist (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
image BLOB,
private NUMERIC,
playlistOwner INTEGER,
FOREIGN KEY(playlistOwner) REFERENCES User(id)
)
and the values that you store in the column playlistOwner must be the ids of the users and not their usernames.
Now if you know the user's id there is no need for a join.
You simply do:
SELECT *
FROM Playlist
WHERE playlistOwner = 10;
If you know only the username then you join the tables:
SELECT p.*
FROM Playlist p INNER JOIN User u
ON u.id = p.playlistOwner
WHERE u.username = 'somename'
or with a subquery in the WHERE clause:
SELECT *
FROM Playlist
WHERE playlistOwner = (SELECT id FROM User WHERE username = 'somename')

PostgreSQL can't aggregate data from many tables

For simplicity, I will write the minimum number of fields in the tables.
Suppose I have this tables: items, item_photos, items_characteristics.
create table items (
id bigserial primary key,
title jsonb not null,
);
create table item_photos (
id bigserial primary key,
path varchar(1000) not null,
item_id bigint references items (id) not null,
sort_order smallint not null,
unique (path, item_id)
);
create table items_characteristics (
item_id bigint references items (id),
characteristic_id bigint references characteristics (id),
characteristic_option_id bigint references characteristic_options (id),
numeric_value numeric(19, 2),
primary key (item_id, characteristic_id),
unique (item_id, characteristic_id, characteristic_option_id));
And I want to aggregate all the photos and characteristics of one item.
For a start, I got this.
select i.id as id,
i.title as title,
array_agg( ip.path) as photos,
array_agg(
array [ico.characteristic_id, ico.characteristic_option_id, ico.numeric_value]) as characteristics_array
FROM items i
LEFT JOIN item_photos ip on i.id = ip.item_id
LEFT JOIN items_characteristics ico on ico.item_id = i.id
GROUP BY i.id
The first problem here arises in the fact that if there are 4 entries in item_characteristics that relate to one item, and, for example, item_photos did not have entries, I get an array of four null elements in the photos field {null, null, null, null}.
So I had to use array_remove:
array_remove(array_agg(ip.path), null) as photos
Further, if I have 1 photo and 4 characteristics, I get a duplicate of 4 photo entries, for example: {img/test-img-1.png,img/test-img-1.png,img/test-img-1.png,img/test-img-1.png}
So I had to use distinct:
array_remove(array_agg(distinct ip.path), null) as photos,
array_agg(distinct
array [ico.characteristic_id, ico.characteristic_option_id, ico.numeric_value]) as characteristics_array
The decision is rather awkward as for me.
The situation is complicated by the fact that I had to add 2 more fields to item_characteristics:
string_value jsonb, --string value
json_value jsonb --custom value
And so I need to aggregate already 5 values ​​from item_characteristics, where 2 are already jsonb and distinct can have a very negative impact on performance.
Is there any more elegant solution?
Aggregate before joining:
SELECT i.id as id, i.title as title, ip.paths, null as photos,
ico.characteristics_array
FROM items i LEFT JOIN
(SELECT ip.item_id, array_agg( ip.path) as paths
FROM item_photos ip
GROUP BY ip.item_ID
) ip
ON ip.id = i.item_id LEFT JOIN
(SELECT ico.item_id,
array_agg(array [ico.characteristic_id, ico.characteristic_option_id, ico.numeric_value]
) as characteristics_array
FROM items_characteristics ico
GROUP BY ico.item_id
) ico
ON ico.item_id = i.id

Need a query to select specific comments with the name of the writer

I have a database contains 3 tables, as following:
CREATE TABLE users
(
id SERIAL PRIMARY KEY,
name VARCHAR(30) NOT NULL,
password VARCHAR(60) NOT NULL,
phone VARCHAR(10) NOT NULL,
email VARCHAR(30) UNIQUE
);
CREATE TABLE posts
(
id SERIAL PRIMARY KEY,
body TEXT NOT NULL,
user_id INTEGER REFERENCES users(id)
);
CREATE TABLE comments
(
id SERIAL PRIMARY KEY,
body TEXT NOT NULL,
post_id INTEGER REFERENCES posts(id),
user_id INTEGER REFERENCES users(id)
-- parent_id INTEGER REFERENCES comments(id)
);
I need a query to select all the comments for one specific post using the id of this post. What made me struggling is how to select the name of the user who wrote the comment!
This is what I tried :
select
c.body as cbody, p.body as pbody, c.user_id as user_id
from
users u
inner join
posts p on u.id = p.user_id
inner join
comments c on c.post_id = p.id
where
p.id=($1)
Any help??
I would use the Comments table as the first table in the FROM clause, since that's your main table for your query data. (Just personal preference, not a requirement.)
After that, I would join the Users table on Comments.user_id to get the comments' authors.
You probably do not want the post's body included with every comment, so I would leave that out.
So my query would look something like this:
SELECT c.body AS cbody, c.user_id AS user_id, u.name AS uname
FROM Comments c LEFT JOIN Users u ON u.id = c.user_id
WHERE c.post_id=($1);
An INNER JOIN might be valid too, but you need to be sure that Comments.user_id is always filled. When Comments.user_id is NULL, the comment will not be included in the queries result when INNER JOIN is used.

Display Titles From two other tables

I have a CONTAINS table in my DB:
CREATE TABLE CONTAINS(
album_id INTEGER NOT NULL REFERENCES Album,
song_id INTEGER NOT NULL REFERENCES Song,
PRIMARY KEY(album_id , song_id)
);
I have two Other Tables ALBUM and SONG:
CREATE TABLE ALBUM(
album_id INTEGER NOT NULL,
title TEXT NOT NULL,
PRIMARY KEY(album_id)
);
CREATE TABLE SONG(
song_id INTEGER NOT NULL,
title TEXT NOT NULL,
PRIMARY KEY(album_id)
);
If I do SELECT * FROM CONTAINS, I will get Album_Id and their respective Song_Id s. Now how do I display the album names and song names instead of their IDs?
Try this query:
select a.title as album_name,s.title as song_name
from contains c join album a
on c.album_id=a.album_id
join song s
on c.song_id=s.song_id;

Multiple Columns in one column

I have a Database which manages my music library, where I store all the id3 Tags in DB tables. The data model looks as followed: http://abload.de/img/modelq5sx3.png
CREATE TABLE Tracks (
Track_ID INTEGER NOT NULL AUTO_INCREMENT,
Title VARCHAR(255),
Track_Year YEAR,
Filename VARCHAR(255),
Track_Length CHAR(5),
Folder_ID INTEGER,
Album_ID INTEGER,
CONSTRAINT PK_Tracks PRIMARY KEY (Track_ID),
UNIQUE Unique_Path (Folder_ID, Filename)
);
CREATE TABLE Artists (
Artist_ID INTEGER NOT NULL AUTO_INCREMENT,
Artist VARCHAR(50),
CONSTRAINT PK_Artists PRIMARY KEY (Artist_ID),
UNIQUE (Artist)
);
CREATE TABLE Albums (
Album_ID INTEGER NOT NULL AUTO_INCREMENT,
Album VARCHAR(100),
AlbumArtist INTEGER,
AlbumCover VARCHAR(100),
CONSTRAINT PK_Albums PRIMARY KEY (Album_ID),
UNIQUE (Album)
);
CREATE TABLE Tracks_Artists (
Artist_ID INTEGER NOT NULL,
Track_ID INTEGER NOT NULL,
CONSTRAINT PK_Tracks_Artists PRIMARY KEY (Artist_ID, Track_ID)
);
So I have tracks,albums and artists in seperate entities with a n:m relation between tracks and artists as a track can be performed by more than one artist and a artist can perform in more than one track. I have done that to search for tracks by artist.
But to make a nice track view I want to have all these artists in one line per track, which I did not manage so far. All I can do is to join over Album, which has one album artist per album:
create or replace view v_c_tracks as SELECT t.traCK_ID,t.title,ar.arTIST,a.album
FROM TRACKS t
join albums a on t.album_id=a.album_id
join artists ar on a.albumartist=ar.artist_id
order by t.title;
You need to join to the track_artists table. Then to get the results on a single row, you need to aggregate by track. The key to bringing the artists together in a list is group_concat():
SELECT t.traCK_ID, t.title, group_concat(ar.artist) as artists, a.album
FROM tracks t join
albums a
on t.album_id = a.album_id join
track_artists ta
on ta.track_id = track.track_id join
artists ar
on ta.artist_id = ar.artist_id
group by t.traCK_ID, t.title, a.album
order by t.title;