How would I execute this complex conditional multi-table MySQL join (queries provided)? - mysql

Ok I have a few tables tables. I am only showing relevant fields:
items:
----------------------------------------------------------------
name | owner_id | location_id | cab_id | description |
----------------------------------------------------------------
itm_A | 11 | 23 | 100 | Blah |
----------------------------------------------------------------
.
.
.
users:
-------------------------
id | name |
-------------------------
11 | John |
-------------------------
.
.
.
locations
-------------------------
id | name |
-------------------------
23 | Seattle |
-------------------------
.
.
.
cabs
id | location_id | name
-----------------------------------
100 | 23 | Cool |
-----------------------------------
101 | 24 | Cool |
-----------------------------------
102 | 24 |thecab |
-----------------------------------
I am trying to SELECT all items (and their owner info) that are from Seattle OR Denver, but if they are in Seattle they can only be in the cab NAMED Cool and if they are in Denver they can only be in the cab named 'thecab' (not Denver AND cool).
This query doesn't work but I hope it explains what I am trying to accomplish:
SELECT DISTINCT
`item`.`name`,
`item`.`owner_id`,
`item`.`description`,
`user`.`name`,
IF(`loc`.`name` = 'Seattle' AND `cab`.`name` = 'Cool',1,0) AS `cab_test_1`,
IF(`loc`.`name` = 'Denver' AND `cab`.`name` = 'thecab',1,0) AS `cab_test_2`,
FROM `items` AS `item`
LEFT JOIN `users` AS `user` ON `item`.`owner_id` = `user`.`id`
LEFT JOIN `locations` AS `loc` ON `item`.`location_id` = `loc`.`location_id`
LEFT JOIN `cabs` AS `cab` ON `item`.`cab_id` = `cabs`.`id`
WHERE (`loc`.`name` IN ("Seattle","Denver")) AND `cab_test_1` = 1 AND `cab_test_2` = 1
I'd rather get rid of the IFs is possible. It seems inefficent, looks clunky, and is not scalable if I have a lot of location\name pairs

Try this:
SELECT DISTINCT
item.name,
item.owner_id,
item.description,
user.name
FROM items AS item
LEFT JOIN users AS user ON item.owner_id = user.id
LEFT JOIN locations AS loc ON item.location_id = loc.id
LEFT JOIN cabs AS cab ON item.cab_id = cabs.id
WHERE ((loc.name = 'Seattle' AND cab.name = 'Cool')
OR (loc.name = 'Denver' AND cab.name = 'thecab'))

