I have a job table and a visit table. A job can have multiple visits. I need to retrieve all jobs, which haven't been set as paid, with all visits tied to that job set as completed.
So basically I need to only retrieve a job if:
It hasn't been paid (paid = 'N')
All the visits tied to that job are set as complete (status = 2)
Obviously doing the following doesn't work as it will return any result where job.paid = 'N' and visit.status = '2':
SELECT *
FROM job INNER JOIN visit
ON job.id = visit.job_id
WHERE job.paid = 'N' AND
visit.status = 2;
I could retrieve the results, and run additional queries to check that all the visits for a job are complete, but I was wondering if it's possible to retrieve the data in a single query?
UPDATE 1
SELECT a.ID -- <<== add some columns here
FROM job a INNER JOIN visit b ON a.id = b.job_ID
WHERE a.paid = 'N'
GROUP BY a.ID
HAVING COUNT(DISTINCT b.Status) = 1 AND MAX(b.status) = 2
SELECT * FROM job WHERE paid = 'N' AND id NOT IN (
SELECT job_id FROM visit WHERE status != 2)
If you have the possibility of a Job that doesn't have any Visit records associated with it, and you'd want to still return that Job record if it has paid = 'N', then you'll want to LEFT JOIN.
SELECT *
FROM JOB j
LEFT JOIN VISIT v
ON j.id = v.job_id
WHERE j.paid = 'N'
AND j.id NOT IN (SELECT job_id FROM visit WHERE status != 2)
SELECT *
FROM job j
WHERE j.paid = 'N' AND
NOT EXISTS (SELECT 1 FROM visit WHERE job_id = j.id AND visit.status <> 2);
Related
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
I need to write a query which I think will need a subquery in it. Currently I'm writing the query as a raw SQL statement using DataGrip and will need to work on a postGres server. I am using Laravel to write the application in which this query needs to work in.
The two tables needed to write the query are media_files and statuses. There is a link between the 2 tables:
media_files.id = statuses.model_id
Files are stored in media_files and can have two statuses which are pending and attached. The statuses for files are stored in statuses. The statuses table can also contain statuses of other things such as tasks, events, users, etc.
I need a way of getting all the files where the last status for them is pending. Some files may not even have a pending status and these can be ignored.
The statuses table can hold multiple statuses of the same media file. So for example you can have:
Record 1
media_files.id = 1
media_files.name = 'CV document'
statuses.id = 2
statuses.model_id = 1
statuses.model_type = 'App\MediaFile'
statuses.name = 'attached'
statuses.created_at = '2020-06-16 17:39:08'
Record 2
media_files.id = 1
media_files.name = 'CV document'
statuses.id = 1
statuses.model_id = 1
statuses.model_type = 'App\MediaFile'
statuses.name = 'pending'
statuses.created_at = '2020-06-14 17:30:00'
I have made a start on the query but it doesn't seem to be working correctly:
select media_files.*, (
select name
from statuses
where model_id = media_files.id
and model_type = 'App\File'
order by statuses.created_at desc
limit 1
)
as latest_status
from media_files
inner join statuses on statuses.model_id = media_files.id
where statuses.model_type = 'App\Entities\Media\File'
order by media_files.id desc;
You can use conditional aggregation to determine if the last pending status is the last status:
select mf.*
from media_files mf join
(select s.model_id,
max(case when s.status = 'pending' then s.created_at end) as last_pending_created_at,
max(s.created_at) as last_created_at
from statuses s
group by s.model_id
) s
on s.model_id = mf.id
where last_pending_created_at = last_created_at;
You can also use a correlated subquery:
select mf.*
from (select mf.*,
(select s.status
from statuses s
where s.model_id = mf.id
order by s.created_at desc
limit 1
) as last_status
from media_files mf
) mf
where last_status = 'pending';
you can use an analytic function as well
SELECT MEDIA_FILES.*,LATEST_STATUS.*
FROM
(SELECT NAME,MODEL_ID,
MAX(CREATED_AT) OVER(PARTITION BY NAME) AS MAX_TM
WHERE MODEL_ID = MEDIA_FILES.ID
AND MODEL_TYPE = 'App\File') AS LATEST_STATUS,MEDIA_FILES
WHERE LATEST_STATUS.MODEL_ID=MEDIA_FILES.MODEL_ID
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
I am trying to add a contact person (T.ContactId) lookup to an existing query. The query uses a client id to get the client from the clients table. I now wish to add T.ContactId to get another name from the clients table. In the script below I have already added 'T.ContactId' to the select but I dont know how to continue from there
select T.Id Tid,Transdate,Quantity Unit,Amount Rate,Discount,T.Comment Comment,T.CmntToInvoice ConInv,T.JobNum JobNum,T.PayNum PayNum,T.ContactId,clients.Id `Id`,`Client`,Cell,Email,Yard,Horse,TransType `Transaction`,PayTypeId,Credit
from
transactions T,clients,yards,horses,transtypes
where
Pending = 'N' and
T.TransTypeId = transtypes.Id and
T.ClientId = clients.Id and
T.HorseId = horses.Id and
T.YardId = yards.Id and
Transdate between '2014-09-08' and '2016-07-08' and
T.JobNum = 0
order by
clients.Id,Transdate asc
You should change your implicit joins to explicit joins and add a second join to get the client id etc for t.contactid
Try this
select T.Id Tid,Transdate,Quantity Unit,Amount Rate,Discount,T.Comment Comment,T.CmntToInvoice ConInv,T.JobNum JobNum,T.PayNum PayNum,
T.ContactId,c1.id as 'ccid',c1.client as 'ContactCLient',
clients.Id `Id`,`Client`,Cell,Email,Yard,Horse,TransType `Transaction`,PayTypeId,Credit
from transactions T
join clients on T.ClientId = clients.Id
join yards on T.YardId = yards.Id
join horse on T.HorseId = horses.Id
join transtypes on T.TransTypeId = transtypes.Id
left outer join clients c1 on c1.id = t.contactid
where Pending = 'N' and
Transdate between '2014-09-08' and '2016-07-08' and
T.JobNum = 0
order by clients.Id,Transdate asc
I haven't tested this but if you can publish sample data and expected results then I would be happy to revisit.
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.