SQL Chaining joins - mysql

EDIT: The isssue was mismatching data the B-ids in tables A_B and B_C had no overlap
I have the following tables:
A(id, …)
A_B(a_id, b_id)
B_C(b_id, c_id)
C_D(c_id, d_id)
D(id, value, …)
Where I want some rows from A based on the value of the row in D.
The tables A_B, B_C and C_D are just id mappings from one table to another.
I was trying to get to something that would look something like this:
select * from A where D.value = "true"
I got this far:
select * from A
inner join A_B on A_B.a_id = A.id
inner join B_C on B_C.b_id = A_B.b_id
which is just an empty table.
I am starting to think that I am approaching this issue in the wrong way or perhaps have misunderstood how one should go about joining tables.

What you've got works OK if your data is coherent. Here's a version of your database and query which demonstrates that. For simplicity, I inferred the existence of tables b and c, and made the various columns in the X_Y style tables into foreign keys to the single-letter tables.
CREATE TABLE a
(
id INTEGER NOT NULL PRIMARY KEY,
info VARCHAR(20) NOT NULL
);
CREATE TABLE b
(
id INTEGER NOT NULL PRIMARY KEY,
data VARCHAR(20) NOT NULL
);
CREATE TABLE C
(
id INTEGER NOT NULL PRIMARY KEY,
extra VARCHAR(20) NOT NULL
);
CREATE TABLE d
(
id INTEGER NOT NULL PRIMARY KEY,
value VARCHAR(20) NOT NULL
);
CREATE TABLE a_b
(
a_id INTEGER NOT NULL REFERENCES a,
b_id INTEGER NOT NULL REFERENCES b,
PRIMARY KEY (a_id, b_id)
);
CREATE TABLE b_c
(
b_id INTEGER NOT NULL REFERENCES b,
c_id INTEGER NOT NULL REFERENCES C,
PRIMARY KEY(b_id, c_id)
);
CREATE TABLE c_d
(
c_id INTEGER NOT NULL REFERENCES C,
d_id INTEGER NOT NULL REFERENCES d,
PRIMARY KEY(c_id, d_id)
);
INSERT INTO a VALUES(1, "Quasimodo");
INSERT INTO b VALUES(20, "Quiet");
INSERT INTO C VALUES(333, "Contemporaneous");
INSERT INTO d VALUES(4444, "true");
INSERT INTO a_b VALUES(1, 20);
INSERT INTO b_c VALUES(20, 333);
INSERT INTO c_d VALUES(333, 4444);
SELECT *
FROM a
JOIN a_b ON a_b.a_id = a.id
JOIN b_c ON b_c.b_id = a_b.b_id
JOIN c_d ON c_d.c_id = b_c.c_id
JOIN d ON d.id = c_d.d_id
WHERE d.value = "true";
For the given data, that produces:
1 Quasimodo 1 20 20 333 333 4444 4444 true
So, if the data is correct, the query you were building can produce an answer. However, if you were getting an empty table on your incomplete query, then there is a data problem in your tables — or (outside chance) your outline schema misled us.
Testing performed on a Mac running macOS High Sierra 10.13.4, using Informix 12.10.FC6, but using what is believed to be a subset of SQL common to Informix and MySQL.

Related

SQL - Foreign Key and Primary Key not Matching

