multi-table query when there is no record in one table - sql

What should I do if I want to:
For now, there are table A and table B,
A:
id, name, address //the id is unique
B
id, contact, email
Since one person may have more than one contact and email, or have no contact and email(which means no record in table B)
Now I want to count how many records for each id, even 0:
And the result will look like:
id, name, contact_email_total_count
How can I do that(for now the only place I can not figure out is how to count 0 record since there is no record in table B)?

For that case you will want to use a LEFT JOIN, then add an aggregate and a GROUP BY:
select a.id,
a.name,
count(b.id) as contact_email_total_count
from tablea a
left join tableb b
on a.id = b.id
group by a.id, a.name
See SQL Fiddle with Demo
If you need help learning join syntax here is a great visual explanation of joins.
Based on your comment the typical order of execution is as follows:
FROM
ON
JOIN
WHERE
GROUP BY
HAVING
SELECT
ORDER BY

Need to do a left join to maintain the records in table A regardless of B:
PostgreSQL: left outer join syntax
Need to aggregate the count of records in B:
PostgreSQL GROUP BY different from MySQL?

Related

Oracle SQL: Selecting all, plus an extra column with a complex query

I have these tables setup:
NOMINATIONS: A table of award nominations
NOMINATION_NOMINEES: A table of users with a FK on NOMINATIONS.ID
One Nomination can be referenced by many nominees via the ID field.
SELECT a.*, COUNT(SELECT all records from NOMINATION_NOMINEES with this ID) AS "b"
FROM NOMINATIONS a
LEFT JOIN NOMINATION_NOMINEES b on a.ID = b.ID
The results would look like:
ID | NOMINATION_DESCRIPTION | ... | NUMBER_NOMINEES
Where NUMBER_NOMINEES is the number of rows in the NOMINATION_NOMINEES table with the current row's ID.
This is a tricky one, we are feeding this into a larger system so I'm hoping to get this in one query with a bunch of subqueries. Implementing subqueries into this has twisted my mind. Anyone have an idea of where to head with this?
I'm sure the above way is not close to a decent approach to this one, but I can't quite wrap my mind around this one.
It can be done with a single correlated sub-query in SELECT clause.
SELECT a.*,
( SELECT COUNT(b.ID) FROM NOMINATION_NOMINEES b WHERE a.ID= b.ID )
FROM NOMINATIONS a
You should be able to use count as an analytic function:
select a.*,
count(b.id) over (partition by b.id)
from nominations a
left join nomination_nominees b on a.id = b.id

Table without ID's in other Table

In my SQL Server 2008 I've got two tables.
Table: All kinds of Users with unique ID's
Table: Blacklisted Users with ID's
Now I'd like to get all Users that are not on the blacklist.
Just doesn't work like I want it to
SELECT A.ID, B.ID FROM Users AS A INNER JOIN Blacklist AS B ON
A.ID != B.ID
Can someone help?
If you expect it to not to be in Blacklist, you won't have any data to select from blacklist in select statement
SELECT A.*
FROM Users A
Where A.ID NOT IN (Select Id From Blacklist )
If you wish, read more about Subqueries with NOT IN
What you want is an anti-join, something like this:
SELECT A.ID, B.ID FROM Users AS A LEFT JOIN Blacklist AS B ON
A.ID = B.ID
WHERE B.ID IS NULL
That is, we perform the join, and then in the WHERE clause we apply a filter which eliminates rows where the join was successful.
Your original query doesn't work (assuming that there is more than one row in Blacklist and that they have different ID values) because, for any ID value in A, we can find a row in B which doesn't match it - even if there's also a row which does match it.
SELECT *
From Users
WHERE ID NOT IN (SELECT ID From BlackListedUsers)
That should select all that are not in the blacklist table.

SQL: How to add a column in the SELECT query result?

Usually we will select the field(s) in the SQL query. e.g.
SELECT A.id FROM Member A
But what if I want to align a column which elements correspond to the other selected field?
For example I want to select the member ID from a member table, and the COUNT that count how many times the member appear in the tuple of other table
So how do I make the COUNT column that align together with the select result?
If I understood you correctly, this is what you want:
SELECT A.id, count(B.MemberID)
FROM Member A
LEFT JOIN TableB B on A.id = B.MemberID
group by A.id
The LEFT JOIN will include records in A that do not have any corresponding records in B. Also, COUNT only counts non-null values, so you need to use it with B.MemberID. This way the count for records in A that do not have any corresponding records in B will be 0, since B.MemberID will be NULL.
I agree with #Adrian's solution, but if there were many columns in the original SELECT list, they all would have to be listed in GROUP BY. I mean something like this:
SELECT
A.id,
A.name,
A.whatever,
...
COUNT(B.member_id)
FROM Member A
LEFT JOIN Member_Something B ON A.id = B.member_id
GROUP BY
A.id,
A.name,
A.whatever,
...
It is not always convenient, especially when the columns are actually expressions. You could take another approach instead:
SELECT
A.id,
A.name,
A.whatever,
...
COALESCE(B.member_count, 0)
FROM Member A
LEFT JOIN (
SELECT member_id, COUNT(*) AS member_count
FROM Member_Something
GROUP BY member_id
) B ON A.id = B.member_id
select member_id, count(*)
from table
group by member_id;

