Postgres: Optimize Query - sql

I have a situation where I need to show a list of subjects to which a learning material or a test has been published to.
The query below works but it takes about ~8s. Is there a better way I can optimize this query?
Thank you.
SELECT sj.*, sj.id AS id FROM subjects sj
WHERE (
(SELECT COUNT(lmc.id) FROM learning_materials_codes lmc
INNER JOIN students s ON lmc.student_id = s.id
INNER JOIN learning_materials lm ON lmc.learning_material_id = lm.id
WHERE lmc.student_id = 1 AND sj.id = ANY(lm.subject_ids)) > 0
OR
(SELECT COUNT(tc.id) FROM test_codes tc
INNER JOIN students s ON tc.student_id = s.id
INNER JOIN tests t ON tc.test_id = t.id
WHERE tc.student_id = 1 AND t.subject_id = sj.id) > 0
)
AND sj.school_id = 1
Table structure below
subjects
~~~~~~~~~~~
id
name
school_id
...
students
~~~~~~~~~~
id
f_name
l_name
school_id
...
tests
~~~~~~~~~
id
title
subject_id
...
test_codes
~~~~~~~~~~
test_id
student_id
code
...
learning_materials
~~~~~~~~~~~~~~~~~
id
title
subject_ids []
...
learning_material_codes
~~~~~~~~~~~~~~~~~~~~~~~~
learning_material_id
student_id
code
...
Note:
Each time a learning material or a test is published an access code is generated for students and that data is kept in learning_material_codes or test_codes table

I would rewrite the query to
SELECT sj.*
FROM subjects sj
WHERE EXISTS (SELECT 1
FROM learning_materials_codes lmc
INNER JOIN students s ON lmc.student_id = s.id
INNER JOIN learning_materials lm ON lmc.learning_material_id = lm.id
WHERE lmc.student_id = 1
AND sj.id = ANY(lm.subject_ids)
AND lmc.id IS NOT NULL)
AND sj.school_id = 1
UNION
SELECT sj.*
FROM subjects sj
WHERE EXISTS (SELECT 1
FROM test_codes tc
INNER JOIN students s ON tc.student_id = s.id
INNER JOIN tests t ON tc.test_id = t.id
WHERE tc.student_id = 1
AND t.subject_id = sj.id
AND tc.id IS NOT NULL)
AND sj.school_id = 1;
That way, PostgreSQL can use a semi-join and may be faster. If you don't mind duplicate result rows, use UNION ALL instead of UNION for better performance.
More performance may be gained with appropriate indexes, but it requires EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS) output to assess that.

Related

Return the ID's & first and last names of all students who have taken both ENG 101 and ENG 102 (2 columns, 11 rows)

