SQL join adding rows with null values - sql

I'm doing a clone of Twitter using PostgreSQL. The users can write posts and they can share them. There is a timeline for each user, where it shows the posts and the shares of the people he follows. I also have to show the difference between a shared post (retweet) and a regular one by the original author. I have the following problem when joining the tables:
Post
post_id | user_id | text
1 1 text
Shares
post_id | user_id
1 2
Join_result
Post.post_id | Post.user_id | Post.text | Shares.post_id | Shares.user_id
1 1 text 1 2
Then I filter by Post.user_id or Shares.user_id. However, with this result, I don't know if I'm showing the post because it is a user 2 sharing or user 1 post. A good solution to me, it would be this join table:
Join_result
Post.post_id | Post.user_id | Post.text | Shares.post_id | Shares.user_id
1 1 text null null
1 1 text 1 2
Now I could filter correctly:
(Post.user_id in following and Share.user_id is NULL) or Share.user_id in followings
In this example, if the user follows user 1, it returns the first row; if he follows user 2 I get the second one and if he follows the two users, returns the two rows.
I've already solved this problem using a UNION query but I'd like to know if there is another solution (and better).
EDIT the query:
SELECT p0* f1.*, FROM "posts" AS p0
LEFT OUTER JOIN "forwards" AS f1 ON f1."post_id" = p0."id"
INNER JOIN (SELECT id FROM users AS u0
INNER JOIN follows AS f1 ON (f1.follower_id = $1) AND (f1.following_id = u0.id) UNION
SELECT id FROM users AS u1 WHERE (u1.id = $1)
) AS f3 ON (p0."author_id" = f3."id") OR (f1."user_id" = f3."id") ORDER BY p0."inserted_at" DESC

You are using a LEFT OUTER JOIN, in that case if no forward exists it will set null values in the results.
See this post for more information:
What is the difference between "INNER JOIN" and "OUTER JOIN"?

Related

postgres STRING_AGG() returns duplicates?

I have seen some similar posts, requesting advice for getting distinct results from the query. This can be solved with a subquery, but the column I am aggregating image_name is unique image_name VARCHAR(40) NOT NULL UNIQUE. I don't believe that should be necersarry.
This is the data in the spot_images table
spotdk=# select * from spot_images;
id | user_id | spot_id | image_name
----+---------+---------+--------------------------------------
1 | 1 | 1 | 81198013-e8f8-4baa-aece-6fbda15a0498
2 | 1 | 1 | 21b78e4e-f2e4-4d66-961f-83e5c28d69c5
3 | 1 | 1 | 59834585-8c49-4cdf-95e4-38c437acb3c1
4 | 1 | 1 | 0a42c962-2445-4b3b-97a6-325d344fda4a
(4 rows)
SELECT Round(Avg(ratings.rating), 2) AS rating,
spots.*,
String_agg(spot_images.image_name, ',') AS imageNames
FROM spots
FULL OUTER JOIN ratings
ON ratings.spot_id = spots.id
INNER JOIN spot_images
ON spot_images.spot_id = spots.id
WHERE spots.id = 1
GROUP BY spots.id;
This is the result of the images row:
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a
Not with linebreaks, I added them for visibility.
What should I do to retrieve the image_name's one time each?
If you don't want duplicates, use DISTINCT:
String_agg(distinct spot_images.image_name, ',') AS imageNames
Likely, there are several rows in ratings that match the given spot, and several rows in spot_images that match the given sport as well. As a results, rows are getting duplicated.
One option to avoid that is to aggregate in subqueries:
SELECT r.avg_raging
s.*,
si.image_names
FROM spots s
FULL OUTER JOIN (
SELECT spot_id, Round(Avg(ratings.rating), 2) avg_rating
FROM ratings
GROUP BY spot_id
) r ON r.spot_id = s.id
INNER JOIN (
SELECT spot_id, string_agg(spot_images.image_name, ',') image_names
FROM spot_images
GROUP BY spot_id
) si ON si.spot_id = s.id
WHERE s.id = 1
This actually could be more efficient that outer aggregation.
Note: it is hard to tell without seeing your data, but I am unsure that you really need a FULL JOIN here. A LEFT JOIN might actually be what you want.

Select with count on 3 tables

I need your help for a particular SELECT on 3 tables. I'm not skilled on SQL so it's a difficult SELECT for me, since I have to apply COUNT (I suppose) to the query.
I show you my tables:
I need to know how many contacts there are in the database (all the contacts!!!!) and how many photos and videos are bound to any contact.
I should get a result similar to this:
-----------------------------------
| ID | NAME | PHOTO | VIDEO |
-----------------------------------
| 1 | MARK | 3 | 1 |
-----------------------------------
| ID | LISA | 2 | 0 |
-----------------------------------
Thank you for your help
You can use the following approach, if you are hesitant about duplicates in the query you can use a sql function and pass type parameter as a string. If you have uncertain number of types (VIDEO, PHOTO, TEXT etc) you need to redesign the output table format (I would go with the following tuple TYPE, CONTACT_ID, COUNT), or at the worst case go with dynamic query construction.
select c.ID, c.NAME,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'PHOTO' where cm.ID_CONTACT = c.ID) as PHOTO,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'VIDEO' where cm.ID_CONTACT = c.ID) as VIDEO
from CONTACT c
Please use below query , this will give you exact result
select contact_media.ID_Contact, contact.Name, count(M1.ID) as 'PHOTO', COUNT(M2.ID) as 'VIDEO' from Contact inner join contact_media on Contact.ID=contact_media.ID_Contact
left outer join media M1 on contact_media.ID_Media=M1.ID and M1.TYPE='PHOTO'
left outer join media M2 on contact_media.ID_Media=M2.ID and M2.TYPE='VIDEO'
group by contact_media.ID_Contact, contact.Name

