How do I select group wise from a second table - sql

This one is hard to explain and I'm sure I will facepalm when I see the solution, but I just can't get it right...
I have three tables:
Table A contains new records that I want to do something with.
Table B contains all activities from Table C of a specific type (done beforehand).
Table C is sort of a "master" table that contains all activities as well as a customer id and a lot of other stuff.
I need to select all activities that is in Table A from Table B. So far so good.
The part I can't get together is that I also need all the activities from Table B that has the same customer id as an activity contained in Table A.
This is what I'm after:
activity
2
3
4
5
6
The trick here is to get activity 2, because activity 2 is also done by customer 2, even though it is not in Table A.
Here are the tables:
Table A (new records)
activity
3
4
5
6
Table B (all records of a specific type from Table C)
activity
1
2 <-- How do I get this record as well?
3
4
5
6
Table C (all records)
activity customer
1 1
2 2
3 2
4 3
5 3
6 4
7 5
I tried something like this...
SELECT *
FROM table_b b
INNER JOIN table_c c
ON c.activity = b.activity
INNER JOIN table_a a
ON a.activity = b.activity
... but of course it only yields:
activity
3
4
5
6
How can I get activity 2 as well here?

To do this returning one column I would recommend staging the customer_ids of activities in Table_b that are in Table_a into a CTE (common table expression MSDN CTE) then select activities in table_c and join to the CTE to get only activities with a valid customer_id.
example of CTE: (Note the semi-colon ; before the WITH keyword is workaround for an issue in SQL 2005 with multiple statements. It it not necessary if you are in a newer version, or not running batch statements.)
;WITH cte_1 AS (
SELECT distinct c.customer --(only need a distinct result of customer ids)
from table_b b
join table_a a on b.activity = a.activity --(gets only activities in b that are in a)
join table_c c on b.activity = c.activity --(gets customer id of activies in table b)
)
SELECT a.activity
FROM table_c a
JOIN cte_1 b ON a.customer = b.customer
Alternatively you could do this in three joins with a select distinct. However I find the CTE to be an easier way to develop and think about this problem regardless of the way you decide to implement your solution. Although the three join solution will most likely scale and perform better over time with a growing data-set.
Example:
SELECT distinct d.activity
from table_b b
join table_a a on b.activity = a.activity --(gets only activities in b that are in a)
join table_c c on b.activity = c.activity --(gets customer id of activies in table b)
join table_c d ON c.customer = d.customer
Both would output:
2
3
4
5
6

Here is one way to do it
SELECT *
FROM TableB b1
WHERE EXISTS (SELECT 1
FROM Tablec c1
WHERE EXISTS (SELECT 1
FROM TableA a
INNER JOIN Tablec c
ON a.activity = c.activity
WHERE c.customer = c1.customer)
AND c1.activity = b1.activity)

Can you try doing a left join?
SELECT *
FROM table_b b
INNER JOIN table_c c
ON c.activity = b.activity
LEFT JOIN table_a a
ON b.activity = a.activity

Related

Return all inactive items from Table B for each item in Table A

