Join table on itself without two queries - mysql

I've seen people recommending cross joining a table on itself by doing this:
SELECT *
FROM tbl AS A, tbl AS B
WHERE A.col1 = 1 AND B.col1 = 1
But here, the engine needs to iterate through all of the rows in tbl twice to match the two queries to the results of A and B, despite the fact that the queries (and therefore the results) are the same.
Assuming that the WHERE on A and B will always be the identical for the two, this is a waste. Is there any way to query for something once, and then cross join the result of that query on itself? I'd like to avoid temp tables, which would require disk writing instead of performing this entire thing in RAM.
I am using MySQL, although any SQL answer would help a lot.
EXAMPLE:
Suppose that tbl looks as follows:
COL1 COL2
1 A
1 B
1 C
2 D
2 E
When I run my where clause of col1 = 1, it returns the first three rows from the above table. What I want is the following table, but with only one execution of the where statement, since the two tables A and B are identical:
A.COL1 A.COL2 B.COL1 B.COL2
1 A 1 A
1 A 1 B
1 A 1 C
1 B 1 A
1 B 1 B
1 B 1 C
1 C 1 A
1 C 1 B
1 C 1 C

You are basically asking for an intentional Cartesian join
select
a.col1,
a.col2,
b.col1,
b.col2
from
tbl a
join tbl b
on a.col1 = b.col1
where
a.col1 = 1
order by
a.col2,
b.col2
To exactly hit your output order sequence, you need the order by by the "a" column 2 then "b" column 2

I really recommend avoiding that JOIN syntax... it can be very difficult to read.
Your explanation of what you are trying to do is a bit cryptic. The query as written offers no value for a JOIN operation. Generally speaking, when you want to JOIN a table to itself, it's on different columns:
select *
from tbl as a
inner join
table as b
on a.col1 = b.col2
where
a.col1 = 1;
This allows you to query against the table, and also collect related information organized in a hierarchical fashion in the same table. For example:
create table tbl (
person_id int,
parent_id int
);
In this case, a parent is a person too. If you wanted to get a list of the parents related to the person with an ID of 1, you could write:
select
person.person_id as OriginalPerson,
parent.person_id as Parent
from
tbl as person
inner join
tbl as parent
on parent.person_id = person.person_id
where
person.person_id = 1;
UPDATE Upon reading your further explanation, you want a cartesian product:
select a.*, b.*
from tbl as a
inner join tbl as b
on 1=1
where a.col1 = 1
and b.col1 = 1

Related

How to get values from tables connected thrugh a middle table, using mysql

Table A
a_id
a_name
1
apples
2
bananas
3
cherries
4
oranges
Table B
b_id
b_name
1
Hot
2
Cold
Table C
c_id
a_id
b_id
1
1
2
2
2
1
3
3
2
4
4
1
5
4
2
I am trying to get resulting table, which should show "a_name" and "b_name" with the following condition: where a_name like '%r%' and b_name like '%o%'.
Problem is that they should be found within the same row in "Table_C".
I've tried various methods of joining tables but I'm unable to get the desired result.
Here's my best coding attempt at this problem:
SELECT a.a_name,
b.b_name
FROM Table_A a
WHERE a.a_name LIKE '%r%' IN (SELECT a.a_id
FROM Table_c
WHERE b_id LIKE '%o');
Any help would be much appreciated.
The problem with your query is that you're trying to extract values of "Table B" by using a filtering operation (WHERE clause). As long as the filtering clause "just filters" - reduces the number of rows of your table - , you can't access "b.b_name" inside your SELECT clause if the FROM contains only "Table A".
In this case you may want to use two JOIN operations in place of WHERE .. IN .. construct. The main table is "Table C" because it connects "Table A" and "Table B" together. Since you have two conditions to be applied on the names, in order to make this more efficient, you can filter on the two tables ("Table A" and "Table B") before applying the JOIN operation. In this way the DBMS will apply the matching on less rows.
SELECT A.a_name,
B.b_name
FROM tabC C
INNER JOIN (SELECT * FROM tabA WHERE a_name LIKE '%r%') A
ON C.a_id = A.a_id
INNER JOIN (SELECT * FROM tabB WHERE b_name LIKE '%o%') B
ON C.b_id = B.b_id
Check the demo here.
select
A.a_name,
B.b_name
from TableA A
inner join TableC C on C.a_id = A.a_id
inner join tableB B on B.b_id = C.b_id
where A.a_name like '%r%' and B.b_name like '%o%';
The inner join 'glues' the table together on the condition specified after the ON
The where clause is a copy provided by you. I Just added the table aliases for clarity.
Try below code:
select tableA.a_name, tableB.b_name
from tableC
left join tableA on tableA.a_id=tableC.a_id
left join tableB on tableB.b_id=tableC.b_id
where tableA.a_name like '%r%' and tableB.b_name like '%o%'

