SQL JOIN Query not returning expected results - mysql

2I know I am having a simple issue .. But I cannot for the life of me solve it .. Here is what I am trying to do. I have 3 tables and some sample data:
customer_entity_varchar
entity_id attribute_id value
'2' '5' 'John'
'2' '7' 'Smith'
'2' '336' 'ADELANTO'
'3' '5' 'Jane'
'3' '7' 'Doe'
'3' '336' 'ADELANTO'
'4' '5' 'Peter'
'4' '7' 'Griffin'
'4' '336' 'Not ADELANTO'
customer_entity
entity_id email
'2' 'jsmith#whatever.com'
'3' 'janed#thisthat.com'
'4' 'peterg#notanemail.com'
What I am trying to come up with first name, last name and email for everyone that matches a certain district which is attribut_id = '336'. What I am trying is this:
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM customer_entity_varchar CEV
LEFT JOIN customer_entity CE
ON ( CE.entity_id = CEV.entity_id)
WHERE CEV.value ='ADELANTO'
AND CEV.attribute_id='336'
My hopes for a result are:
email FirstName LastName
jsmith#whatever.com John Smith
janed#thisthat.com Jane Doe
However what I am getting back is a SINGLE row -- where email has a value, however both FirstName and LastName are blank. Is my logic flawed?

I would probably solve this like this. It's a solution that favours readability.
WITH firstNames AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '5')
lastNames AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '7')
districts AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '336')
SELECT ce.email, fn.Value, ln.Value, d.Value FROM firstNames fn,lastNames ln, districts d
INNER JOIN customer_entity ce
WHERE fn.entity_id = ln.entity_id AND ln.entity_id = d.entity_id AND ce.entity_id = d.entity_id
AND d.Value = 'ADELANTO';

The WHERE condition in your query is excluding rows with attribute_id 5,7 and so it will not give value containing first and last name.
Try this
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM
(
SELECT entity_id,attribute_id,value
FROM customer_entity_varchar
WHERE entity_id IN (
SELECT entity_id
FROM customer_entity_varchar
WHERE value ='ADELANTO'
AND attribute_id='336'
)
AND attribute_id IN ('5','7')
)As CEV
INNER JOIN customer_entity CE
ON CE.entity_id = CEV.entity_id
GROUP BY CEV.entity_id
SQL Fiddle Demo

You're dealing with an entity-attribute-value table. That means you're going to suffer the joys of an endless number of outer joins in every query. This is why EAV sucks and you're told not to do it unless you have to.
The single query fix:
SELECT DISTINCT
-- ^^^^You need this because you're cross joining the crap out of the EAV table.
CEV.entity_id,
CE.email,
CEV5.value "FirstName",
CEV7.value "LastName"
FROM customer_entity_varchar CEV
-- ^^^^ This is the base table that determines what entities exist. It makes sure you always have your entity_id even if attributes are missing.
LEFT JOIN customer_entity_varchar CEV5
ON CEV5.entity_id = CEV.entity_id AND CEV5.attribute_id = 5
-- ^^^^ This is the join that lets you access attribute 5, FirstName
LEFT JOIN customer_entity_varchar CEV7
ON CEV7.entity_id = CEV.entity_id AND CEV7.attribute_id = 7
-- ^^^^ This is the join that lets you access attribute 7, LastName
LEFT JOIN customer_entity_varchar CEV336
ON CEV336.entity_id = CEV.entity_id AND CEV336.attribute_id = 336
-- ^^^^ This is the join that lets you access attribute 336, City?
LEFT JOIN customer_entity CE
ON CE.entity_id = CEV.entity_id
-- ^^^^ Here's the other table, joined to the base table so we're sure it joins when partial data exists.
WHERE CEV336.value = 'ADELANTO'
Here's how to do it more easily.
First, make a view for your EAV table:
CREATE VIEW vw_customer_entity_varchar AS
SELECT DISTINCT
CEV.entity_id
CEV5.value "FirstName",
CEV7.value "LastName",
CEV336.value "City?"
FROM customer_entity_varchar CEV
LEFT JOIN customer_entity_varchar CEV5
ON CEV5.entity_id = CEV.entity_id AND CEV5.attribute_id = 5
LEFT JOIN customer_entity_varchar CEV7
ON CEV7.entity_id = CEV.entity_id AND CEV7.attribute_id = 7
LEFT JOIN customer_entity_varchar CEV336
ON CEV336.entity_id = CEV.entity_id AND CEV336.attribute_id = 336
Add additional joins for every field in your EAV table.
Then you can treat this view as a normal table, except it has terrible performance, and can't use it for INSERT, UPDATE, or DELETE.
Welcome to EAV hell.