How to use sql join where table1 has rows not present in table2

I have two tables record and share. record has columns: name and id. share has columns id.
I want to find the rows which are present in record but not present in share.
How can I do this?
SQL LEFT JOIN returns all rows from the left table even if there are no matches in the right table
SELECT name, id
FROM record r LEFT JOIN share s on r.id = s.id
WHERE s.id is null
You have this tables:
RECORD (ID, NAME)
SHARE(ID,VALUE)
If your SQL engine supports LEFT OUTER JOINS, the best way is:
SELECT RECORD.* FROM RECORD LEFT OUTER JOIN SHARE WHERE RECORD.ID=SHARE.ID
WHERE SHARE.ID IS NULL
Important
place an index on SHARE.ID
How it works:
SQL Engine span all the RECORD table looking for a record in SHARE, for each record in RECORD if it is found a "linked" record in SHARE the where clause is falso, so the record is not included in the result set, if no records are found in share the where clause is true and the RECORD.* is included in result set. This works thanks to LEFT OUTER JOIN.
Note:
Another way of doing the same thing is to use the WHERE RECORD.ID NOT IN (SELECT ID FROM SHARE).
Pay attention that depending on the sql engine you are using this may lead to serious performance issues because the internal engine can run the (SELECT ID FROM SHARE) once per record in RECORD table.
select Id from t1 where id not in (select id from t2)
SELECT * from RECORD where ID not in (SELECT DISTINCT ID FROM SHARE);
SELECT DISTINCT ID FROM SHARE - will get all the distinct IDs in the table share
SELECT * from RECORD where ID not in (SELECT DISTINCT ID FROM SHARE); would display all records whose ID is not in the first query.

How do I get a count of items in one column that match items in another column?

Assume I have two data tables and a linking table as such:
A B A_B_Link
----- ----- -----
ID ID A_ID
Name Name B_ID
2 Questions:
I would like to write a query so that I have all of A's columns and a count of how many B's are linked to A, what is the best way to do this?
Is there a way to have a query return a row with all of the columns from A and a column containing all of linked names from B (maybe separated by some delimiter?)
Note that the query must return distinct rows from A, so a simple left outer join is not going to work here...I'm guessing I'll need nested select statements?
For your first question:
SELECT A.ID, A.Name, COUNT(ab.B_ID) AS bcount
FROM A LEFT JOIN A_B_Link ab ON (ab.A_ID = A.ID)
GROUP BY A.ID, A.Name;
This outputs one row per row of A, with the count of matching B's. Note that you must list all columns of A in the GROUP BY statement; there's no way to use a wildcard here.
An alternate solution is to use a correlated subquery, as #Ray Booysen shows:
SELECT A.*,
(SELECT COUNT(*) FROM A_B_Link
WHERE A_B_Link.A_ID = A.A_ID) AS bcount
FROM A;
This works, but correlated subqueries aren't very good for performance.
For your second question, you need something like MySQL's GROUP_CONCAT() aggregate function. In MySQL, you can get a comma-separated list of B.Name per row of A like this:
SELECT A.*, GROUP_CONCAT(B.Name) AS bname_list
FROM A
LEFT OUTER JOIN A_B_Link ab ON (A.ID = ab.A_ID)
LEFT OUTER JOIN B ON (ab.B_ID = B.ID)
GROUP BY A.ID;
There's no easy equivalent in Microsoft SQL Server. Check here for another question on SO about this:
"Simulating group_concat MySQL function in MS SQL Server 2005?"
Or Google for 'microsoft SQL server "group_concat"' for a variety of other solutions.
For #1
SELECT A.*,
(SELECT COUNT(*) FROM A_B_Link WHERE A_B_Link.A_ID = AOuter.A_ID)
FROM A as AOuter
SELECT A.*, COUNT(B_ID)
FROM A
LEFT JOIN A_B_Link ab ON ab.A_ID=A.ID