Not sure if I can explain this well in words..
I want to find all items in Table B that have ALL items inactive for each item in Table A. Let's say Table A just has column "item" and Table B has columns "item" and "active"
Table A Table B
A A | 1
A A | 1
A B | 1
B B | 0
B C | 0
B C | 0
C D | 0
C E | 1
D
E
F
In that example, it should return C and D.
I managed to join tables A and B together with a group by clause but not sure where to go from there. Tried looking around and only found answers where the other table's value doesn't exist so you can use "NOT IN" but that doesn't seem to work here.
Any help would be appreciated!
You can join the tables and use the HAVING clause to make the comparison like this:
SELECT ta.Item
FROM TableA tA
LEFT JOIN TableB tB
ON tA.Item=tB.Item
GROUP BY tA.Item
HAVING SUM(tB.Inactive)=COUNT(tb.Inactive)
This would give you a distinct list of Items in TableA
The above query assumes 1 is Inactive and 0 is Active. If your data is opposite (which it looks like it is), you could instead say:
SELECT ta.Item
FROM TableA tA
LEFT JOIN TableB tB
ON tA.Item=tB.Item
GROUP BY tA.Item
HAVING SUM(tB.Inactive)=0
This would also return Item F as it doesn't have a value in table b to SUM. Just flip the LEFT JOIN to an INNER JOIN if you don't want Item F to return.
If you need to return back all instances in TableA, you could use a subquery and join to that:
SELECT ta.Item
FROM TableA tA
LEFT JOIN (SELECT ITEM, SUM(ACTIVE) Sum0 FROM TableB GROUP BY ITEM)tB
ON tA.Item=tB.Item
WHERE tB.Sum0 = 0
/*or tB.Sum0 is null would give you Item F*/
Use NOT EXISTS:
select distinct a.item
from table_A a
where not exists (select 1 from table_B b where b.item = a.item and b.status = 1);
I believe you would just need to join the tables together on table name and filter to only value of 0 on the active column.
SELECT B.*
FROM TableA A INNER JOIN TableB B ON A.item = B.item
WHERE B.active = 0
Does that get you what you need?

SQL Logic: When joining Child table B to Parent Table A on A.FID = B.ID

I have been wondering if the results would change in multi-join tables queries.
If you have parent Table A
A B
ID|FID FID
1|2 1
2|4 2
3|5 3
4|7 4
5|8 5
6|NULL 6
7|NULL 7
8|NULL 8
does it matter which table column you specified in the WHERE clause?
For example, what is the difference between the two:
Select *
From Table A
Left Join B on A.FID = B.FID
WHERE A.FID IN (2,5,8)
Select *
From Table A
Left Join B on A.FID = B.FID
WHERE B.ID IN (2,5,8)
Thank you for the help!
EDIT:
Micheal has solved my question and I have tested it out
'Actually, while your answer is a good one (and probably the one he's looking for), since both of his queries are essentially filtering on the primary key of B (A.FID, B.ID), they actually are logically identical (assuming that A.FID is a true foreign key constraint on B). That is, both queries filter out rows in which B.ID is not 2, 5 or 8.' – Michael L.
It is only different is Table B is the main table and you queried based on B.ID as in:
SELECT *
FROM B
LEFT JOIN A ON A.FID = B.FID
WHERE B.FID IN (2,5,8)
While this will be the same as having A as the main table:
SELECT *
FROM B
LEFT JOIN A ON A.FID = B.FID
WHERE A.FID IN (2,5,8)
Yes, it does.
When you use an OUTER JOIN, values from one of the tables may be NULL. So, the second query is equivalent to:
Select *
From Table A Inner Join
B
on A.FID = B.ID
WHERE B.ID IN (2, 5, 8);
because the NULL values are filtered out.
As a general rules with LEFT JOIN:
Filters on the first table belong in the WHERE clause.
Filters on the second and subsequent tables should to in the ON clause.

SQL: Sort a table by the first relation to a second table

I have a Many-to-Many relationship between two tables. I'd like to sort the first table by the first relationship with the second table and only return a single result from that table. This is on SQL Server. I'd like something like this:
SELECT a.retrieve_me
FROM table_A AS a
JOIN table_B AS b ON a.foo = b.foo
JOIN table_C AS c ON b.bar = c.bar
ORDER BY c.sort_me
Unfortunately it returns MN(k) results, where M is the count of "table_A" and N(k) is the number of relations of a single row k with "table_C." To have it return just the results I wanted without post filtering I tried using DISTINCT on the SELECT clause and using TOP(SELECT COUNT(*) FROM table_A) but neither are valid syntax.
Any ideas? Hoping I can make this as performant as possible.
EDIT:
For clarity
table A
------------
"joe" 1
"betty" 2
"george" 3
table B
------------
1 2
1 3
2 3
2 4
3 1
table C
------------
1 "ashton"
2 "harding"
3 "spring"
4 "merry lane"
I'd like the results returned in the order of "george", "joe", and "betty" which is in the order (george -> ashton, joe -> harding, betty -> merry lane.)
If I understood correctly what you need, cause I think is very hard to follow you .. this should do it:
SELECT a.nm
FROM tablea a
cross apply (select top 1 *
from tableb b
join tablec c on b.id2 = c.id
where a.id = b.id1
order by c.nm) bc
order by bc.nm
http://sqlfiddle.com/#!3/661c0/5/0

