MYSQL query help (EAV Table) - mysql

I have the following query to retrieve customers who answer YES to a particular question "OR" NO to another question.
SELECT customers.id
FROM customers, responses
WHERE (
(
responses.question_id = 5
AND responses.value_enum = 'YES'
)
OR (
responses.question_id = 9
AND responses.value_enum = 'NO'
)
)
GROUP BY customers.id
Which works fine. However I wish to change the query to retrieve customers who answer YES to a particular question "AND" answer NO to another question.
Any ideas on how I can achieve this?
PS - The responses above table is in an EAV format ie. a row represents an attribute rather than a column.

I'm assuming that you have a column called customer_id in your responses table. Try joining the responses table to itself:
SELECT Q5.customer_id
FROM responses Q5
JOIN responses Q9 ON Q5.customer_id = Q9.customer_id AND Q9.question_id = 9
WHERE Q5.question_id = 5
AND Q5.value_enum = 'YES'
AND Q9.value_enum = 'NO'

Approximately as such:
SELECT distinct
c.id
FROM
customers c
WHERE
exists (select 1 from responses r where r.customer_id = c.id and r.response_id = 5 and r.value_enum = 'YES')
and exists (select 1 from responses r2 where r2.customer_id = c.id and r2.response_id = 9 and r2.value_enum = 'NO')
I made an assumption on the missing join condition, modify as correct for your schema.

Related

How to get only all rows that apply this WHERE clause?

I have two tables, tasks and views, with the following structure:
tasks
-- id
-- status
views
-- id
-- taskid (FK of tasks.id)
-- status
And the tasks table, has a row with id = 1 and status = 1, whilst the views table has two rows with id = 1, taskid = 1, status = 1 and id = 2, taskid = 1, status = 0.
When I try to get all the tasks id that have all its views status set to 1 and the task's status itself set to 1 too and only, then I get in return a row with task id = 1 because view number 1 is set to 1 and view number 2 is set to 0.
So basically, what I need is an SQL statement that returns only one row for each task that has all its views and task status set to 1 (tasks.status = 1, views.status = 1) and only when that happens, and if any of the tasks' views is set to something different than 1, then the SQL statement doesn't return it.
Here is my SQL Statement so far which kind of works, but there is still something I am missing because it doesn't work as expected. Sorry if something isn't clear!
SELECT tasks.id FROM tasks JOIN views ON tasks.id = views.taskid WHERE tasks.status = 1 AND views.status = 1;
Join tasks to a query that uses aggregation to return only the taskids with min status set to 1 (which means there is no 0):
SELECT t.id
FROM tasks t
INNER JOIN (
SELECT taskid
FROM views
GROUP BY taskid
HAVING MIN(status) = 1
) v ON v.taskid = t.id
WHERE t.status = 1;
with t as (select taskid,
count(status) status_cnt,
sum(case when status = 1 then 1 else 0 end) as status_1_cnt
from views
group by taskid),
t2 as (select taskid from t where status_1_cnt > 0 and status_cnt = status_1_cnt)
select tasks.id from tasks join t2 on tasks.id = t2.taskid and status = 1
so If I am reading your question correctly you want all of the statuses in the view to be 1 per taskid. so I would count the view statuses and compare that count to where the view status is 1. (the case statement).
then just join this to the task table where the task status is 1
(although I like #forpas answer better)
Conceptually you only want to join on the records in the views table where status = 1, so like this:
SELECT A.id
FROM
tasks A
INNER JOIN
(
SELECT
tasks.taskid
FROM
views
WHERE
view.status = 1
) B
ON A.id = B.taskid
Although the syntax is less intuitive, this is equivalent and shorter:
SELECT
tasks.id
FROM
tasks
JOIN views
ON tasks.id = views.taskid
AND view.status = 1
WHERE
tasks.status = 1
This should also work, for a different reason (more like a trick):
SELECT A.id
FROM
tasks A
INNER JOIN views B
ON a.id = b.taskid
and a.status = b.status
WHERE
A.status = 1
This might be more stringent of a test if thats what you need (the matching records in views with the requirement that no other records with status = 0 exist in views) - but I would like to avoid this style of using a correlated subquery in real life if the tables are of an significant size:
SELECT A.id
FROM
tasks A
INNER JOIN views B
ON A.id = B.taskid
WHERE
A.status = 1
AND B.status = 1
AND NOT EXISTS (SELECT * FROM views c
WHERE c.taskid = b.taskid and c.status = 0)
Finally this is a solution that thinks conceptually more in terms of the intersection of the sets:
SELECT A.id
FROM
tasks a
INNER JOIN views a
ON A.id = b.taskid
AND b.status = 1
LEFT JOIN views c
ON a.id = c.taskid
AND c.status = 0
WHERE
A.status = 1
AND c.status is null
I just saw that forpas has just shown a different but very good solution using aggregation with a min() clause to select only the appropriate records from views for use in joining to tasks which seems like it may be the winner to me :)
If I understand you correctly, you want to get id of task , if and ONLY if it's status = 1, and there are particular records in views table with ONLY same status = 1.
Then your query would be like this:
select tasks.id
from tasks
where status =1 and not exists(
select 1
from views
where taskid=tasks.id and views.status!=tasks.status)
Check demo https://www.db-fiddle.com/f/cQfQMx5LGJN516ND2iVj8y/3

