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

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

Related

A SQL query to get the last status for a file uploaded possibly using a sub query

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

sql multiple where on join

A have a table called advert_property
And I have a table advert, which is not important, advert properties connects to advert by advert_id column in advert_property table.
I wrote this SQL request :
SELECT *
FROM `advert`
JOIN advert_property ON advert.id = advert_property.advert_id
WHERE (advert_property.property_id = 1
AND advert_property.property_value = "Манчего")
AND (advert_property.property_id = 2
AND advert_property.property_value = "козий")
What I want to get, is advert that have certain properties, for example : I want an idvert that have property_id = 1 and property_value = "Манчего" AND have property_id = 2 and property_value = "козий". SQL request returns null, how should I change my SQL request. Thanks!
Assuming I'm understanding your question correctly and you want to return all adverts that have both properties, then there are a couple ways of doing this using multiple joins, exists, in, group by...
Here is the method using multiple joins:
SELECT a.*
FROM `advert` a
JOIN advert_property ap ON a.id=ap.advert_id
AND ap.property_id = 1 AND ap.property_value = 'Манчего'
JOIN advert_property ap2 ON a.id=ap2.advert_id
AND ap2.property_id = 2 AND ap2.property_value = 'козий'
You can't return all columns * where the property_id is both 1 and 2 because a record can't have two values for the same field. You can, however, return records where the property_id is 1 OR 2. You could then have it only return DISTINCT advert_id where this is true:
SELECT DISTINCT advert_id
FROM `advert` JOIN advert_property ON advert.id=advert_property.advert_id
WHERE (advert_property.property_id = 1 AND advert_property.property_value = "Манчего")
OR (advert_property.property_id = 2 AND advert_property.property_value = "козий")
Query which you are trying to execute will never give you a result because it is trying to get a row with property id = 1 and property id = 2
For same row, there will never be two property_ids (1,2).
You need to review your where conditions.
My be what you need in where condition is as below(Either id will be 1 or id will be 2):
Try this :
(advert_property.property_id = 1 AND advert_property.property_value = "Манчего")
OR
(advert_property.property_id = 2 AND advert_property.property_value = "козий")

Cross Referencing Multiple Tables

What I was trying to do is to get data from multiple tables, supposed that I have the following results in my query:
The numbers in the column ticket_item_type represents certain table. For example, 2 is for tbl_company and 3 is for tbl_lease. Then the details represents the id of a certain record in that table.
Suppose that I want to get the title of those records using ticket_item_type and details. Is it possible to embed it to the results? Or should I make separate queries for each.
I know JOIN, but I is it only for single table?
Here's my MYSQL query for the image above:
SELECT *
FROM
(SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN
(SELECT id
FROM tickets
WHERE hs_customer='1'
AND ticket_status = 'dispatch_reviewed')
AND ticket IN
(SELECT ticket
FROM ticket_items
WHERE ticket_item_type = 5
AND details = '159')) AS TB1
WHERE ticket_item_type IN (3,
2,
8)
You could try something like this:
SELECT
TB1.*,
CASE
WHEN TB1.ticket_item_type = 2 THEN t2.title
WHEN TB1.ticket_item_type = 3 THEN t3.title
WHEN TB1.ticket_item_type = 8 THEN t8.title
ELSE 'NA'
END as title
FROM
(
SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN (SELECT id FROM tickets WHERE hs_customer='1' AND ticket_status = 'dispatch_reviewed')
AND ticket IN (SELECT ticket FROM ticket_items WHERE ticket_item_type = 5 AND details = '159')
) AS TB1
LEFT JOIN tbl_company t2 ON TB1.details = t2.id
LEFT JOIN tbl_lease t3 ON TB1.details = t3.id
LEFT JOIN tbl_next t8 ON TB1.details = t8.id
WHERE ticket_item_type IN (3, 2, 8)
However, this is not a design that I would prefer. Without looking at details of your database it's going to be hard to write a query to cover multiple types of ticket_item_type. I hope this query works for you, though.

updating in a query with joins. Counter to be increased by number of rows

I have written a following query.
UPDATE
tbl_bookings tb
INNER JOIN
tbl_slots ts
ON ( tb.slot_id = ts.id )
SET tb.seat_freed = 1, ts.free_machines = ts.free_machines + 1
WHERE 1
AND tb.seat_freed = 0
AND tb.transactionComplete = 0
Here I am trying to free the seats by updating the seat_freed to 1 and increasing the free_machines counter by 1.
In case, there are more than 1 rows (say 3 rows) returned from tbl_bookings, I would want to increment the counter by .
Is there any way to do it, using the single. I can obviously do it by breaking it down into different queries, but single query is what I desire. :)
You could use a subquery with the exact same conditions to calculat the number of rows which will be affected by the update. I used DISTINCT for the count since i don't know how bookings and slots are related in your example.
UPDATE tbl_bookings tb
INNER JOIN tbl_slots ts ON ( tb.slot_id = ts.id )
INNER JOIN (SELECT count(DISTINCT b.id) seats_to_be_freed
FROM tbl_bookings b INNER JOIN tbl_slots s ON ( b.slot_id = s.id )
WHERE b.seat_freed=0 and b.transactionComplete=0) tmp
SET tb.seat_freed = 1, ts.free_machines = tmp.seats_to_be_freed
WHERE 1
AND tb.seat_freed = 0
AND tb.transactionComplete = 0

Retrieve rows where all related data meets specified criteria

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);