Select from one table matching criteria in another?

I'd really appreciate some help with an SQL query across tables. I realise this sort of thing is asked constantly, but I can't find a similar enough question to make sense of the answers.
I want to select rows from table_A that have a corresponding tag in table_B.
So, for example, " select rows from table_a which are tagged 'chair' " would return table_C.
Also, id is a unique in table_a, and not in table_b.
table_A: table_B: table_C:
id object id tag id object
1 lamp 1 furniture 3 stool
2 table 2 furniture 4 bench
3 stool 3 furniture
4 bench 4 furniture
4 chair
3 chair
Alternatively, is there a better way to organise the data?
The simplest solution would be a correlated sub select:
select
A.*
from
table_A A
where
A.id in (
select B.id from table_B B where B.tag = 'chair'
)
Alternatively you could join the tables and filter the rows you want:
select
A.*
from
table_A A
inner join table_B B
on A.id = B.id
where
B.tag = 'chair'
You should profile both and see which is faster on your dataset.
You should make tags their own table with a linking table.
items:
id object
1 lamp
2 table
3 stool
4 bench
tags:
id tag
1 furniture
2 chair
items_tags:
item_id tag_id
1 1
2 1
3 1
4 1
3 2
4 2
select a.id, a.object
from table_A a
inner join table_B b on a.id=b.id
where b.tag = 'chair';
I have a similar problem (at least I think it is similar). In one of the replies here the solution is as follows:
select
A.*
from
table_A A
inner join table_B B
on A.id = B.id
where
B.tag = 'chair'
That WHERE clause I would like to be:
WHERE B.tag = A.<col_name>
or, in my specific case:
WHERE B.val BETWEEN A.val1 AND A.val2
More detailed:
Table A carries status information of a fleet of equipment. Each status record carries with it a start and stop time of that status. Table B carries regularly recorded, timestamped data about the equipment, which I want to extract for the duration of the period indicated in table A.

Bidirectional outer join

Suppose we have a table A:
itemid mark
1 5
2 3
and table B:
itemid mark
1 3
3 5
I want to join A*B on A.itemid=B.itemid both right and left ways. i.e. result:
itemid A.mark B.mark
1 5 3
2 3 NULL
3 NULL 5
Is there a way to do it in one query in MySQL?
It's called a full outer join and it's not supported natively in MySQL, judging from its docs. You can work around this limitation using UNION as described in the comments to the page I linked to.
[edit] Since others posted snippets, here you go. You can see explanation on the linked page.
SELECT *
FROM A LEFT JOIN B ON A.id = B.id
UNION ALL
SELECT *
FROM A RIGHT JOIN B ON A.id = B.id
WHERE A.id IS NULL
Could do with some work but here is some sql
select distinct T.itemid, A.mark as "A.mark", B.mark as "B.mark"
from (select * from A union select * from B) T
left join A on T.itemid = A.itemid
left join B on T.itemid = B.itemid;
This relies on the left join, which returns all the rows in the original table (in this case this is the subselect table T). If there are no matches in the joined table, then it will set the column to NULL.
This works for me on SQL Server:
select isnull(a.id, b.id), a.mark, b.mark
from a
full outer join b on b.id = a.id