CDbCommand - Three Tables Join and IN Clause Syntax - mysql

In my Yii application, I have following database structure:
Table user (id, firstName, lastName)
Table category (id, categoryName)
Table item (id, categoryId, name)
Table usergroup(id, userId, groupName)
I am trying to run following query:
$sql = 'SELECT a.id, a.firstName, a.lastName, b.id, b.categoryName, c.id, c.name
FROM user AS a
INNER JOIN (category AS b ON a.id = b.userId
WHERE b.userId IN (SELECT f.id FROM usergroup f WHERE f.userId=:currentUserId))
INNER JOIN item AS c ON b.id = c.categoryId
WHERE c.name LIKE :listedName';
$command=$connection->createCommand($sql);
$currentUserId = Yii::app()->user->id;
//binding :currentUserId
$command->bindParam(":currentUserId", $currentUserId,PDO::PARAM_STR);
$listedItemName = $data->name;
//binding :listedItemName
$command->bindParam(":listedName", $listedName,PDO::PARAM_STR);
$dataReader=$command->query();
However, I get following excption:
CDbException
CDbCommand failed to execute the SQL statement: SQLSTATE[42000]:
Syntax error or access violation: 1064 You have an error in your SQL
syntax; check the manual that corresponds to your MySQL server version
for the right syntax to use near 'ON a.id = b.userId WHERE b.userId IN
(SELECT f.id FROM usergroup' at line 3.
The SQL statement executed was:
SELECT a.id, a.firstName, a.lastName, b.id, b.categoryName, c.id, c.name FROM user AS a INNER JOIN (category AS b ON a.id = b.userId WHERE b.userId IN (SELECT f.id FROM usergroup f WHERE f.userId=:currentUserId)) INNER JOIN item AS c ON b.id = c.categoryId WHERE c.name LIKE :listedName. Bound with :currentUserId='2', :listedName='Oliver Twist'
Any sort of help or advice will be highly appreciated. Thank You!

You seem to have some parenthesis issue in there as well as possibly an unnecessary sub-select. I am guessing that what you are looking for is:
SELECT a.id, a.firstName, a.lastName, b.id, b.categoryName, c.id, c.name
FROM user AS a
INNER JOIN category AS b ON a.id = b.userId
INNER JOIN usergroup AS f ON f.userID = a.id
INNER JOIN item AS c ON b.id = c.categoryId
WHERE c.name LIKE :listedName'
AND f.userID=:currentUserId;
But, I am unclear on the join between item and category tables. You don't seem to to have foreign for category in items table and are just joining the item id and category id.
I am also unclear on your join between the user table and category table where you are saying the user id = category id. I don't see how these are related.

For update/reference of any one else facing same issue. I later figured out the problem that SQL WHERE CLAUSE should only be used at the end of statement, and I changed the first WHERE clause with AND condition. Here is the working query:
$sql = 'SELECT a.id, a.firstName, a.lastName, b.id, b.categoryName, c.id, c.name
FROM user AS a
INNER JOIN (category AS b ON a.id = b.userId
AND b.userId IN (SELECT f.id FROM usergroup f WHERE f.userId=:currentUserId))
INNER JOIN item AS c ON b.id = c.categoryId
WHERE c.name LIKE :listedName';

Related

1064 You have an error in your SQL syntax; In connecting 3 tables Where I have an error?

Good day what have I change in my query?
'SELECT a.first_name,
a.last_name,
a.birth_date,
a.email,
a.aba_email,
a.phone,
a.mobile_phone,
FROM passport a
INNER JOIN USER c ON a.id=c.passport_id
INNER JOIN consultant b ON b.user_id=c.id
WHERE c.id = :user_id
AND b.create_time BETWEEN UNIX_TIMESTAMP(DATE_FORMAT(:begin_date,"%Y-%m-%d 00:00:00")) AND UNIX_TIMESTAMP(DATE_FORMAT(:end_date,"%Y-%m-%d 23:59:59"))'
I got below Error
I think its the comma after mobile_phone, you only need commas when there is another column after it.
'SELECT a.first_name,
a.last_name,
a.birth_date,
a.email,
a.aba_email,
a.phone,
a.mobile_phone
FROM passport a
INNER JOIN USER c ON a.id=c.passport_id
INNER JOIN consultant b ON b.user_id=c.id
WHERE c.id = :user_id
AND b.create_time BETWEEN UNIX_TIMESTAMP(DATE_FORMAT(:begin_date,"%Y-%m-%d 00:00:00")) AND UNIX_TIMESTAMP(DATE_FORMAT(:end_date,"%Y-%m-%d 23:59:59"))'

Why am I getting duplicates in a join between four tables?

tables and query are here
http://sqlfiddle.com/#!9/2b6f35a/1/0
In it for each name i get its tags title, So it would be like this
name1: title1;
name2: title1;
name3: title1;
My problem is that i get double the tags
name1: title1, title1;
name2: title1, title1;
name3: title1;
What is the mistake i've made?
The entire problem comes for tablx
Sorry about the mess before, didn't know about sqlfiddle
The join with X table is returning the 4th row. http://sqlfiddle.com/#!9/2b6f35a/6, which draws the same title1 value in from the tags table, which is then aggregated into the final response with GROUP_CONCAT()
A solution if you need to join both these tables in one query but only want the title1 one time is to remove the GROUP_CONCAT() aggregator: http://sqlfiddle.com/#!9/2b6f35a/7
SELECT a.name, x.rate, c.title
FROM tabl1 a
LEFT JOIN tablx x ON x.pid = a.id
INNER JOIN tabl2 b ON a.id = b.pid
INNER JOIN tabl3 c ON c.id = b.bid
WHERE c.title IN ('title1')
GROUP BY a.id
In your case, it might be more useful to have an aggregator on the rate column for x table, like so: http://sqlfiddle.com/#!9/2b6f35a/9
SELECT a.name, x.rate, c.title, SUM(x.rate) AS rate_sum
FROM tabl1 a
LEFT JOIN tablx x ON x.pid = a.id
INNER JOIN tabl2 b ON a.id = b.pid
INNER JOIN tabl3 c ON c.id = b.bid
WHERE c.title IN ('title1')
GROUP BY a.id
If you just want to count the number of distinct tags in this situation, you can use COUNT(DISTINCT...). http://sqlfiddle.com/#!9/2b6f35a/15:
SELECT a.name, b.id as bid, c.title, x.id as xid, x.rate, c.title, SUM(x.rate) AS rate_sum, COUNT(DISTINCT c.title) as title_count
FROM tabl1 a
LEFT JOIN tablx x ON x.pid = a.id
INNER JOIN tabl2 b ON a.id = b.pid
INNER JOIN tabl3 c ON c.id = b.bid
WHERE c.title IN ('title1')
GROUP BY a.id
If everything in the posted question is correct, you aren't doing anything wrong.
See this sqlfiddle:
http://sqlfiddle.com/#!9/03205d/1

SELECT categories from a mapping table

I have 3 tables :
categories:
ID, category
"1","Cars"
"2","Trucks"
"3","Bikes"
"4","Planes"
"5","Boats"
users:
ID, username
"1","john"
"2","bob"
"3","billy"
users_categories:
ID, userid, categoryid
"1","1","2"
"2","1","5"
"3","2","3"
"4","3","2"
"5","3","4"
"6","3","5"
Q1. What I want is :
john,Trucks,Boats
bob,Bikes
billy,Trucks,Planes,Boats
I've come to this. A Concat of the categories would do.
SELECT U.`username`, (SELECT C.`category` FROM `categories` C LEFT JOIN `users_categories` UC ON C.`ID` = UC.`categoryid` WHERE U.ID = UC.userid) FROM `users` U
But I get #1242 - Subquery returns more than 1 row.
Q2. Is there a better way to structure this ? There won't be more than 50-100 categories.
use GROUP_CONCAT to achieve what you want
SELECT a.username,
GROUP_CONCAT(c.category)
FROM users a
INNER JOIN users_categories b
On a.Id = b.userID
INNER JOIN categories c
ON b.categoryID = c.ID
GROUP BY a.ID
SQLFiddle Demo
if you can live with having the categories as a comma separated string, you can use the GROUP_CONCAT function.
Let's see (I've never tried myself in mysql)
select u.username,
GROUP_CONCAT(DISTINCT c.Category order by c.Category SEPARATOR ',')
from users u
join usersCategories uc
on u.ID = uc.userID
join Categories c
on c.ID = uc.CategoryID
You might have to adjust it to MySQL specific syntax, sorry.