I've created two instances of the tables using aliases But my query doesn't return any results when I search for student who has taken one AND the other class.
select s01.StudentID, s01.StudFirstName, s01.StudLastName
from Students s01
join Student_Schedules as ss01
on s01.StudentID = ss01.StudentID
join Classes as c01
on ss01.ClassID = c01.ClassID
join Subjects as sbj01
on c01.SubjectID = sbj01.SubjectID
--rejoins
join Students as s02
on s01.StudentID = s02.StudentID
join Student_Schedules as ss02
on ss01.StudentID = ss02.StudentID
join Classes as c02
on c01.ClassID = c02.ClassID
join Subjects as sbj02
on sbj01.SubjectID = sbj02.SubjectID
where sbj01.SubjectCode like 'ENG 101'
and sbj02.SubjectCode like 'ENG 102';
If you wanted all the students who have taken 3 subjects or more would you use triple joins (or more)?
This is a performance killer.
Instead of all these double joins, join once, filter and aggregate with a condition in the having clause:
select s.StudentID, s.StudFirstName, s.StudLastName
from Students s
join Student_Schedules as ss on s.StudentID = ss.StudentID
join Classes as c on ss.ClassID = c.ClassID
join Subjects as sbj on c.SubjectID = sbj.SubjectID
where sbj.SubjectCode in ('ENG 101', 'ENG 102')
group by s.StudentID, s.StudFirstName, s.StudLastName
having count(distinct sbj.SubjectCode) = 2
I believe that distinct is not really needed in count() if there are no duplicates.
I think that this is what you mean:
select s.StudentID, s.StudFirstName, s.StudLastName
from Students s
join Student_Schedules as ss01 on s.StudentID = ss01.StudentID
join Classes as c01 on ss01.ClassID = c01.ClassID
join Subjects as sbj01 on c01.SubjectID = sbj01.SubjectID
join Student_Schedules as ss02 on s.StudentID = ss02.StudentID
join Classes as c02 on ss02.ClassID = c02.ClassID
join Subjects as sbj02 on c02.SubjectID = sbj02.SubjectID
where sbj01.SubjectCode = 'ENG 101' and sbj02.SubjectCode = 'ENG 102';
Explanation:
your query is trying to find a student who took both 'ENG 101' and 'ENG 102' classes; for this, you are following two different paths (Students > Student_Schedule > Subjects)
but you are using join conditions such as ss01.StudentID = ss02.StudentID, which force both paths to be identical; so you actually end up looking for a Subject whose code is both 'ENG 101' and 'ENG 102', which is not possible
I fixed the join conditions to what I think that you want.
Side notes:
you don't need to bring in Students twice
LIKE 'ENG 101' is equivalent to = 'ENG 101'
Personally I would re-arrange the logic using exists as this is how I would be thinking about it in my mind.
I would use the full join name, inner join for clarity.
I would fully qualify the table names with the schema name e.g. dbo.
I would use = instead of like as you are not performing a wildcard search
select s01.StudentID, s01.StudFirstName, s01.StudLastName
from dbo.Students s01
where exists (
select 1
from dbo.Student_Schedules as ss01
inner join dbo.Classes as c01 on ss01.ClassID = c01.ClassID
inner join dbo.Subjects as sbj01 on c01.SubjectID = sbj01.SubjectID
where s01.StudendID = ss01.StudendID
and sbj01.SubjectCode = 'ENG 101'
)
and exists (
select 1
from dbo.Student_Schedules as ss02
inner join dbo.Classes as c02 on ss02.ClassID = c02.ClassID
inner join dbo.Subjects as sbj02 on c02.SubjectID = sbj02.SubjectID
where s01.StudendID = ss02.StudendID
and sbj02.SubjectCode = 'ENG 102'
)

Postgresql select related tags (many to many)

I have a VIEW named review, which is related to a cars table, and the cars table have a many to many relationship with table tags (through join table named cars_tags), but what I need is retrieve the reviews from the cars which are related with some tags, AND at same time related to another tags. What I have today is the following SQL code:
SELECT "cars"."review".*
FROM "cars"."review"
LEFT JOIN cars.cars ON (cars.review.car_id = cars.cars.id)
LEFT JOIN cars.makes ON (cars.cars.make_id = cars.makes.id)
LEFT JOIN cars.cars_tags ON (cars.cars.id = cars.cars_tags.car_id)
LEFT JOIN cars.tags ON (cars.cars_tags.tag_id = cars.tags.id)
WHERE (cars.tags.id IN ('91782e95-8c5d-4254-82ab-b11a21306c18'))
AND (cars.tags.id IN ('031cec30-df27-471e-858d-53c3d9657c8a'))
ORDER BY "cars"."review"."score" DESC LIMIT 100
This SQL brings me NO results, but I am sure that there are cars which are related to first id:'91782e95-8c5d-4254-82ab-b11a21306c18'AND '031cec30-df27-471e-858d-53c3d9657c8a' at same time.
What am I doing wrong?
bool_or
select r.col1, r.col2
from
cars.review r
left join
cars.cars on r.car_id = cars.id
inner join
cars.cars_tags on cars.id = cars_tags.car_id
inner join
cars.tags on cars_tags.tag_id = tags.id
group by r.col1, r.col2
having
bool_or (tags.id = '91782e95-8c5d-4254-82ab-b11a21306c18')
and
bool_or (tags.id = '031cec30-df27-471e-858d-53c3d9657c8a')
order by r.score desc
limit 100
exists version:
select col1, col2
from cars.review
where exists (
select 1
from
cars.cars
inner join
cars.cars_tags on cars.id = cars_tags.car_id
inner join
cars.tags on cars_tags.tag_id = tags.id
where review.car_id = cars.id
group by 1
having
bool_or (tags.id = '91782e95-8c5d-4254-82ab-b11a21306c18')
and
bool_or (tags.id = '031cec30-df27-471e-858d-53c3d9657c8a')
)
order by score desc
limit 100
From what I understand you're interested in reviews for a car that has 2 specific tags.
This can be done with the query below. I've removed the reference to cars.make as you weren't retrieving any data from it. Also I've removed the reference to cars.tags as the only information you were using was the tag id which is in the cars_tags table.
SELECT "cars"."review".*
FROM "cars"."review"
WHERE
EXISTS (SELECT * FROM cars.cars_tags
WHERE cars.cars_tags.car_id = cars.review.cars_id
AND cars.cars_tags.tag_id = '91782e95-8c5d-4254-82ab-b11a21306c18')
AND
EXISTS (SELECT * FROM cars.cars_tags
WHERE cars.cars_tags.car_id = cars.review.cars_id
AND cars.cars_tags.tag_id = '9031cec30-df27-471e-858d-53c3d9657c8a')
ORDER BY "cars"."review"."score" DESC LIMIT 100
The query simply finds all reviews where there exists a cars_tags entry for the two tag_id's you're after.

