I have 3 left joined tables in MySql.
contratos, which stores customer data
funcionario, which stores employee data
cobranca, which stores every customer charge.
I want to generate a report based on charge status. But each customer charge has many status, and i want to retrieve the current status.
The following code returns the last update from cobrancas table.
SELECT cob.status, cob.created, con.data_venda, cpn.contrato, con.razao_social, con.cnpj, con.valor, f.nick
FROM cobrancas cob
LEFT JOIN contratos con
ON c.id = cob.contrato
LEFT JOIN funcionarios f
ON f.id = cob.cobrador
WHERE 1=1
ORDER BY cob.created DESC
LIMIT 1
But it returns without a status filter. If i put a WHERE clause like cob.status = 'x', it returns the last record with this status, but it may not be the current. So how can i check if cob.status is the current status in WHERE clause to decide if i will put it in the list? Something like:
WHERE IF(cob.status == the last status inserted AND cob.status == 'x')
Can you understand what i want to do? Thank you.
One solution to use subquery
select * from
(
SELECT cob.status, cob.created, con.data_venda, cpn.contrato, con.razao_social, con.cnpj, con.valor, f.nick
FROM cobrancas cob
LEFT JOIN contratos con
ON c.id = cob.contrato
LEFT JOIN funcionarios f
ON f.id = cob.cobrador
WHERE 1=1
ORDER BY cob.created DESC
LIMIT 1
) where status = 'X'
Related
Here is my SQL query:
SELECT s.name,
f.message,
e.timestamp,
e.result,
o.details
FROM event e
LEFT JOIN feed f ON e.id=f.id
LEFT JOIN oper o ON o.id=e.id
LEFT JOIN system s ON o.id=e.id
WHERE (e.date = ’15-06-2020’)
AND e.oper_id IN (SELECT id from oper
where state = ‘READY1’ or state = ‘READY2’ or state = ‘READY2’
group by state
order by state)
Now I have more then 3 results in my e.results table, however I wanted to take only recent 3 results (so sort on timestamp). So my final result from SQL should be only have recent 3 results.
I tried doing (select e.result from event ORDER BY DESC LIMIT 3) in place of e.result, which is not working.
Any idea how could I achieve this. I am querying MySQL.
firstly make the order in descending order and set limit for 3 in sql right after the order by clause.
change the column in subquery.
SELECT s.name,
f.message,
e.timestamp,
e.result,
o.details FROM event e
LEFT JOIN feed f ON e.id=f.id
LEFT JOIN oper o ON o.id=e.id
LEFT JOIN system s ON o.id=e.id
WHERE (e.date = ’15-06-2020’) AND
e.oper_id IN (SELECT id,state from oper
where state = ‘READY1’ or state = ‘READY2’
group by state order by state desc LIMIT 3)
try this.
SELECT *
FROM notifications
INNER JOIN COMMENT
ON COMMENT.id = notifications.source_id
WHERE idblog IN (SELECT blogs_id
FROM blogs
WHERE STATUS = "active")
INNER JOIN reportmsg
ON reportmsg.msgid = notifications.source_id
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20;
Here I am INNER JOINing notifications with comment and reportmsg; then filtering content with WHERE.
But my problem is that for the first INNER JOIN [i.e, with comment], before joining notifications with comment, I want to match notifications.idblog with blogs.blogs_id and SELECT only those rows where blogs.status = "active".
For better understanding of the code above:
Here, for INNER JOIN, with comment I want to SELECT only those rows in notifications whose idblog matches blogs.blogs_id and has status = "active".
The second INNER JOIN with reportmsg needs not to be altered. I.e, it only filters through uid.
As you can see from the image below, you can just need to merge other tables to notifications table using LEFT JOIN like that:
SELECT n.notificationid, n.uid, n.idblog, n.source_id,
b.blogs_id, b.status,
c.id,
r.msgid
-- ... and the other columns you want
FROM notifications n
LEFT JOIN blogs b ON b.blogs_id = n.idblog AND b.STATUS = "active" AND n.uid =: uid
LEFT JOIN comment c ON c.id = n.source_id
LEFT JOIN reportmsg r ON r.msgid = n.source_id
ORDER BY n.notificationid DESC
LIMIT 20;
There's no need/reason to filter before the second join because you only use inner joins and then the order of joins and WHERE-conditions don't matter:
SELECT n.*, c.*, r.*
FROM notifications AS n
JOIN COMMENT as c
ON n.source_id = c.id
LEFT JOIN blogs as b
ON n.idblogs = b.blogs_id
AND B.STATUS = 'active'
JOIN reportmsg AS R
ON n.source_id = r.msgid
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20
You can switch the order of joins, you can move B.STATUS = 'active' into the join-condition, but all queries will return the same result. (After the edit it's a LEFT JOIN, of course now the result differs)
And of course you shouldn't use *, better list only the columns you actually need.
if query optimizer does its work, it does not matter where you put filtering statement in INNER JOIN case but in the LEFT JOIN it has effects. Putting filtering statement in LEFT JOIN conditions cause table filtered at first and joined after while putting filtering statement in WHERE clause will filter results of join. Hence, if you want to use LEFT JOIN your query must look like:
SELECT nt.*
FROM notifications nt
LEFT JOIN Blogs bg on nt.blogs_id = bg.blogs_id and bg.STATUS = "active"
LEFT JOIN COMMENT cm ON cm.id = nt.source_id
LEFT JOIN reportmsg rm ON rm.msgid = nt.source_id
WHERE uid =: uid
ORDER BY nt.notificationid DESC
LIMIT 20;
It's very unclear what you are after here.. while your table diagram is useful, you should really supply some sample data and an expected result even if it is just a couple of dummy rows for each table.
Queries work row by row, both INNER JOINs are applied to the same notification row and non-matching rows are discarded.
Any filter applies to both JOIN and any returned rows must have a match in BOTH comment and reportmsg.
Perhaps you want two LEFT JOINs that can apply different filters and guessing from the table names perhaps it could look like this:
SELECT *
FROM notifications n
LEFT JOIN blogs b
ON n.blogId = b.blogs_id
LEFT JOIN comment c
ON c.id = n.source_id
AND b.status = "Active"
LEFT JOIN reportmsg rm
ON rm.msgid = n.source_id
WHERE n.uid =: uid
AND (c.id IS NOT NULL OR rm.msgid IS NOT NULL)
ORDER BY n.notificationid DESC
LIMIT 20
You also should work on your naming convention:
notifications, comment -> pick either plural or singular table names
notifications.notificationid, comment.id -> pick adding table name to id
notificationid, source_id -> pick underscore or no separation
idblog, notificationid -> pick prepending or appending id
Currently you pretty much have to look up every id field every time you want to use one.
You should change your query to this:
SELECT *
FROM notifications
INNER JOIN comment ON comment.id = notifications.source_id
INNER JOIN reportmsg ON reportmsg.msgid=notifications.source_id
LEFT JOIN blogs ON notifications.idblog = blogs.blogs_id
WHERE blogs.status = 'active'
ORDER BY notificationid DESC
LIMIT 20;
Some sql query gives me the following result:
As you can see, it already has GROUP BY.
So what I need? I need to group it again (by treatment_name) and count rows for each group. See more details on screenshot.
Here is full query:
SELECT
treatment_summaries.*
FROM `treatment_summaries`
INNER JOIN
`treatments`
ON
`treatments`.`treatment_summary_id` = `treatment_summaries`.`id`
AND
(treatment <> '' and treatment is not null)
INNER JOIN
`treatment_reviews`
ON
`treatment_reviews`.`treatment_id` = `treatments`.`id`
INNER JOIN
`conditions_treatment_reviews`
ON
`conditions_treatment_reviews`.`treatment_review_id` = `treatment_reviews`.`id`
INNER JOIN
`conditions` ON `conditions`.`id` = `conditions_treatment_reviews`.`condition_id`
INNER JOIN `conditions_treatment_summaries` `conditions_treatment_summaries_join`
ON
`conditions_treatment_summaries_join`.`treatment_summary_id` = `treatment_summaries`.`id`
INNER JOIN `conditions` `conditions_treatment_summaries`
ON `conditions_treatment_summaries`.`id` = `conditions_treatment_summaries_join`.`condition_id`
WHERE
`conditions`.`id` = 9
AND `conditions`.`id` IN (9)
AND (latest_review_id is not null)
GROUP BY
treatment_reviews.id
ORDER BY
treatment_summaries.reviews_count desc
LIMIT 20 OFFSET 0
Maybe there is another issue, cause GROUP BY should not leave same lines (for given column), but anyway you can wrap it like this:
SELECT * FROM ( YOUR_SQL_SELECT_WITH_EVERYTHING ) GROUP BY id
So the result you get will behave as another table and you can do all operations like GROUP BY again.
Ok, I tried to simplify my question by abstracting away the details but I'm afraid I wasn't clear and didn't meet moderator requirements. So I will post the full query with my problem in more detail and the actual query I am struggling with. If the question is still inadequate, could you please comment with specifics about what is unclear and I will do my best to clarify.
First, here is the current query that returns all assignment rows for each bed:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assignments.assignment_id,
assignments.student_id,
assignments.assign_dt,
assignments.assigned_by,
assignments.assignment_status,
assignments.expected_arrival_dt as arrival_dt,
assignments.room_charge_type,
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.buildings_id)
LEFT JOIN assignments ON
((beds.bed_id=assignments.bed_id) AND (term_id = #term_id))
LEFT JOIN
students ON (assignments.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assignments.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1) AND
(buildings.building_id = #building_id)
)
ORDER BY BY rooms.room;
The problem is that there may be multiple rows in the "assignments" table for each room distinguished by the "assignment_status" field and I want a single row for each assignment. I want to determine which assignment row to select based on the value in assignment_status. That is if the assignment status is "active", I want that row, otherwise, if there is a row with status "waiting approval" then I want that row, etc...
Barmar's suggestion is given here:
LEFT JOIN (SELECT *
FROM OtherTable
WHERE <criteria>
ORDER BY CASE status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
...
END
LIMIT 1) other
This was very helpful and I attempted this approach:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assign.assignment_id,
assign.student_id,
assign.assign_dt,
assign.assigned_by,
assign.assignment_status,
assign.expected_arrival_dt as arrival_dt,
assign.room_charge_type,
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.buildings_id)
LEFT JOIN (SELECT *
FROM assignments
WHERE ((assignments.bed_id==beds.bed_id) AND (term_id = #term_id))
ORDER BY CASE assignment_status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
END
LIMIT 1) assign
LEFT JOIN
students ON (assign.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assign.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1) AND
(buildings.building_id = #building_id)
)
ORDER BY rooms.room;
But I realized, the problem here is that OtherTable (assignments) is joined to the parent query based on a FK:
((beds.bed_id=assignments.bed_id) AND (term_id = #term_id))
So I can't do the subselect as the beds.bed_id isn't in scope for the subselect. So as Barmar's comment indicates the join criteria needs to be outside the subselect--but I'm having trouble figuring out how to both restrict the results to a single row per room and move the join outside the subselect. I'm wondering if travelboy's suggestion to use GROUP BY may be more fruitful, but haven't been able to determine how the grouping should be done.
Let me know if I can provide additional clarification.
Original Question:
I need from Table A to do a LEFT JOIN on a SINGLE row in another table, Table B meeting certain criteria (there may be multiple or no rows in Table B that meet the criteria). If there are multiple rows I want to select which row in B to join based on the value of a field in Table B. For example, if there is a row in B with status column='Active', I want that row, if not, if there is a row with status='Waiting Approval', I want that row, if there is a row with status='Canceled', I want that row, etc... Can I do this without a sub select? With a sub select?
Use:
LEFT JOIN (SELECT *
FROM OtherTable
WHERE <criteria>
ORDER BY CASE status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
...
END
LIMIT 1) other
In some cases (but not in all cases) you can do it without a sub-select. You would need to GROUP BY a unique field in table A, typically an ID. This ensures that you get only one (or none) row from table B. However, selecting the row you want is the tricky part. You need an aggregating function such as MAX(). If the field in B is a number, that's easy to do. If not, you can apply some SQL functions on the fields in B to calculate something like a score to sort by. For example, Active could correspond to a higher value than Cancelled etc. That will work without a sub-select and likely be faster on big data sets.
With a sub-select it's easy to do. You can either use Barmar's solution, or, if you only need one specific field from B, you can also put the sub-select within the SELECT clause of the outer query.
I need to follow up with some additional testing to make sure this is accomplishing my goal--but I think I've done this using travelboy's suggestion of a group by query combined with barmar's case logic (wish I could split the answer). Here's the query:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assignments.assignment_id,
assignments.student_id,
assignments.assign_dt,
assignments.assigned_by,
assignments.assignment_status,
assignments.expected_arrival_dt as arrival_dt,
assignments.room_charge_type,
MIN(CASE assignments.assignment_status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
END),
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.building_id)
LEFT JOIN assignments
ON ((assignments.bed_id=beds.bed_id) AND (term_id = 28))
LEFT JOIN
students ON (assignments.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assignments.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (assignments.room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1)
)
GROUP BY
bed_id
ORDER BY rooms.room;
I have a function that gets a SQL code and inserts a count field in it and executes the query to return the number of rows in it. The objective is to have a dynamic SQL code and be able to get its record count no matter what code it has, because I use it in a registry filter window and I never know what code may be generated, because the user can add as many filters as he/she wants.
But as I use the group by clause, the result is wrong because it is counting the number of times a main registry appears because of the use on many join connections.
The result of that code above should only one row with a columns with 10 as result, but I get a new table with the first columns with a 2 in the first row and a 1 on the other rows.
If I take off the group by clause I will receive a 11 as a count result, but the first row will be counted twice.
What should I do to get a single row and the correct number?
SELECT
COUNT(*) QUERYRECORDCOUNT, // this line appears only in the Count() function
ARTISTA.*,
CATEGORIA.NOME AS CATEGORIA,
ATIVIDADE.NOME AS ATIVIDADE,
LOCALIDADE.NOME AS CIDADE,
MATRICULA.NUMERO AS MAP
FROM
ARTISTA
LEFT JOIN PERFIL ON PERFIL.REGISTRO = ARTISTA.ARTISTA_ID
LEFT JOIN CATEGORIA ON CATEGORIA.CATEGORIA_ID = PERFIL.CATEGORIA
LEFT JOIN ATIVIDADE ON ATIVIDADE.ATIVIDADE_ID = PERFIL.ATIVIDADE
LEFT JOIN LOCALIDADE ON LOCALIDADE.LOCALIDADE_ID = ARTISTA.LOCAL_ATIV_CIDADE
LEFT JOIN MATRICULA ON MATRICULA.REGISTRO = ARTISTA.ARTISTA_ID
WHERE
((ARTISTA.SIT_PERFIL <> 'NORMAL') AND (ARTISTA.SIT_PERFIL <> 'PRIVADO'))
GROUP BY
ARTISTA.ARTISTA_ID
ORDER BY
ARTISTA.ARTISTA_ID;
This always gives you the number of rows for any query you have:
Select count(*) as rowcount from
(
Paste your query here
) as countquery
Since your are GROUPING BY ARTISTA.ARTISTA_ID, COUNT(*) QUERYRECORDCOUNT will return records count for each ARTISTA.ARTISTA_ID value.
If you want GLOBAL count, then you need to use a nested query:
SELECT COUNT(*) AS QUERYRECORDCOUNT
FROM (SELECT
ARTISTA.*,
CATEGORIA.NOME AS CATEGORIA,
ATIVIDADE.NOME AS ATIVIDADE,
LOCALIDADE.NOME AS CIDADE,
MATRICULA.NUMERO AS MAP
FROM
ARTISTA
LEFT JOIN PERFIL ON PERFIL.REGISTRO = ARTISTA.ARTISTA_ID
LEFT JOIN CATEGORIA ON CATEGORIA.CATEGORIA_ID = PERFIL.CATEGORIA
LEFT JOIN ATIVIDADE ON ATIVIDADE.ATIVIDADE_ID = PERFIL.ATIVIDADE
LEFT JOIN LOCALIDADE ON LOCALIDADE.LOCALIDADE_ID = ARTISTA.LOCAL_ATIV_CIDADE
LEFT JOIN MATRICULA ON MATRICULA.REGISTRO = ARTISTA.ARTISTA_ID
WHERE
((ARTISTA.SIT_PERFIL <> 'NORMAL') AND (ARTISTA.SIT_PERFIL <> 'PRIVADO'))
GROUP BY
ARTISTA.ARTISTA_ID
ORDER BY
ARTISTA.ARTISTA_ID);
In this case, you may not need to select those many columns.
If you need to retrieve the all records count with details, then better to use two separate queries.