Optimizing this SQL Query

How would you optimize the following query?
'example_companies' contains companies data.
'example_roles_companies' contains companies roles (pivot)
'example_industries_companies' contains companies industries (pivot)
SELECT DISTINCT a.id,
a.mode,
a.name,
a.city,
b.name AS USER,
b.phone
FROM example_companies a
LEFT JOIN example_users b
ON a.contact_id = b.id
LEFT JOIN example_roles_companies c
ON a.id = c.company_id
WHERE "2" IN (SELECT industry_id
FROM example_industries_companies
WHERE company_id = a.id)
AND c.role_id = 2
AND a.account_mode != 2
ORDER BY a.id
Query:
SELECT DISTINCT a.id,
a.mode,
a.name,
a.city,
b.name AS USER,
b.phone
FROM example_companies a
LEFT JOIN example_users b ON a.contact_id = b.id
INNER JOIN example_roles_companies c ON a.id = c.company_id AND c.role_id = 2
INNER JOIN example_industries_companies i
ON i.company_id = a.id AND i.industry_id = "2"
WHERE
a.account_mode != 2
ORDER BY
a.id
Structure:
Index on a.id, not null
Index on b.id, not null [analyze the opportunity of adding another index (b.id, b.name, b.phone) to this table as well]
Index on (c.company_id, c.role_id) not null both
Index on (i.company_id, i.industry_id), not null both
Remarks:
Please note that your industry_id = "2" seems weird to me, ids are generally numbers and if they are not then it should be looked since integers are faster to process than strings. Additionally, this way of double-quoting is not usual in mysql. Are you sure of your syntax?