MySQL query returning only one row because of SUM function in it

I am trying to create an advertisement query where I want to fetch data of all the impressions per advertisement. One user can have multiple advertisements and impressions will be counted in a table on per day basis. So for each day I will have one different row. Here is how my query currently looks like.
SELECT
eac.id,
eac.gender,
eac.start_date,
eac.end_date,
eac.ad_image_path,
eac.ad_link,
eac.requestfrom,
eac.traffic,
eac.registertype,
eacr.region_id,
eac.active,
eac.impression,
eac.center_image_path,
eac.bottom_image_path,
eac.approved_by,
er.name as country_name,
eac.budget,
sum(budget/ (DATEDIFF(end_date,start_date)) *1000) as daily_imp,
eaa.impression_count,
eac.customer_id,
eaa.created_at
FROM
`enrich_advert_customer` eac
JOIN `enrich_advert_customer_regions` eacr ON eac.id = eacr.advert_customer_id
JOIN `enrich_regions` er ON er.id = eacr.region_id
LEFT JOIN `enrich_advert_abstract` eaa on eac.id = eaa.advert_customer_id
WHERE
eac.requestfrom ='web' AND
eac.registertype = 'paid' AND
eac.active = 1 AND
eac.approved_by = 1 AND
eac.gender ='male' AND
er.name = 'india' AND
eac.start_date <= '2018-11-5' AND
eac.end_date >= '2018-11-10'
But the problem here is if I am using
sum(budget/ (DATEDIFF(end_date,start_date)) *1000) as daily_imp
this then its returning only one row at a time.
If you can suggest where I am making a mistake that will be helpful.
Thank you!
You need to add group by clause and others column in there as you applyied aggregation
SELECT eac.id,eac.gender,eac.start_date,eac.end_date,eac.ad_image_path,eac.ad_link,eac.requestfrom,eac.traffic,eac.registertype,eacr.region_id,eac.active,eac.impression,eac.center_image_path,eac.bottom_image_path,eac.approved_by,er.name as country_name,eac.budget,sum(budget/ (DATEDIFF(end_date,start_date)) *1000) as daily_imp ,eaa.impression_count,eac.customer_id,eaa.created_at
FROM
`enrich_advert_customer` eac
JOIN
`enrich_advert_customer_regions` eacr
ON eac.id = eacr.advert_customer_id
JOIN
`enrich_regions` er
ON er.id = eacr.region_id
LEFT JOIN
`enrich_advert_abstract` eaa
on eac.id = eaa.advert_customer_id
WHERE eac.requestfrom ='web' AND
eac.registertype = 'paid' AND
eac.active = 1 AND
eac.approved_by = 1 AND
eac.gender ='male' AND
er.name = 'india' AND
eac.start_date <= '2018-11-5' AND
eac.end_date >= '2018-11-10'
group by eac.id,eac.gender,eac.start_date,eac.end_date,eac.ad_image_path,eac.ad_link,eac.requestfrom,eac.traffic,eac.registertype,eacr.region_id,eac.active,eac.impression,eac.center_image_path,eac.bottom_image_path,eac.approved_by,er.name,eac.budget, eaa.impression_count,eac.customer_id,eaa.created_at

How to pivot the following JOIN query

I am attempting to pass data from database to Google charts, however, Google charts has a specific way in which it excepts data. For this reason, I need to change the following output:
to this :
the query for the original output is:
SELECT (select COUNT(projects.funding)from projects where funding > 0)as Funding,
SUM(projects.mreq = 'yes') Mentor_Required,
SUM(projects.tmreq = 'yes') Team_Member_Required
FROM projects INNER JOIN users ON projects.members = users.email
WHERE users.company = 'vit'
The 2 tables are:
projects
users
Would love some guidance on putting pivot on the above query or any better method.
I think you can just do:
SELECT (CASE WHEN p.mreq = 'yes' THEN 'Mentor_Required'
WHEN p.tmreq = 'yes' THEN 'Team_Member_Required'
END) as Funding,
COUNT(*) as cnt
FROM projects p INNER JOIN
users u
ON p.members = u.email
WHERE u.company = 'vit'
GROUP BY Funding;