SQL: how to select customers who have ordered multiple items

here is some sample data:
ID Item
1 A
1 A
1 B
2 A
2 A
3 A
3 A
3 A
Question: Im trying to write code so that the only records that are selected are those of customer with ID 1 (ie a customer that has both product A and B). So results should look like this:
1 A
1 A
1 B
I've tried a lot of different things, but I am stuck. I tried self-join, but it doesnt produce what I want:
SELECT a.id, a.item
FROM table1 a Join table1 b on a.id=b.id
WHERE upper(a.item) = 'A'
AND upper(b.item) = 'B';
This will give me the right customer (ie customer 1) but it doesnt pull all 3 records. It just gives 1 row.
the closest similar question is
enter link description here
since you want to see which users match a certain condition and the fetch everything about those users - you need a nested query:
SELECT id,item FROM table1 WHERE id IN(
SELECT a.id
FROM table1 a Join table1 b on a.id=b.id
WHERE upper(a.item) = 'A'
AND upper(b.item) = 'B'
)
I took your working query, and used it in a WHERE clause for a more generic query - should do the trick for you
You could use your query as subselect to get the pid and then output all the pids rows. Like this:
SELECT id, item
FROM table1
WHERE id IN (SELECT a.id
FROM table1 a
JOIN table1 b
ON a.id=b.id
WHERE UPPER(a.item) = 'A'
AND UPPER(b.item) = 'B')
is it this what you are looking for? or may be I didn't understand your question.
SELECT a.pid, a.mname
FROM meds a
WHERE a.pid = 1

SQL Query to compare two tables for names

I am building a SQL query which compares two tables A and B by a [name] column and returns the names from table A that are not in table B
Example
Table A
ID Name Address
1 A ABC
2 B XYZ
3 C PQR
Table B
ID Name Gender
1 A F
2 B M
3 D F
The query I wrote should return third row from table A as it is not in table B and should exclude all other rows
Following is the query I built
Select * from A oa left join B gp ON oa.name!=gp.name
the above doesn't return the results I was expecting.
Can this be corrected?
Easiest way:
select * from A where name not in (select name from B)
Better way:
select * from A where not exists (select 1 from B where B.name = A.name)
"A left join B" means keeping everything in A, and associating records in B if the condition is satisfied.
In your case, if you really wanna use left join, here is what it should be ('=', not '!='):
Select * from A oa left join B gp ON oa.name=gp.name where gp.name is null
Better way would be using 'not exists' performance-wise, or 'except' if null values are not an issue.
Using excpet operator will help
select * from TableA
except
select * from TableB
SELECT a.*
FROM A a
LEFT JOIN B b
ON a.name = b.name
WHERE b.name IS NULL

Unusual MySQL behavior with select query