MySQL Question (joins)

I'm not that into MySQL joins, so maybe you could give me a hand. I've got the following tables:
Table a
Fields ID,name
Table b
Fields aID,cID,ID,found
Table c
Fields ID,name
The result I want to get is the following: I want all the records where b.found = 1. Of these records I don't want a.id or a.name, but I want the number of records that would have been returned if I would have wanted so. So if there are five records that have b.found = 1 and c.id = (for example) 3, then I want a returned value of 5, c.id and c.name.
Someone is able to do this?
Actually this is what I want to get from the database:
A list of all records in table C and a count of records in table B that has found = 1 and b.c_id = c.id
Table: a
Fields: ID, name
Table: b
Fields: aID, cID, found
Table: c
Fields: ID, name
SELECT c.ID, c.name, COUNT(1)
FROM b
JOIN c ON c.ID = b.cID AND b.found=1
GROUP BY c.ID
SELECT c.id, c.name, COUNT(*)
FROM c
INNER JOIN b
ON c.id = b.c_id
AND b.found = 1
GROUP BY c.id, c.name
SELECT COUNT(*), c.id, c.name
FROM a, b, c
WHERE a.id = b.a.id AND c.id = b.a.id AND b.found = 1 AND c.id = idThatIAmSearchingFor
Apologies if I didn't get the syntax exact, but I believe that's the basic structure you want. The COUNT function returns the number of rows found by the query.
Something like:
SELECT count(`c`.*),
`c`.`id`,
`c`.`name`
FROM `b`
JOIN `c`
ON `c`.`id` = `b`.`c_id`
WHERE `b.found` = 1
I think this would provide the required output -
select count(*), b.cID, c.name from b
inner join c on c.id=b.cID and b.found=1
group by b.cID
SELECT COUNT(*) AS Count, c.id, c.name
FROM b join a on a.id = b.a_id
WHERE b.found = 1
GROUP BY c.Id;
COUNT returns count of records in each group from GROUP BY.