SQL query (Join without duplicates)

I have tables users and topics. Every user can have from 0 to several topics (one-to-many relationship).
How I can get only those users which have at least one topic?
I need all columns from users (without columns from topics) and without duplicates in table users. In last column I need number of topics.
UPDATED:
Should be like this:
SELECT user.*, count(topic.id)
FROM ad
LEFT JOIN topic ON user.id = topic.ad
GROUP BY user.id
HAVING count(topic.id) > 0;
but it takes 0 result. But it should not be 0.
Firstly you need to have your two tables, because you have left limited information about your table structure I will use an example to explain how this works, you should then be able to easily apply this to your own tables.
Firstly you need to have two tables (which you do)
Table "user"
id | name
1 | Joe Bloggs
2 | Eddy Ready
Table "topic"
topicid | userid | topic
1 | 1 | Breakfast
2 | 1 | Lunch
3 | 1 | Dinner
Now asking for a count against each user is done using the follwing;
SELECT user.name, count(topic.topicid)
FROM user
INNER JOIN topic ON user.id = topic.userid
GROUP BY user.name
If you use a left join, this will include records from the "user" table which does not have any rows in the "topic" table, however if you use an INNER JOIN this will ONLY include users who have a matching value in both tables.
I.e. because the user id "2" (which we use to join) is not listed in the topic table you will not get any results for this user.
Hope that helps!
use inner join and distinct
select distinct user_table.id
from user_table
inner join topics_table on topic_table.user_id = user_table.id
select u.id
, u.name
, count(b.topicName)
from user u
left join topic t on t.userid = u.id
group by u.id, u.name
You can select topic number per user and then join it with user data. Something like this:
with t as
(
select userid, count(*) as n
from topic
group by userid
)
SELECT user.*, t.n
FROM user
JOIN t ON user.id = t.userid

Select value in another table if reference is found

Alright, I'm pretty desperate. This is my first time using StackOverflow (I've always find all my answer but not this time). Please Community I need your help cause I'm not a SQL wiz. (I'm using Access).
I have 3 tables "User", "Type" and "User_update". I want to get the value from the "User" table but if there's an update for that user in the "User_update" table I would like to have the value from the "User_update" table instead and the date of the update. :-/
TABLE "USER"
id | user | type_id
--------------------
0 | bibi | 1
1 | toto | 1
TABLE "TYPE"
id | type
-----------
0 | admin
1 | normal
TABLE "USER_UPDATE"
id | user_id | type_id | date
-----------------------------------
0 | 1 | 0 | 9/3/2015
Would like to get something like this:
user | type | date
--------------------
bibi | normal |
toto | admin | 9/3/2015
Hope you guys can help!
I don't know Access, but here's an ANSI SQL answer:
select coalesce(uu.user, u.user), t.type, uu.date
from user u
left join user_update uu on u.id = uu.id
join type t on t.id = coalesce(uu.type_id, u.type_id)
The LEFT JOIN is there to read from user_update if a matching row is found. COALESCE returns the first non-null value, so if a user_update has been found that value is returned, otherwise user value is returned.
NOTE: In ANSI SQL date and user are reserved words, so these may need to be delimited, e.g. "date" or perhaps [date].
Access attempts:
select coalesce(uu.user, u.user), t.type, uu.date
from (user u
left join user_update uu on (u.id = uu.id))
join type t on (t.id = coalesce(uu.type_id, u.type_id))
Or perhaps:
select coalesce(uu.user, u.user), t.type, uu.date
from type t
join (user u
left join user_update uu on (u.id = uu.id))
on (t.id = coalesce(uu.type_id, u.type_id))
As someone new to access, I would recommend you design most of your queries using the "Design View". You want to left join the USER table on both tables, using the USER.type_id = TYPE.id and USER.user = USER_UPDATE.user
The following SQL should accomplish what you want, returning the "USER_UPDATE" record should it have a value, otherwise it will return the "TYPE" record.
You will need to look to see if there can be multiple records in "USER_UPDATE" for the same user, depending on your use, the return of multiple records may not be what you are looking for, you will need to build a select query to return only the most recent record in "USER_UPDATE"
SELECT USER.id, USER.user, USER.type_id, IIf([USER_UPDATE].[type] Is Null, [type].[type],[USER_UPDATE].[type]) AS user_type, USER_UPDATE.date
FROM ([USER] LEFT JOIN TYPE ON USER.type_id = TYPE.id) LEFT JOIN USER_UPDATE ON USER.user = USER_UPDATE.user;

include users having no records

In my relational Database and I have 2 tables:
user
id | userName | email | col1 | col2
log
ID | user_id | cl1 | cl2 | cl3
And I want in my admin section to view all the users along with the count of their records.
I am using SQLite3 as my DB
SELECT user_id,username,email,count(link)'count'
FROM log,user
where user_id=user.id group by user_id;
it works fine but doesn't include users having no record in log table please help how can I make it include all the users and show the count value as 0.
Use LEFT OUTER JOIN to include all the rows from user instead of old style of comma separated INNER JOIN
SELECT user.id,username,email,count(link)'count'
FROM user
LEFT OUTER JOIN Log
ON Log.user_id=user.id
group by user.id;