I have 3 tables A, B and C:
Table A is small (~1000 rows).
Table B has ~200,000 rows.
Table C has ~2.2 million rows.
I'm running a query like this:
SELECT A.Id FROM A, B, C
WHERE A.Id = B.SomeId OR (A.Id = C.SomeId AND C.SomeValue = 'X')
INTO OUTFILE '/tmp/result.txt';
A.Id is the primary key of table A
B.SomeId has an index set up
Edit: C.SomeId has an index set up
C.SomeVal has an index set up too but it's a VARCHAR(1) with only two possible values
I thought this would only have to iterate over each Id in table A (1000 rows) and then potentially query across the other tables (depending on whether MySQL short circuits, I don't know if it does).
But the query seems to hang, or at least it's taking a very long time. Much longer than I would have expected if it only had to iterate 1000 rows. 10 minutes in and the output file is still empty. Let me know if I can provide any more information.
my#laptop$ mysql --version
mysql Ver 14.14 Distrib 5.5.37, for debian-linux-gnu (i686) using readline 6.3
Edit:
The result I'm looking for is 'Give me all the Id's in table A where the Id matches B.SomeId OR ELSE the Id matches C.SomeId AND C.SomeValue equals 'X'.
OR expressions often make it difficult for MySQL to use indexes. Try changing to a UNION:
SELECT A.id
FROM A
JOIN B ON A.id = B.SomeID
UNION
SELECT A.id
FROM A
JOIN C ON A.id = C.SomeID
WHERE C.SomeValue = 'A'
From the documentation:
Minimize the OR keywords in your WHERE clauses. If there is no index that helps to locate the values on both sides of the OR, any row could potentially be part of the result set, so all rows must be tested, and that requires a full table scan. If you have one index that helps to optimize one side of an OR query, and a different index that helps to optimize the other side, use a UNION operator to run separate fast queries and merge the results afterward.
Your query is described by the last sentence: you have different indexes for each side of the OR query.
Let's go even smaller. Let's say that your tables look like this:
A.ID
1
2
B.SomeID
1
3
C.SomeID | C.SomeValue
1 | X
2 | X
Now, let's see what your query will do. First, we look to see if A.ID match and B.SomeID match. In the case of A.ID = 1, we have a match! Sql short circuits. This means that if the first part of your or is true, sql doesn't evaluate the 2nd part of your or. Now, we still have to join with table C. Since there is no join condition, for table C sql matches A.ID with all the columns in table C.
Now we need to compare A.ID with the next row in B. Well, 1 <> 3. So, we move on to the second part of the or. When C.SomeID = 1, the row is included. When C.SomeID = 2, the row is not included. Your results for A.ID = 1 are:
A.ID | B.SomeID | C.SomeID | C.SomeValue
1 | 1 | 1 | X
1 | 1 | 2 | X
1 | 3 | 1 | X
This is clearly not the results table that you are looking for. Since you are going to join A with either table B or C, instead of an or, you should use a union
SELECT A.Id FROM A, B
WHERE A.Id = B.SomeId
Union All
Select A.ID From A, C
Where A.Id = C.SomeId AND C.SomeValue = 'X'
Union all puts the results from the first query into the same results table as the results from the second query. Now, your question says that you only want the A.IDs that are in one table but not the other (or else). There are several ways to do this. In this case, I am going to use a having and a subquery. You could also use a not exists but I believe that having is going to use less resources.
Select T.ID
From
(SELECT A.Id FROM A, B
WHERE A.Id = B.SomeId
Union All
Select A.ID From A, C
Where A.Id = C.SomeId AND C.SomeValue = 'X') T
Group By T.ID
Having count(1) = 1
We only want the Ids that show up exactly one time. This will only work if the id is not repeated in B or C, so keep that in mind. Since the condition is based on the aggregate function, count, this stipulation must be in the having.
I'm not strong in MySQL, but I think this would work better:
SELECT Id FROM
( SELECT A.Id FROM A, B
WHERE A.Id = B.SomeId
UNION
SELECT A.Id FROM A, C
WHERE A.Id = C.SomeId AND C.SomeValue = 'X'
) X
INTO OUTFILE '/tmp/result.txt';

mysql exclude problem

table a
_______________________________
id col1 col2 col3 ...........col20
1 ............................
2 ............................
3 ............................
table b
_______________________________
id colA colB colC colD ...... colZ
query
________________________________
select a.*, b.* from a left join b on b.id = a.col20 where a.id = 1;
In this query table a and b has same column name.
And I need both of them.
select a.id as a_id .. b.id as b_id .. from a left join b on b.id = a.col20 where a.id = 1;
How to avoid typing all column name?
As far as I know there is not an easy way to select * and exclude columns, and it requires putting the full column list, but I'm sure there are other possibilities.
One way of doing this which would make those a.*, b.* type queries work, but requires some initial setup, is to create a view for the table which aliases all of the columns.
the view of a would be a select query with all of the column names aliased.
create view aview as
select id as a_id,
col1 as a_col1,
col2 as a_col2,
...
...
from a
Then anywhere else you could do something like this:
select a.*, b.*
from aview a
left join bview b on b.b_id = a.a_col20
where a.a_id = 1
If the example were that simple and you really only had 2 tables, it would be sufficient to just make a view for one of them.
Hackish, maybe.. I'd probably look to permanently change the column names on the base tables.