First, do you really have those tick marks around all your values? In other words, are the quotations stored in the db?
As a couple of folk have pointed out, this database design is really a pain to query. Basically, you'll have to take two passes through your customer_entity_varchar table. One pass is to get the entity_id for whoever your're interested in (aliased as T1 in my query), and one to get their attributes (cev in the query). You will filter for this 'adelanto' value against the t1 query, which will give you the entity_ids. Then you can join that back to the CEV alias to get the actual information you want. (Hopefully all that made sense).
I think this what you are looking for:
SQL Fiddle
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM customer_entity_varchar CEV
inner join (select distinct entity_id, value from customer_entity_varchar where attribute_id = 336) t1
on t1.entity_id = cev.entity_id
LEFT JOIN customer_entity CE
ON ( CE.entity_id = CEV.entity_id)
and t1.value = 'ADELANTO'
group by
ce.entity_id
EDIT: As MPelletier also pointed out, you need to include a group by. I can't stand that MySQL will allow you to submit that query without it.

Related

Sql trouble with coalesce() not working propely

i have a query and i'm having trouble to change the name of the last row of columb name to 'TOTAL'. The result gives me the same name of the row above the last row.
Here's my query:
SELECT COALESCE(ticket_types.name,'TOTAL') AS name,
COUNT(1) AS quantity
FROM tr_logs
LEFT JOIN tickets ON tr_logs.value = tickets.id
LEFT JOIN ticket_types ON tickets.ticket_type_id = ticket_types.id
LEFT JOIN transactions ON tr_logs.transaction_id = transactions.id
LEFT JOIN tr_fields_data AS tfd_shipping ON tfd_shipping.transaction_id = transactions.id
WHERE type = 'ADDITEM'
AND transactions.event_id = '46'
AND DATE(tr_logs.created_date)
BETWEEN '2017-03-26' AND '2017-05-24'
AND tfd_shipping.data IN ('0','570','571','771')
AND name IS NOT NULL
GROUP BY ticket_types.id WITH ROLLUP
The result looks like this:
name quantity
premium 56
outlaw 6
outlaw 62
Last row name from rollup is not null.... I need it to be TOTAL and not outlaw
Thanks
You haven't changed the name to TOTAL at all: you've changed the name of the column to name, and you've told it to replace any null values with TOTAL.
If you want to change the name of ticket_types.name to total, you just want
SELECT ticket_types.name AS total ...
(But it would be weird to rename something called name to total, so perhaps you need to clarify your requirements a little.)
This may or not be related to your observed problem, but the WHERE and GROUP BY clauses turn all the outer joins into inner joins. You should simplify the query to:
SELECT COALESCE(tt.name, 'TOTAL') AS name, COUNT(1) AS quantity
FROM tr_logs l JOIN
tickets
ON l.value = t.id JOIN
ticket_types tt
ON t.ticket_type_id = tt.id JOIN
transactions tr
ON l.transaction_id = tr.id JOIN
tr_fields_data fd
ON fd.transaction_id = tr.id
WHERE type = 'ADDITEM' AND
tr.event_id = '46' AND
DATE(l.created_date) BETWEEN '2017-03-26' AND '2017-05-24' AND
fd.data IN ('0', '570', '571', '771') AND
tt.name IS NOT NULL
GROUP BY tt.id WITH ROLLUP
Thanks to Gordon Linoff I have figure out my problem.
The name of the last row was never null beacause i GROUP BY with a different attribute.
Here's the solution.
SELECT COALESCE(tckn,'TOTAL') AS name, quantity FROM
(SELECT tt.name AS tckn, COUNT(1) AS quantity
FROM tr_logs AS l
LEFT JOIN tickets AS t ON l.value = t.id
LEFT JOIN ticket_types AS tt ON t.ticket_type_id = tt.id
LEFT JOIN transactions AS tr ON l.transaction_id = tr.id
LEFT JOIN tr_fields_data AS tfd ON tfd.transaction_id = tr.id
WHERE type = 'ADDITEM'
AND tr.event_id = '46'
AND DATE(l.created_date)
BETWEEN '2017-03-26' AND '2017-05-24'
AND tfd.data IN ('0','570','571','771')
GROUP BY tckn WITH ROLLUP) as sum;

left join logic not as expected