combine tables with 1 to N relationship into 1 line of record with the last value of the N record

I need a modification of my previous post regarding
how to combine tables with 1 to many relationship into 1 line of record
how to combine tables with 1 to many relationship into 1 line of record
now my problem is my record has now 1 to many relationship. What I need to show is the last record only and combine it in a single line
tables tbl_equipment and tbl_warranty
and here is the desired output
here is the code I'm trying to implement
SELECT
a.equipmentid,
a.codename,
a.name,
a.labelid,
a.ACQUISITIONDATE,
a.description,
a.partofid,
w1.warrantyid as serviceidwarranty,
w1.startdate,
w1.enddate,
w2.warrantyid as productidwarranty,
w2.startdate,
w2.enddate,
s.equipstatusid,
l.equiplocationid FROM TBL_EQUIPMENTMST a
left JOIN tbl_equipwarranty w1
ON w1.equipmentid=a.equipmentid and w1.serviceproduct = 'service'
left JOIN tbl_equipwarranty w2
ON w2.equipmentid=a.equipmentid and w2.serviceproduct = 'product'
left join tbl_equipstatus s
on a.equipmentid = s.equipmentid
left join tbl_equiplocation l
on a.equipmentid = l.equipmentid WHERE a.equipmentid = '112'
I only want to show 1 record with the last value of warranty product and warranty service in the output. Can anyone guide me how to modify my code so that when I try join all the tables listed above can produce 1 record only with the last record of warranty as an output.
I am using firebird as a database. If you have a solution in mysql kindly tell me and ill try to find the counterpart in firebird.
with summary as(
select e.equipmentid ,e.Codename,e.Name,w.warrantyid ,w.Satartdate ,w.Enddate,w.warrantytype
from Eqp e
join Warranty w
on(w.equipmentid =e.equipmentid )
where w.warrantyid =3)
select *,w.warrantyid,w.Satartdate ,w.Enddate,w.warrantytype
from summary s
join Warranty w
on s.Satartdate =w.Satartdate and s.Enddate =w.Enddate
where w.warrantyid =4
after reading the comment of Barmar at the question for solution. I Figured out subquery can solve my problem. Subquery is a new word for me. I research on how to use subquery and came out with a solution below. you can correct me if my code is wrong or how to improve the performance of the query
SELECT
a.equipmentid,a.codename,a.name,a.labelid,a.ACQUISITIONDATE,a.description,a.partofid,
w1.warrantyid as serviceidwarranty,w1.startdate,w1.enddate,
w2.warrantyid as productidwarranty,w2.startdate,w2.enddate,
s.equipstatusid,
l.equiplocationid
FROM
TBL_EQUIPMENTMST a
left JOIN
(select first 1 *
from tbl_equipwarranty
where equipmentid='112' and serviceproduct = 'service'
order by warrantyid desc) w1 ON w1.equipmentid = a.equipmentid
and w1.serviceproduct = 'service'
left JOIN
(select first 1 *
from tbl_equipwarranty
where equipmentid = '112' and serviceproduct = 'product'
order by warrantyid desc) w2 ON w2.equipmentid = a.equipmentid
and w2.serviceproduct = 'product'
left join
(select first 1 *
from tbl_equipstatus
where equipmentid = '112'
order by equipstatusid desc) s on a.equipmentid = s.equipmentid
left join
(select first 1 *
from tbl_equiplocation
where equipmentid = '112'
order by equiplocationid desc) l on a.equipmentid = l.equipmentid
WHERE
a.equipmentid = '112'

Search a table based on multiple rows in another table