I am new to SQL and I am running into trouble.
I have 3 tables:
CREATE TABLE indexCodes
{
(indexNum VARCHAR(5) PRIMARY KEY,
courseString VARCHAR(10),
title VARCHAR(20)
}
CREATE TABLE user
{
(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL)
}
CREATE TABLE snipes
{
(snipeNumber INT NOT NULL PRIMARY KEY AUTO_INCREMENT),
FOREIGN KEY indexNum REFERENCES indexcodes(indexNum),
FOREIGN KEY userID REFERENCES user(id)
}
and inserting into snipes with
INSERT INTO snipes(indexNum, userID) VALUES ("06666", 1);
INSERT INTO snipes(indexNum, userID) VALUES ("06675", 1);
When I run
SELECT * FROM snipes, user, indexcodes where id=1
two indexNum columns appear with two different values, I am assuming that the snipes indexNum column shows whatever was inserted but the indexCodes indexNum displays a different result (I believe it is the first two entries in indexCodes)
When you put all the tables in the FROM clause divided only by comma without any restrictions of joining the tables with a WHERE clause you get a cartesian product of all the tables referenced in the FROM clause, for example, or using JOIN syntax instead of putting all the tables int the FROM clause. Here is two examples that would work on your scenario:
WHERE clause
SELECT * FROM snipes s, t_user u, indexcodes i where
s.indexNum = i.indexnum
and s.userid = u.id
and id=1;
JOIN syntax
SELECT * FROM snipes s
inner join t_user u on s.userid = u.id
inner join indexcodes i on s.indexNum = i.indexnum
where id=1;
Personally, I prefer to use the join syntax, it's a cleaner way to see the query.

inner join on null condition : bug or feature?

Here is the test setup:
CREATE TABLE A (
id bigint NOT NULL AUTO_INCREMENT,
value bigint,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO A (id, value) VALUES (1, 22);
INSERT INTO A (id, value) VALUES (2, 25);
INSERT INTO A (id, value) VALUES (3, 25);
CREATE TABLE B (
id bigint NOT NULL AUTO_INCREMENT,
value bigint,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Important note: table B does not contain any row!!!
Test query:
select * from A inner join B on (A.value=25 OR B.value=null);
Surprise: Empty result set fetched
If table B contain anything like:
INSERT INTO B (id, value) VALUES (3, 66);
Then the same query will return 2 rows:
id value id value
-- ----- -- -----
2 25 3 66
3 25 3 66
This is a bug or a feature of MySQL?
INNER JOIN by definition returns matching records only. If a table does not have any records, then there cannot be any metching record. This is a standard behaviour across all RDBMs. Use left or right join instead of inner if you want to return rows from a table regardless of matching rows from another.
select * from A left join B on ... where A.value=25 ;
Moreover, anything=NULL comparison will always retur false, because NULL does not equal to anything, not even to another NULL value. If you want to test if a field has a value of NULL, then use fieldname IS NULL expression.

Retrieve rows with more than 2 relations in another table

I have two tables, table_a and table_b. table_a has the following schema :
CREATE TABLE table_a (
a_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
a VARCHAR(255) NOT NULL UNIQUE,
b_id INT(11)
);
and table_b :
CREATE TABLE table_b (
b_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
b VARCHAR(255) NOT NULL UNIQUE
);
Each element in table_a refers (in table_a.b_id) to one element of table_b.
I want a query that would output every element of table_b for which there are 2 elements or more referencing to it in table_a (and for each row, I'd like it to display how many elements in table_a refer to it)
Thanks
Try this query:
SELECT b.b_id, b.b, t.b_count
FROM table_b b INNER JOIN
(
SELECT a.b_id, COUNT(*) AS b_count
FROM table_a a
GROUP BY a.b_id
HAVING COUNT(*) > 1
) t
ON b.b_id = t.b_id
This avoids a single GROUP BY query which would contain ambiguous columns (and therefore would not run on SQL Server and some other flavors).

MySQL Prepared Statement table 3th

I am not able to get data using the join query with 3Th table.
Table p:
- id
- name
Table s:
- id
- name
- description
Table ps:
- p_id
- s_id
$stmt = $this->conn->prepare("SELECT p.id, p.name FROM p, ps WHERE p.id = 1 AND ps.p_id = 1 AND ps.s_id = 1");
Here's the error: Call to a member function execute() on a non-object
Thanks
Two question:
(1) Have you tried running your query directly from the MySQL command line? And if so does it give you your desired result?
(2) Could you show more of your code. Perhaps the problem located some where else in your code.
For a start try this explicit JOIN query:
SELECT p.id, p.name
FROM p
JOIN ps
ON p.id = ps.p_id
JOIN s
ON ps.s_id = s.id
WHERE s.id = 1;
Sample data:
CREATE TABLE p
(
id int auto_increment primary key,
name varchar(20)
)ENGINE=InnoDB;
INSERT INTO p
(name)
VALUES
('Bob'),
('Jack'),
('John');
CREATE TABLE s
(
id int auto_increment primary key,
name varchar(30)
)ENGINE=InnoDB;
INSERT INTO s
(name)
VALUES
('blablabla'),
('Foo'),
('Foobar');
CREATE TABLE ps
(
id int auto_increment primary key,
p_id int,
s_id int,
FOREIGN KEY (p_id) REFERENCES p(id),
FOREIGN KEY (s_id) REFERENCES s(id)
)ENGINE=InnoDB;
INSERT INTO ps
(p_id,s_id)
VALUES
(1,1),
(1,2),
(2,1),
(2,2),
(3,2);
SQLFiddle demo

Formulating a query to detect inconsistent data

I have the following table structure:
CREATE TABLE a (
a_id int(10) unsigned NOT NULL AUTO_INCREMENT,
);
CREATE TABLE b {
b_id int(10) unsigned NOT NULL AUTO_INCREMENT,
};
CREATE TABLE cross (
a_id int(10) unsigned NOT NULL,
b_id int(10) unsigned NOT NULL,
PRIMARY KEY (a_id),
KEY (b_id),
CONSTRAINT FOREIGN KEY (a_id) REFERENCES a (a_id),
CONSTRAINT FOREIGN KEY (b_id) REFERENCES b (b_id)
);
CREATE TABLE prices (
a_id int(10) unsigned NOT NULL,
price int(10) NOT NULL,
PRIMARY KEY (a_id),
CONSTRAINT FOREIGN KEY (a_id) REFERENCES a (a_id)
);
I would like to retrieve every b_id value for which there are inconsistent prices. A b.id value 'B' has an inconsistent price if the following conditions both hold:
There exist two a_id values (say, 'A1' and 'A2') such that table cross contains both ('A1', 'B') and ('A2', 'B'). (For any b_id value, there may be zero or more rows in cross.)
Either 'A1' and 'A2' correspond to rows of prices that have different values of price, or else exactly one of 'A1' and 'A2' corresponds to an entry in prices.
Because of restrictions by the hosting provider, I cannot use stored procedures with this data base. I haven't figured out a sensible way to do this with SQL queries. So far, I've resorted to retrieving all relevant data and scanning for inconsistencies in Perl. That's a lot of data retrieval. Is there a better way? (I'm using InnoDB, if it makes a difference.)
/* Condition 1 and Condition 2a */
SELECT
c.b_id
FROM
`cross` AS c
JOIN prices AS p ON (p.a_id = c.a_id)
GROUP BY
c.b_id
HAVING
COUNT(c.a_id) > 1 AND
MAX(p.price) != MIN(p.price)
UNION
/* Condition 1 and Condition 2b */
SELECT
c.b_id
FROM
`cross` AS c
LEFT JOIN prices AS p ON (p.a_id = c.a_id)
GROUP BY
c.b_id
HAVING
COUNT(c.a_id) > 1 AND
SUM(IF(p.price IS NULL, 0 ,1)) = 1;