Filtering rows if two columns values are not same - sql server

I need to apply date filter on rows where OwningOfficeID and ScopeOfficeID is not same.
Here is the main query;
SELECT distinct v.VisitID, a.OfficeID AS OwningOfficeID,
scp.OfficeID AS ScopeOfficeID, V.VisitDate,
a.staffID as OwningStaff ,scp.StaffID as OwningScope
FROM Visits v
INNER JOIN VisitDocs vdoc ON vdoc.VisitID = v.VisitID
INNER JOIN InspectionScope scp ON scp.ScopeID = v.ScopeID
INNER JOIN Assignments a ON a.AssignmentID = scp.AssignmentID
INNER JOIN Staff s ON s.StaffID = a.StaffID
WHERE
v.VisitType = 1 AND
--'SCOPE OWNER AND LOOK FOR INSPECTION REPORT BUT NOT FOR COORD/FINAL REPORT.
(scp.StaffID = 141
AND EXISTS(SELECT *
FROM VisitDocs d
WHERE d.VisitID = v.VisitID
AND d.docType = 13)
AND NOT EXISTS(SELECT *
FROM VisitDocs d
WHERE d.VisitID = v.VisitID AND d.docType IN (1,2))
)
OR
--'ASSIGNMENT OWNER AND NOT SCOPE OWNER AND LOOK FOR COORDINATOR REPORT.
(a.StaffID = 141 AND scp.StaffID != 141
AND EXISTS(SELECT *
FROM VisitDocs d
WHERE d.VisitID = v.VisitID
AND d.docType = 2)
AND NOT EXISTS(SELECT * FROM VisitDocs d
WHERE d.VisitID = v.VisitID AND d.docType IN (1))
)
Result Set
Following condition can be applied to outer select query to achieve the results.
(OwningOfficeID <> ScopeOfficeID AND VisitDate >='01/11/2012' OR OwningOfficeID = ScopeOfficeID)
Is there anyway to do it in the one select statement?
I'm not sure what you mean by one select statement, but I think what you really want to know is how to do this query so that it runs quickly, is easy to see it is correct and easy to maintain.
Here is how to do that; use CTEs. (If you don't know what a CTE is go read about them and then come back here -- they are documented on Microsoft's website.)
CTEs can be much faster and clearer than sub-queries. Let me show you how, taking the code above and re-factoring with CTEs I get:
WITH DocTypes AS
(
SELECT VisitID, docType
FROM VisitDocs
), DocType13 AS
(
SELECT VisitID
FROM DocTypes
WHERE docType = 13
), DocType1and2 AS
(
SELECT VisitID
FROM DocTypes
WHERE docType IN (1,2)
), DocType1 AS
(
SELECT VisitID
FROM DocTypes1and2
WHERE docType = 1
), DocType2 AS
(
SELECT VisitID
FROM DocTypes1and2
WHERE docType = 2
), Base AS
(
SELECT distinct v.VisitID, a.OfficeID AS OwningOfficeID,
scp.OfficeID AS ScopeOfficeID, V.VisitDate,
a.staffID as OwningStaff ,scp.StaffID as OwningScope
FROM Visits v
INNER JOIN VisitDocs vdoc ON vdoc.VisitID = v.VisitID
INNER JOIN InspectionScope scp ON scp.ScopeID = v.ScopeID
INNER JOIN Assignments a ON a.AssignmentID = scp.AssignmentID
INNER JOIN Staff s ON s.StaffID = a.StaffID
WHERE
v.VisitType = 1 AND
--'SCOPE OWNER AND LOOK FOR INSPECTION REPORT BUT NOT FOR COORD/FINAL REPORT.
(scp.StaffID = 141
AND EXISTS(SELECT * FROM DocType13 d WHERE d.VisitID = v.VisitID)
AND NOT EXISTS(SELECT * FROM DocType1and2 d WHERE d.VisitID = v.VisitID)
)
OR
--'ASSIGNMENT OWNER AND NOT SCOPE OWNER AND LOOK FOR COORDINATOR REPORT.
(a.StaffID = 141 AND scp.StaffID != 141
AND EXISTS(SELECT * FROM DocType2 d WHERE d.VisitID = v.VisitID)
AND NOT EXISTS(SELECT * FROM DocType1 d WHERE d.VisitID = v.VisitID)
)
)
SELECT *
FROM Base
WHERE (OwningOfficeID <> ScopeOfficeID
AND VisitDate >='01/11/2012'(
OR OwningOfficeID = ScopeOfficeID
It should be clear why this is better, but if it isn't briefly:
All the selects you see at the beginning are exactly as they were in the original query, but because they are broken out prior the optimizer has an easier time, it will do better than when they were sub-queries -- I've seen huge changes in some cases, but in any case the optimizer has the option now to do a better job of memory caching and such.
It is clearer and easier to test. If you have problems with the query you can test it easy, comment out the main select and just select one of the the sub-queries, is it what you expect? These sub-queries are simple, but they can get complicated and this makes it easier.
You said you wanted to apply some logic to this query. I've added it to the CTE, as you can see it makes this complicated "layering" simple.

Very slow stored procedure

I have a hard time with query optimization, currently I'm very close to the point of database redesign. And the stackoverflow is my last hope. I don't think that just showing you the query is enough so I've linked not only database script but also attached database backup in case you don't want to generate the data by hand
Here you can find both the script and the backup
The problems start when you try to do the following...
exec LockBranches #count=64,#lockedBy='034C0396-5C34-4DDA-8AD5-7E43B373AE5A',#lockedOn='2011-07-01 01:29:43.863',#unlockOn='2011-07-01 01:32:43.863'
The main problems occur in this part:
UPDATE B
SET B.LockedBy = #lockedBy,
B.LockedOn = #lockedOn,
B.UnlockOn = #unlockOn,
B.Complete = 1
FROM
(
SELECT TOP (#count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
FROM Objectives AS O
INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
INNER JOIN Branches AS B ON B.GenerationID = G.ID
INNER JOIN
(
SELECT SB.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes
FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
INNER JOIN
(
SELECT P.ID, 1 AS SuitableProbes
FROM Probes AS P
/* ----> */ INNER JOIN Results AS R ON P.ID = R.ProbeID /* SSMS Estimated execution plan says this operation is the roughest */
GROUP BY P.ID
HAVING COUNT(R.ID) > 0
) AS X ON P.ID = X.ID
GROUP BY SB.BranchID
) AS X ON X.BranchID = B.ID
WHERE
(O.Active = 1)
AND (B.Sealed = 0)
AND (B.GenerationNo < O.BranchGenerations)
AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
AND (B.Complete = 1 OR X.SuitableProbes = O.BranchSize * O.EstimateCount * O.ProbeCount)
) AS B
EDIT: Here are the amounts of rows in each table:
Spicies 71536
Results 10240
Probes 10240
SpicieBranches 4096
Branches 256
Estimates 5
Generations 1
Versions 1
Objectives 1
Somebody else might be able to explain better than I can why this is much quicker. Experience tells me when you have a bunch of queries that collectively run slow together but should be quick in their individual parts then its worth trying a temporary table.
This is much quicker
ALTER PROCEDURE LockBranches
-- Add the parameters for the stored procedure here
#count INT,
#lockedOn DATETIME,
#unlockOn DATETIME,
#lockedBy UNIQUEIDENTIFIER
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
--Create Temp Table
SELECT SpicieBranches.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes
INTO #BranchSuitableProbeCount
FROM SpicieBranches
INNER JOIN Probes AS P ON P.SpicieID = SpicieBranches.SpicieID
INNER JOIN
(
SELECT P.ID, 1 AS SuitableProbes
FROM Probes AS P
INNER JOIN Results AS R ON P.ID = R.ProbeID
GROUP BY P.ID
HAVING COUNT(R.ID) > 0
) AS X ON P.ID = X.ID
GROUP BY SpicieBranches.BranchID
UPDATE B SET
B.LockedBy = #lockedBy,
B.LockedOn = #lockedOn,
B.UnlockOn = #unlockOn,
B.Complete = 1
FROM
(
SELECT TOP (#count) Branches.LockedBy, Branches.LockedOn, Branches.UnlockOn, Branches.Complete
FROM Objectives
INNER JOIN Generations ON Generations.ObjectiveID = Objectives.ID
INNER JOIN Branches ON Branches.GenerationID = Generations.ID
INNER JOIN #BranchSuitableProbeCount ON Branches.ID = #BranchSuitableProbeCount.BranchID
WHERE
(Objectives.Active = 1)
AND (Branches.Sealed = 0)
AND (Branches.GenerationNo < Objectives.BranchGenerations)
AND (Branches.LockedBy IS NULL OR DATEDIFF(SECOND, Branches.UnlockOn, GETDATE()) > 0)
AND (Branches.Complete = 1 OR #BranchSuitableProbeCount.SuitableProbes = Objectives.BranchSize * Objectives.EstimateCount * Objectives.ProbeCount)
) AS B
END
This is much quicker with an average execution time of 54ms compared to 6 seconds with the original one.
EDIT
Had a look and combined my ideas with those from RBarryYoung's solution. If you use the following to create the temporary table
SELECT SB.BranchID AS BranchID, COUNT(*) AS SuitableProbes
INTO #BranchSuitableProbeCount
FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
WHERE EXISTS(SELECT * FROM Results AS R WHERE R.ProbeID = P.ID)
GROUP BY SB.BranchID
then you can get this down to 15ms which is 400x better than we started with. Looking at the execution plan shows that there is a table scan happening on the temp table. Normally you avoid table scans as best you can but for 128 rows (in this case) it is quicker than whatever it was doing before.
This is basically a complete guess here, but in times past I've found that joining onto the results of a sub-query can be horrifically slow. That is, the subquery was being evaluated way too many times when it really didn't need to.
The way around this was to move the subqueries into CTEs and to join onto those instead. Good luck!
It appears the join on the two uniqueidentifier columns are the source of the problem. One is a clustered index, the other non-clustered on the (FK table). Good that there are indexes on them. Unfortunately guids are notoriously poor performing when joining with large numbers of rows.
As troubleshooting steps:
what state are the indexes in? When was the last time the statistics were updated?
how performant is that subquery onto itself, when executed adhoc? i.e. when you run this statement by itself, how fast does the resultset return? acceptable?
after rebuilding the 2 indexes, and updating statistics, is there any measurable difference?
SELECT P.ID, 1 AS SuitableProbes FROM Probes AS P
INNER JOIN Results AS R ON P.ID = R.ProbeID
GROUP BY P.ID HAVING COUNT(R.ID) > 0
The following runs about 15x faster on my system:
UPDATE B
SET B.LockedBy = #lockedBy,
B.LockedOn = #lockedOn,
B.UnlockOn = #unlockOn,
B.Complete = 1
FROM
(
SELECT TOP (#count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
FROM Objectives AS O
INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
INNER JOIN Branches AS B ON B.GenerationID = G.ID
INNER JOIN
(
SELECT SB.BranchID AS BranchID, COUNT(*) AS SuitableProbes
FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
WHERE EXISTS(SELECT * FROM Results AS R WHERE R.ProbeID = P.ID)
GROUP BY SB.BranchID
) AS X ON X.BranchID = B.ID
WHERE
(O.Active = 1)
AND (B.Sealed = 0)
AND (B.GenerationNo < O.BranchGenerations)
AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
AND (B.Complete = 1 OR X.SuitableProbes = O.BranchSize * O.EstimateCount * O.ProbeCount)
) AS B
Insertion of sub query into local temporary table
SELECT SB.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes
into #temp FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
INNER JOIN
(
SELECT P.ID, 1 AS SuitableProbes
FROM Probes AS P
/* ----> */ INNER JOIN Results AS R ON P.ID = R.ProbeID /* SSMS Estimated execution plan says this operation is the roughest */
GROUP BY P.ID
HAVING COUNT(R.ID) > 0
) AS X ON P.ID = X.ID
GROUP BY SB.BranchID
The below query shows the partial joins with the corresponding table instead of complete!!
UPDATE B
SET B.LockedBy = #lockedBy,
B.LockedOn = #lockedOn,
B.UnlockOn = #unlockOn,
B.Complete = 1
FROM
(
SELECT TOP (#count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
From
(
SELECT ID, BranchGenerations, (BranchSize * EstimateCount * ProbeCount) as MultipliedFactor
FROM Objectives AS O WHERE (O.Active = 1)
)O
INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
Inner Join
(
Select Sealed, GenerationNo, LockedBy, UnlockOn, ID, Complete
From Branches
Where B.Sealed = 0 AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
)B ON B.GenerationID = G.ID
INNER JOIN
(
Select * from #temp
) AS X ON X.BranchID = B.ID
WHERE
AND (B.GenerationNo < O.BranchGenerations)
AND (B.Complete = 1 OR X.SuitableProbes = O.MultipliedFactor)
) AS B

SQL joins "going up" two tables

I'm trying to create a moderately complex query with joins:
SELECT `history`.`id`,
`parts`.`type_id`,
`serialized_parts`.`serial`,
`history_actions`.`action`,
`history`.`date_added`
FROM `history_actions`, `history`
LEFT OUTER JOIN `parts` ON `parts`.`id` = `history`.`part_id`
LEFT OUTER JOIN `serialized_parts` ON `serialized_parts`.`parts_id` = `history`.`part_id`
WHERE `history_actions`.`id` = `history`.`action_id`
AND `history`.`unit_id` = '1'
ORDER BY `history`.`id` DESC
I'd like to replace `parts`.`type_id` in the SELECT statement with `part_list`.`name` where the relationship I need to enforce between the two tables is `part_list`.`id` = `parts`.`type_id`. Also I have to use joins because in some cases `history`.`part_id` may be NULL which obviously isn't a valid part id. How would I modify the query to do this?
Here is some sample date as requested:
history table:
(source: ianburris.com)
serialized_parts table:
(source: ianburris.com)
parts table:
(source: ianburris.com)
part_list table:
(source: ianburris.com)
And what I want to see is:
id name serial action date_added
4 Battery 567 added 2010-05-19 10:42:51
3 Antenna Board 345 added 2010-05-19 10:42:51
2 Main Board 123 added 2010-05-19 10:42:51
1 NULL NULL created 2010-05-19 10:42:51
This would at least be on the right track...
If you're looking to NOT show any parts with an invalid ID, simply change the LEFT JOINs to INNER JOINs (they will restrict NULL values)
SELECT `history`.`id`
, `parts`.`type_id`
, `part_list`.`name`
, `serialized_parts`.`serial`
, `history_actions`.`action`
, `history`.`date_added`
FROM `history_actions`
INNER JOIN `history` ON `history`.`action_id` = `history_actions`.`id`
LEFT JOIN `parts` ON `parts`.`id` = `history`.`part_id`
LEFT JOIN `serialized_parts` ON `serialized_parts`.`parts_id` = `history`.`part_id`
LEFT JOIN `part_list` ON `part_list`.`id` = `parts`.`type_id`
WHERE `history`.`unit_id` = '1'
ORDER BY `history`.`id` DESC
Boy, these backticks make my eyes hurt.
SELECT
h.id,
p.type_id,
pl.name,
sp.serial,
ha.action,
h.date_added
FROM
history h
INNER JOIN history_actions ha ON ha.id = h.action_id
LEFT JOIN parts p ON p.id = h.part_id
LEFT JOIN serialized_parts sp ON sp.parts_id = h.part_id
LEFT JOIN part_list pl ON pl.id = p.type_id
WHERE
h.unit_id = '1'
ORDER BY
history.id DESC