My first thought is to store the pairs of locations and cab names in a separate table. Well not quite a table, but a derived table generated by a subquery.
You still have the problem of pivoting the test results into separate columns. The code can be simplified by making use of mysql boolean expressions, which get rid of the need for a case or if.
So, the approach is to use the same joins you have (although left join is not needed because the comparison on cab.name turns them in to inner joins). Then add a table of the pairs you are looking for, along with the "test name" for the pair. The final step is an explicit group by and a check whether conditions are met for each test:
SELECT i.`name`, i.`owner_id`, i.`description`, u.`name`,
max(pairs.test_name = 'test_1') as cab_test_1,
max(pairs.test_name = 'test_2') as cab_test_2
FROM `items` i LEFT JOIN
`users` u
ON i.`owner_id` = u.`id` LEFT JOIN
`locations` l`
ON i.`location_id` = l.`location_id` left join
`cabs` c
ON i.`cab_id` = c.`id` join
(select 'test_1' as testname, 'Seattle' as loc, 'cool' as cabname union all
select 'test_2', 'Denver', 'thecab'
) pairs
on l.name = pairs.name and
l.cabname = c.name
group by i.`name`, i.`owner_id`, i.`description`, u.`name`;
To add in additional pairs, add them into the pairs table along, and add an appropriate line in the select for the test flag.

Related

Mysql join table and select when record doesn't exit in left join

I have a table which list available items, when user click on any item it will insert in added_items table. Now my problem is I want to use join to select all items that any user has not added. My current query doesn't show items if one user has added it.
items
name | type | id | user
-------|------|----|------------------
JAVA | A | 1 | SYSTEM
PHP | A | 2 | SYSTEM
HTML | B | 3 | USER1
added_items
item_id | user
----------|--------------
1 | peter
My query
SELECT it.*
FROM items it
LEFT JOIN added_items ait
ON ait.user = it.user
#on ait.item_id = it.id
WHERE it.type = "A"
AND ait.user IS NULL
The second query I tried
SELECT it.*
FROM items it
LEFT JOIN added_items ait
ON ait.item_id = it.id
WHERE it.type = "a"
AND ait.user != "peter"
Expected result
when current user is peter I want to retrieve only PHP as peter has added JAVA.
But if current user isn't on added_items the retrieve all record.
Add fiddle http://sqlfiddle.com/#!9/3761e40
SELECT *
FROM items
WHERE NOT EXISTS ( SELECT NULL
FROM added_items
WHERE items.id = added_items.item_id
-- AND added_items.user = "peter"
)
-- AND items.type = 'A'
fiddle
You can try:
SELECT it.*
FROM items it
LEFT JOIN added_items ait
ON ait.item_id = it.id
WHERE ait.item_id IS NULL

MySQL left join with default values

I have a couple of tables, one with source data which I'll call SourceData and another which defines overridden values for a given user if they exist called OverriddenSourceData.
The basic table format looks something this like:
SourceData
| source_id | payload |
--------------------------------
| 1 | 'some json' |
| 2 | 'some more json' |
--------------------------------
OverriddenSourceData
| id | source_id | user_id | overrides
| 1 | 2 | 4 | 'a change' |
------------------------------------------
For a given user, I'd like to return all the Source data rows with the overrides column included. If the user has overridden the source then the column is populated, else it is null.
I started by executing a left join and then including a condition for checking the user like so:
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
WHERE user_id = 4
but then source rows that weren't overridden wouldn't be included ( it was acting like an inner join) (e.g source id 1)
I then relaxed the query and used a strict left join on source_id.
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
# WHERE user_id = 4
This can return more data than I need though (e.g other users who have overridden the same source data) and then I have to filter programatically.
It seems like I should be able to craft a query that does this all the DB level and gives me what I need. Any help?
You should add your condition on LEFT JOIN clause, if you use WHERE, mysql will do it with INNER JOIN, so try this;)
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
AND B.user_id = 4

two inner join in one sql statement

What's wrong here ? i just want to display all the item in item_tb with 2 different group , vicma and branch but it returns nothing. It only works in one inner join but when i join the other one it display nothing.
|-------------|-------------------------|---------------|
|item_tb | vicma_tb | branch_tb |
| | vID - PK | id-PK |
|branchID-FK | | |
|vicma - FK | | |
|-------------|-------------------------|---------------|
$sql = "
SELECT item_tb.*
, branch_tb.*
, vicma_tb.*
from item_tb
JOIN branch_tb
on item_tb.branchID = branch_tb.id
JOIN vicma_tb
on item_tb.vicma = vicma_tb.vID ";
Seems like you need to do a LEFT JOIN instead of INNER JOIN. LEFT JOIN will return all values from your original table and NULL if there is no match. Try:
SELECT item_tb.*, branch_tb.* , vicma_tb.* from item_tb
LEFT JOIN branch_tb on item_tb.branchID = branch_tb.id
LEFT JOIN vicma_tb on item_tb.vicma = vicma_tb.vID

Symfony2 Query Using Left Join on Two Tables

I am trying to retrieve product information from a query that looks in one table for an identifier and matches that on another to get the product id then looks in the product table to get the information for the product(s). For some reason I cannot get this query to run successful. Any help would me much appreciated!!!
$query = $em->createQuery('
SELECT p
FROM WIC\ProductBundle\Entity\Product p
LEFT JOIN WIC\ListingBundle\Entity\Listing l
ON l.product = p.id
LEFT JOIN WIC\ListingBundle\Entity\ListingAma la
ON la.id = l.id
WHERE la.standardProductIdValue LIKE :stringValue
AND p.account = :account_id')
->setMaxResults(10)
->setParameter('stringValue', "%1234567890%")
->setParameter('account_id', $account_id);
Product Table
|id|product_name |account_id|
----------------------------------
|1|Test Product 1 |74 |
|2|Test Product 2 |74 |
|3|Test Product 3 |74 |
|4|Test Product 4 |74 |
|5|Test Product 5 |74 |
Listing Table
|id|product_id |
----------------------
|1|1 |
|2|1 |
|3|2 |
|4|3 |
|5|1 |
|6|3 |
|7|5 |
ListingAma Table
|id|standardProductIdValue |
----------------------------
|1|1234567890 |
|2|1234567890 |
|3|AAAAAAAAAA |
|4|BBBBBBBBBB |
|5|1234567890 |
|6|CCCCCCCCCC |
|7|0000000000 |
The Listing and ListingAma tables are a base class (Listing) and and extended class (ListingAma) so they share the ID number in each table.
I want to return product information for any query that has standardProductIdValue equaling "1234567890". In this case it should retrieve rows 1,2 and 5 and then in the listing table it should sync that with Product id #1 and just return "Test Product 1".
My query above creates this error in Symfony:
[Syntax Error] line 0, col 163: Error: Expected Doctrine\ORM\Query\Lexer::T_WITH, got 'ON' (500 Internal Server Error)
Thanks for your help!!!
Try to use this query:
$query = $em->createQuery("
SELECT p
FROM WIC\ProductBundle\Entity\Product p
LEFT JOIN WIC\ListingBundle\Entity\Listing l
ON l.product = p.id
LEFT JOIN WIC\ListingBundle\Entity\ListingAma la
ON la.id = l.id
WHERE la.standardProductIdValue LIKE :stringValue")
->setMaxResults(10)
->setParameter("stringValue", "%1234567890%");
Is this failed too with same error?
Thanks to this post, I was able to find out what the issue was:
http://stackoverflow.com/questions/20467428/error-expected-doctrine-orm-query-lexert-with-got-on
Here Is The Correct Code:
$query = $em->createQuery('
SELECT p
FROM WIC\ProductBundle\Entity\Product p
LEFT JOIN WIC\ListingBundle\Entity\Listing l
WITH l.product = p.id
LEFT JOIN WIC\ListingBundle\Entity\ListingAma la
WITH la.id = l.id
WHERE la.standardProductIdValue LIKE :stringValue
AND p.account = :account_id')
->setMaxResults(10)
->setParameter('stringValue', "%B00AM204Q6%")
->setParameter('account_id', $account_id);

Complex Query with related tables - Optimal Solution

The Schema:
I have 3 Tables:
User
Feature
User_has_Feature:
initially all users has no features
Example data:
User:
| id | name |
| 1 | Rex |
| 2 | Job |
Feature:
| id | name |
| 1 | Eat |
| 2 | Walk |
User_has_Feature:
| id | user_id | feature_id | have_feature |
| 1 | 1 | 1 | true |
| 2 | 1 | 1 | true |
| 3 | 2 | 2 | true |
| 4 | 2 | 2 | false |
The questions are:
¿How to get only the records that have all features? (explicitly)
Example:
| user_name | feature_name | feature_status |
| Rex | Eat | true |
| Rex | Walk | true |
How to get records that do not have all the features? (again explicitly)
Example:
| user_name | feature_name | feature_status |
| Job | Eat | true |
| Job | Walk | false |
Some conditions have to be attended
I need the Users list with all features (true or false) in both queries like examples
User have 650k records (for now)
Feature have 45 records (for now)
Is one time query.
The idea is to export the result to a CSV file
Early Solution
thanks to the answers of (#RolandoMySQLDBA, #Tom Ingram, #DRapp) I found a solution:
SELECT u.name, f.name, IF(uhf.status=1,'YES','NO') as status
FROM user u
JOIN user_has_feature uhf ON u.id = uhf.user_id
JOIN feature f ON f.id = uhf.feature_id
JOIN
(
SELECT u.id as id
FROM user u
JOIN user_has_feature uhf ON uhf.user_id = u.id
WHERE uhf.status = 1
GROUP BY u.id
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature)
) as `condition` ON `condition`.id = u.id
ORDER BY u.name, f.id, uhf.status
For get records that do not have all the features and for get all record that have all features change:
WHERE uhf.status = 1 by WHERE uhf.status = 2
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature) by HAVING count(u.id) = (SELECT COUNT(1) FROM feature)
but I want to know if this is an optimal solution?
SELECT
UNF.*,
IF(
(LENGTH(UNF.FeatureList) - LENGTH(REPLACE(UNF.FeatureList,',','')))
= (FC.FeatureCount - 1),'Has All Features','Does Not Have All Features'
) HasAllFeatures
FROM
(SELECT
U.name user_name
GROUP_CONCAT(F.name) Features
FROM
(SELECT user_id,feature_id FROM User_has_Feature
WHERE feature_status = true) UHF
INNER JOIN User U ON UHF.user_id = U.id
INNER JOIN Feature F ON UHF.feature_id = F.id
GROUP BY
U.name
) UNF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
;
The UNF subquery returns with all users listed in User_has_Feature and a comma-separated list of the features. The column HasAllFeatures is determined by the number of columns in UNF.FeatureList. In your case, there are two features. If the number of commas in UNF.FeatureList is FeatureCount - 1, then the user has all features. Otherwise, user does not have all features.
Here is a better version that shows all users and whether or not they have all, some or no features
SELECT
U.name user_name,
IFNULL(UsersAndFeatures.HasAllFeatures,
'Does Not Have Any Features')
WhatFeaturesDoesThisUserHave
FROM
User U LEFT JOIN
(
SELECT
UHF.user_id id,
IF(
(LENGTH(UHF.FeatureList) - LENGTH(REPLACE(UHF.FeatureList,',','')))
= (FC.FeatureCount - 1),
'Has All Features',
'Does Not Have All Features'
) HasAllFeatures
FROM
(
SELECT user_id,GROUP_CONCAT(Feature.name) FeatureList
FROM User_has_Feature INNER JOIN Feature
ON User_has_Feature.feature_id = Feature.id
GROUP BY user_id
) UHF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
) UsersAndFeatures
USING (id);
select
u.id,
u.name as User_Name,
f.name as Feature_Name,
uhf.feature_Status
from
( select uhf.user_id,
sum( if( uhf.feature_status, 1, 0 ) ) as UserFeatureCount
from user_has_feature uhf
group by uhf.user_id ) AllUsersWithCounts
join
( select count(*) as AllFeaturesCount
from Feature ) AllFeatures
on AllUsersWithCounts.UserFeatureCount = AllFeatures.AllFeaturesCount
join user u
on AllUsersWithCounts.user_id = u.ID
join user_has_feature uhf
on AllUsersWithCounts.User_id = uhf.user_id
join feature f
on uhf.feature_id = f.id
The above query should get all people that explicitly have ALL features. In order to get those that do NOT have all features, just change the one join from = to <
on AllUsersWithCounts.UserFeatureCount < AllFeatures.AllFeaturesCount
Here's my bash at it
create a view of the general information
CREATE VIEW v_users_have_features AS
SELECT usr.id, usr.name, feature.name, has_feature.status
FROM usr
JOIN has_feature ON usr.id = has_feature.user_id
JOIN feature ON has_feature.feature_id = feature.id;
use the view for other queries
SELECT v_users_have_features.id, v_users_have_features.u_name, v_users_have_features.f_name
FROM v_users_have_features
GROUP BY v_users_have_features.id
HAVING COUNT( v_users_have_features.id ) = (SELECT COUNT( feature.id )
FROM feature
WHERE feature.name = v_users_have_features.f_name )
p.s. you may need to adapt (particularly the latter) to your exact requirements you could also omit creating the view and nest it in the FROM clause like in another answer it just seemed handier to create the view
Count the number of features. Write a query over users that uses a correlated subquery to find all the features a user has and count them. Make the restriction criterion in the top query the equality of that count and the global number of features.
Can MySQL do correlated subqueries? If not, you might need to use a better database.