I have created a query that should, I believe, return all email addresses from table 1 regardless.
If I go SELECT COUNT(email), COUNT(DISTINCT email) contacts.sid208 I get 200,000 and 175000.
With this in mind, by using left joins the count of email from the following query result should be the same no?
SELECT
COUNT(email), COUNT(DISTINCT email)
FROM
(SELECT
co.email,
env.env_medium,
CAST(MIN(co.created) AS DATE) AS first_contact,
MIN(CASE
WHEN my.my_id = 581 THEN my.data
END) AS Created,
MIN(CASE
WHEN my.my_id = 3347 THEN my.data
END) AS Upgraded
FROM
contacts.sid208 co
LEFT JOIN contacts.my208 my ON co.id = my.eid
LEFT JOIN contacts.env208 env ON env.eid = co.id
WHERE
my_id = 581 OR my_id = 3347
GROUP BY email) b1
But the results here, if I keep things proportionate, are 150000 and 150000.
I expected the results to be 175000.
My understanding of LEFT JOIN was that all records from contacts.sid208 would be maintained, regardless of whether or not they appear in my208 or env208.
Is my understanding flawed here? Hope my query makes sense to folk, if there's any more info I can add to make my question clearer let me know.
For a left join, move the conditions to the join as well:
SELECT
COUNT(email), COUNT(DISTINCT email)
FROM
(SELECT
co.email,
env.env_medium,
CAST(MIN(co.created) AS DATE) AS first_contact,
MIN(CASE
WHEN my.my_id = 581 THEN my.data
END) AS Created,
MIN(CASE
WHEN my.my_id = 3347 THEN my.data
END) AS Upgraded
FROM
contacts.sid208 co
LEFT JOIN contacts.my208 my
ON co.id = my.eid
AND (my_id = 581 OR my_id = 3347)
LEFT JOIN contacts.env208 env ON env.eid = co.id
GROUP BY email) b1
If you don't do so, you will first perform the join, resulting in all rows from sid208, regardless, with null values for missing emails. But then the filtering in the where clause kicks in and those records are removed anyway.
When you move all those conditions to the join, you get all rows, and the emails are only joined when they have the matching contact id, and their own id is either 581 or 2247.

Where clause inside joined select

I'm trying to accommodate a similar solution to this one - what I have is a SELECT query inside a JOIN, and the problem is that the query runs at full for all rows (I'm talking 60,000 rows per table - and it runs on 3 tables!).
So what I want to do, is add a WHERE clause to the SELECTs inside the JOIN.
But, I can't access the outer SELECT and get the proper WHERE condition I need.
The query I'm attempting is here:
SELECT c.compete_id AS id,
s.id AS store_id,
c.enabled AS enabled,
s.store_name AS store_name,
s.store_url AS store_url,
c.verified AS verified,
r.rating_total AS rating,
r.positive_percent AS percent,
r.type AS type
FROM compete_settings c
LEFT JOIN stores s
ON c.compete_id = s.id
LEFT JOIN (
(SELECT store_id, rating_total, positive_percent, 'ebay' AS type FROM ebay_sellers WHERE store_id = c.compete_id)
UNION
(SELECT store_id, rating_total, positive_percent, 'amazon' AS type FROM amazon_sellers WHERE store_id = c.compete_id)
UNION
(SELECT store_id, CASE WHEN rank = 0 THEN NULL ELSE (200000 - rank) END AS rating_total, '100' as positive_percent, 'alexa' AS type FROM alexa_ratings WHERE store_id = c.compete_id)
) AS r
ON c.compete_id = r.store_id
WHERE c.store_id = :store_id
Note, :store_id is a variable bound through the framework - let's imagine it's the number 12345.
How can I do this? Any ideas?
We ended up going witha different approach - we just JOINed everything and only selected the right columns with a CASE. Here's the final query:
SELECT c.id AS id,
s.id AS store_id,
c.enabled AS enabled,
s.store_name AS store_name,
s.store_url AS store_url,
c.verified AS verified,
(CASE WHEN eb.rating_total IS NOT NULL THEN eb.rating_total
WHEN am.rating_total IS NOT NULL THEN am.rating_total
WHEN ax.rank IS NOT NULL THEN ax.rank
END) AS rating,
(CASE WHEN eb.positive_percent IS NOT NULL THEN eb.positive_percent
WHEN am.positive_percent IS NOT NULL THEN am.positive_percent
WHEN ax.rank IS NOT NULL THEN '100'
END) AS percent,
(CASE WHEN eb.positive_percent IS NOT NULL THEN 'ebay'
WHEN am.positive_percent IS NOT NULL THEN 'amazon'
WHEN ax.rank IS NOT NULL THEN 'alexa'
END) AS type
FROM compete_settings c
LEFT JOIN stores s
ON c.compete_id = s.id
LEFT JOIN ebay_sellers eb ON c.compete_id = eb.store_id
LEFT JOIN amazon_sellers am ON c.compete_id = am.store_id
LEFT JOIN alexa_ratings ax ON c.compete_id = ax.store_id
WHERE c.store_id = :store_id

How do I do a partial match on an IN statement in MySQL