Basically I have three MySQL tables:
Users - contains base information on users
Fields - describes additional fields for said users (e.g. location, dob etc.)
Data - Contains user data described via links to the fields table
With the basic design as follows (the below is a stripped down version)
Users:
ID | username | password | email | registered_date
Fields
ID | name | type
Data:
ID | User_ID | Field_ID | value
what I want to do is search Users by the values for the fields they have, e.g. example fields might be:
Full Name
Town/City
Postcode
etc.
I've got the following, which works when you're only wanting to search by one field:
SELECT `users`.`ID`,
`users`.`username`,
`users`.`email`,
`data`.`value`,
`fields`.`name`
FROM `users`,
`fields`,
`data`
WHERE `data`.`Field_ID` = '2'
AND `data`.`value` LIKE 'london'
AND `users`.`ID` = `data`.`User_ID`
AND `data`.`Field_ID` = `fields`.`ID`
GROUP BY `users`.`ID`
But what about if you want to search for Multiple fields? e.g. say I want to search for Full Name "Joe Bloggs" With Town/City set to "London"? This is the real sticking point for me.
Is something like this possible with MySQL?
I'm going with the assumption that "searching multiple fields" is talking about the Entity-Attribute-Value structure.
In that case, I propose that the first step is to create a derived query - basically, we want to limit the "EAV data joined" to only include the records that have the values we are interested in finding. (I've altered some column names, but the same premise holds.)
SELECT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
-- now that we establish data/field relation, filter rows
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
This resulting rows are derived from the filtered EAV triplets that match our conditions. Only the userId is selected in this case (as it will be used to join against the user relation), but it is also possible to push fieldId/value/etc through.
Then we can use all of this as a derived query:
SELECT *
FROM users u
JOIN (
-- look, just goes in here :)
SELECT DISTINCT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
) AS e
ON e.userId = u.userId
Notes:
The query planner will figure all the RA stuff out peachy keen; don't worry about this "nesting" as there is no dependent subquery.
I avoid the use of implicit cross-joins as I feel they muddle most queries, this case being a particularly good example.
I've "cheated" and added a DISTINCT to the derived query. This will ensure that at most one record will be joined/returned per user and avoids the use of GROUP BY.
While the above gets "OR" semantics well (it's both easier and I may have misread the question), modifications are required to get "AND" semantics. Here are some ways that the derived query can be written to get such. (And at this point I must apologize to Tony - I forget that I've already done all the plumbing to generate such queries trivially in my environment.)
Count the number of matches to ensure that all rows match. This will only work if each entity is unique per user. It also eliminates the need for DISTINCT to maintain correct multiplicity.
SELECT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
-- now that we establish data/field relation, filter rows
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
GROUP BY d.userId
HAVING COUNT(*) = 2
Find the intersecting matches:
SELECT d.userId
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "location" AND d.value = "london"
INTERSECT
SELECT d.userId
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "job" AND d.value = "programmer"
Using JOINS (see Tony's answer).
SELECT d1.userId
FROM data d1
JOIN data d2 ON d2.userId = d1.userId
JOIN fields f1 ON f1.fieldId = d1.fieldId
JOIN fields f2 ON f2.fieldId = d2.fieldId
-- requires AND here across row
WHERE f1.type = "location" AND d1.value = "london"
AND f2.type = "job" AND d2.value = "programmer"
An inner JOIN itself provides conjunction semantics when applied outside of the condition. In this case I show "re-normalize" the data. This can also be written such that [sub-]selects appear in the select clause.
SELECT userId
FROM (
-- renormalize, many SO questions on this
SELECT q1.userId, q1.value as location, q2.value as job
FROM (SELECT d.userId, d.value
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "location") AS q1
JOIN (SELECT d.userId, d.value
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "job") AS q2
ON q1.userId = q2.userId
) AS q
WHERE location = "london"
AND job = "programmer"
The above duplicity is relatively easy to generate via code and some databases (such as SQL Server) support CTEs which make writing such much simpler. YMMV.
If I understood you right, this is what you want:
FROM `users`,
`fields`,
`data` `location`
`data` `name`
WHERE `location`.`Field_ID` = '2'
AND `location`.`value` LIKE 'london'
AND `location`.`Field_ID` = `fields`.`ID`
AND `name`.`Field_ID` = 'whathere? something for its name'
AND `name`.`value` LIKE 'london'
AND `name`.`Field_ID` = `fields`.`ID`
AND `users`.`ID` = `data`.`User_ID`
I'd prefer joins though
Well here you hit one of the downsides of the EAV you are using
SELECT u.ID, u.username,u.email, d1.value, f1.Name, d2.Value, f2.name
FROM `users` u,
inner join data d1 On d1.User_id = u.id
inner join data d2 On d2.User_id = u.id
inner join fields f1 on f1.id = d1.field_id
inner join fields f2 on f2.id = d2.field_id
WHERE d1.Field_id = '2' and d1.Value = 'london'
and d2.field_id = '??' and d2.value = 'Joe Bloggs'
GROUP BY `users`.`ID`
Messy isn't it? Bet you can't wait to go for, four or five values. Or think about (Forename = Joe Or surname = Bloggs) and City = London...