I am joining multiple tables into a single query. I need to do a partial match on values in an IN statement. Here is an example.
SELECT DISTINCT
am.id AS id,
am.flagged AS flagged,
am.name AS name,
am.type AS type,
am.file AS file,
am.s3_tag AS s3_tag,
am.low_s3_tag AS low_s3_tag
FROM accounts_media am
LEFT JOIN accounts_location_media alm ON am.id = alm.media_id
LEFT JOIN accounts_location al ON al.id = alm.location_id
LEFT JOIN accounts_person_media apm ON am.id = apm.media_id
LEFT JOIN accounts_person ap ON ap.id = apm.person_id
LEFT JOIN accounts_event_media_record aemr ON am.id=aemr.media_id
LEFT JOIN accounts_medianote_media_record amma ON am.id=amma.media_id
LEFT JOIN accounts_medianote amn ON amma.medianote_id=amn.id
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
AND ('Da' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
AND ('Rob' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
In the
AND ('Da' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
statement there are values that say 'Dan', 'Daniel', etc. in the table. There is also 'Rob' and 'Robert'. I need that statement to make sure and name that contains 'Da' AND any name that contains 'Rob' from that table. Is there a way to do this?
So a record can be linked to multiple people in the accounts_person table. So lets say I have three records.
Record One: A person named Dan is attached to the record.
Record Two: A person named Robert is attached to the record.
Record Three: A person named Dan and a person named Robert are attached to the record.
I want the query to only return Record Three because it has the match of 'Da' and 'Rob'.
Try this:
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
-- AND ( ap.first_name LIKE '%Da%' OR ap.first_name LIKE '%Rob%')
AND EXISTS
( SELECT 1
FROM accounts_person apx
WHERE apx.first_name LIKE '%Da%'
AND apx.account_id = am.account_id
)
AND EXISTS
( SELECT 1
FROM accounts_person apy
WHERE apy.first_name LIKE '%Rob%'
AND apy.account_id = am.account_id
)
I'm not sure, but I think you want a like statement, with a wild card after the Da.
SELECT DISTINCT
am.id AS id,
am.flagged AS flagged,
am.name AS name,
am.type AS type,
am.file AS file,
am.s3_tag AS s3_tag,
am.low_s3_tag AS low_s3_tag
FROM accounts_media am
LEFT JOIN accounts_location_media alm ON am.id = alm.media_id
LEFT JOIN accounts_location al ON al.id = alm.location_id
LEFT JOIN accounts_person_media apm ON am.id = apm.media_id
LEFT JOIN accounts_person ap ON ap.id = apm.person_id
LEFT JOIN accounts_event_media_record aemr ON am.id=aemr.media_id
LEFT JOIN accounts_medianote_media_record amma ON am.id=amma.media_id
LEFT JOIN accounts_medianote amn ON amma.medianote_id=amn.id
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
AND (accounts_person.first_name like '%Da%'
OR accounts_person.first_name like '%Rob%')
AND accounts_person.account_id = '1234'

Combine many joins into one join?

There are two tables - incoming tours(id,name) and incoming_tours_cities(id_parrent, id_city) where id_parrent is id from first table.
Here is the query i wrote
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '1'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '6'
And now, what is the question...
Why i can't write both conditions in single join?(i.e. i can, but it returns empty result.)
as i understand joins, when i wrote
JOIN incoming_tours_cities tc ON tc.id_parrent = t.id
it must return the list of rows where the condition is true. isn't it?
So why i can't write
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc ON tc.id_parrent = t.id
AND tc.id_city = '1'
AND tc.id_city = '6'
And maybe there is more efficient method to rich same effect(because in my structure the count of conditions can be very big)
Thanks much
the value of tc.id_city cannot be both '1' and '6' simultaneously. I think you want an OR rather than an AND:
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc ON tc.id_parrent = t.id
AND (tc.id_city = '1'
OR tc.id_city = '6')
Think of it this way. If you ask for rows from incoming_tour_cities for which the id_city value is '1' and is also at the same time '6', how many rows will you match?
What you really want is:
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc ON tc.id_parrent = t.id
WHERE (tc.id_city = '1' OR tc.id_city = '6')
or, more compactly:
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc ON tc.id_parrent = t.id
WHERE tc.id_city IN ('1', '6')
An alternative answer based on the user's clarification that the first query is the one he wants to duplicate.
Here is the only "short cut" way I know of doing this, where "short cut" means not performing two independent tests (using JOINs or EXISTs clauses):
SELECT t.cities
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc ON tc.id_parrent = t.id
WHERE tc.id_city IN ('1', '6')
GROUP BY t.cities HAVING COUNT(DISTINCT tc.id_city) > 2