I'm trying to run this query:
SELECT
Destaque.destaque, Noticia.id, Noticia.antetitulo,
Noticia.titulo, Noticia.lead, Noticia.legenda,
Noticia.publicacao, Seccao.descricao, Album.pasta,
Foto.ficheiro, Foto.descricao, Cronista.nome,
Cronista.profissao, Cronista.ficheiro,
AudioFile.*, AudioCollection.*, VideoFile.*, VideoCollection.*
FROM
nt_highlights AS Destaque
LEFT JOIN nt_noticias AS Noticia ON Destaque.noticia_id = Noticia.id
LEFT JOIN mm_fotos AS Foto ON Noticia.foto_id = Foto.id
LEFT JOIN nt_temas AS Seccao ON Noticia.tema_id = Seccao.id
LEFT JOIN mm_albuns AS Album ON Foto.album_id = Album.id
LEFT JOIN nt_cronistas AS Cronista ON Cronista.id = Noticia.cronista_id
LEFT JOIN ntNoticias_mmFiles AS Rel ON Rel.noticia_id = Noticia.id
LEFT JOIN mm_files AS AudioFile ON AudioFile.id = Rel.file_id
LEFT JOIN mm_coleccoes AS AudioCollection ON AudioFile.coleccao_id = AudioCollection.id
LEFT JOIN mm_files AS VideoFile ON VideoFile.id = Rel.file_id
LEFT JOIN mm_coleccoes AS VideoCollection ON VideoFile.coleccao_id = VideoCollection.id
WHERE
Destaque.area_id = 1
AND Noticia.paraPublicacao = 1
AND Noticia.publicacao <= NOW()
AND (AudioFile.mimeType != '' OR AudioFile.id IS NULL)
AND (VideoFile.mimeType = '' OR VideoFile.id IS NULL)
ORDER BY
Destaque.destaque
This will get me a number of articles (from nt_noticias) and the idea is to get at the same time a Video and an Audio file from the mm_files table.
What happens is that when I have an article with a sound and a video, MySQL will return 4 rows:
with the sound (video is null)
with the video (sound is null)
with all nulls
with the sound AND the video
How can I "force" it to return just one row per article with any existing video AND audio associated? What am I doing wrong here?
I think you want something like this:
SELECT
Destaque.destaque, Noticia.id, Noticia.antetitulo,
Noticia.titulo, Noticia.lead, Noticia.legenda,
Noticia.publicacao, Seccao.descricao, Album.pasta,
Foto.ficheiro, Foto.descricao, Cronista.nome,
Cronista.profissao, Cronista.ficheiro,
AudioFile.*, AudioCollection.*, VideoFile.*, VideoCollection.*
FROM
nt_highlights AS Destaque
LEFT JOIN nt_noticias AS Noticia ON Destaque.noticia_id = Noticia.id
LEFT JOIN mm_fotos AS Foto ON Noticia.foto_id = Foto.id
LEFT JOIN nt_temas AS Seccao ON Noticia.tema_id = Seccao.id
LEFT JOIN mm_albuns AS Album ON Foto.album_id = Album.id
LEFT JOIN nt_cronistas AS Cronista ON Cronista.id = Noticia.cronista_id
LEFT JOIN ntNoticias_mmFiles AS AudioRel ON Rel.noticia_id = Noticia.id
AND AudioRel.file_id IN (
SELECT file_id
FROM ntNoticias_mmFiles
WHERE noticia_id = Noticia.id AND IsAudioFile = 1 /* whatever the check is */
LIMIT 1
)
LEFT JOIN mm_files AS AudioFile ON AudioFile.id = Rel.file_id
LEFT JOIN mm_coleccoes AS AudioCollection ON AudioFile.coleccao_id = AudioCollection.id
LEFT JOIN ntNoticias_mmFiles AS VideoRel ON VideoRel.noticia_id = Noticia.id
AND VideoRel.file_id IN (
SELECT file_id
FROM ntNoticias_mmFiles
WHERE noticia_id = Noticia.id AND IsVideoFile = 1 /* whatever the check is */
LIMIT 1
)
LEFT JOIN mm_files AS VideoFile ON VideoFile.id = Rel.file_id
AND VideoFile.IsVideoFile = 1
LEFT JOIN mm_coleccoes AS VideoCollection ON VideoFile.coleccao_id = VideoCollection.id
WHERE
Destaque.area_id = 1
AND Noticia.paraPublicacao = 1
AND Noticia.publicacao <= NOW()
ORDER BY
Destaque.destaque
My thought was this:
You want one audio file and one video file, at most. There are several files available per Noticia, so you need to make sure that a maximum of one file per type gets into the join. This also means you have to join in the ntNoticias_mmFiles table twice — once per type.
This is what the sub-queries in the join conditions are supposed to do: Select one row per file type. Going on from there you LEFT JOIN the rest of the data in, just like you already do.
The JOIN will return all the combinations, that's the problem.
If you only have one audio and/or videofile per article then you might want to look at subselects.
In SQL Server this would look something like (untested code):
SELECT title,
(select TOP 1 audio from audio where audio.aid = articles.id) as Audio,
(select TOP 1 video from video where video.aid = articles.id) as Video
FROM articles
Be careful that on large datasets this can perform poorly as the subselects in this example are executed individually for each row returned to the outer query. For example, if you return 10,000 articles then a total of 20,001 queries would actually be executed on the server.
There are other possible answers to overcome this but they get more involved (I suspect you could do something with a derived table but it eludes me at the moment).
You probably want to optimize that join query into a view. It's a large query, and with that many joins, it's going to be pretty inefficient. Plus, a view helps you debug the joins and will basically simplifies by allowing you to write your joins (in the view) and the WHERE clause (in your select from the view) separately, which can help with debugging the queries.
Related
I'm not familiar with MS Access 2003-2007 SQL, but I have to maintain/extend a project that uses it. (The original author has left the company. Hooray for legacy code.) What I'm trying to do is join a number of related tables so that the query gives me the number of transactions for a particular user within a particular time range. The end result of this is that I want to see how many hours passed between an OUT event and the previous IN event for that user.
The code I have so far is as follows:
SELECT Directions.DirectionText, Transactions.Timestamp
FROM Users
LEFT JOIN AccessNumbers ON Users.AccessNumberID = AccessNumbers.AccessNumberID
LEFT JOIN Transactions ON AccessNumbers.Number = Transactions.Number
LEFT JOIN Events ON Transactions.Event = Events.EventNumber
LEFT JOIN Readers ON Transactions.ReaderID = Readers.ReaderID
LEFT JOIN Directions ON Readers.Direction = Direction.Direction
WHERE
(Events.EventNum IN (1, 22)) AND
(Users.[Name] = "firstName") AND
(Users.Surname = "Surname") AND
(Transactions.Timestamp >=#2017-04-10 01:00:00#) AND
(Transactions.Timestamp <=#2017-05-09 14:57:30#)
ORDER BY Transactions.Timestamp
The error I receive is "Syntax Error (missing operator in query expression 'Users.AccessNumberID = ... Direction.Direction'
I have also tried the following, to receive "Syntax Error on JOIN operation':
SELECT Directions.DirectionText, Transactions.Timestamp
FROM Users
LEFT JOIN (AccessNumbers ON Users.AccessNumberID =
AccessNumbers.AccessNumberID)
ON (AccessNumbers.Number = Transactions.Number)
ON (Transactions.Event = Events.EventNumber)
ON (Transactions.ReaderID = Readers.ReaderID)
ON (Readers.Direction = Direction.Direction)
WHERE ...
I'm aware that the SQL needs parentheses, but I don't know where to place them.
I've had issues with Access trying to put in my parentheses for me in the past... especially with LEFT joins. I'd normally parenthesize your first query like so:
SELECT Directions.DirectionText, Transactions.Timestamp
FROM ((((Users
LEFT JOIN AccessNumbers ON Users.AccessNumberID = AccessNumbers.AccessNumberID)
LEFT JOIN Transactions ON AccessNumbers.Number = Transactions.Number)
LEFT JOIN Events ON Transactions.Event = Events.EventNumber)
LEFT JOIN Readers ON Transactions.ReaderID = Readers.ReaderID)
LEFT JOIN Directions ON Readers.Direction = Directions.Direction
WHERE
(Events.EventNum IN (1, 22)) AND
(Users.[Name] = "firstName") AND
(Users.Surname = "Surname") AND
(Transactions.Timestamp >=#2017-04-10 01:00:00#) AND
(Transactions.Timestamp <=#2017-05-09 14:57:30#)
ORDER BY Transactions.Timestamp
Your second query would need more LEFT JOIN clauses thrown in to function.
A simple trick is the following:
Add a closing parenthesis before each LEFT beyond the first.
Add an opening parenthesis after FROM for each LEFT beyond the first.
Result:
SELECT Directions.DirectionText, Transactions.Timestamp
FROM ((((Users
LEFT JOIN AccessNumbers ON Users.AccessNumberID = AccessNumbers.AccessNumberID
) LEFT JOIN Transactions ON AccessNumbers.Number = Transactions.Number
) LEFT JOIN Events ON Transactions.Event = Events.EventNumber
) LEFT JOIN Readers ON Transactions.ReaderID = Readers.ReaderID
) LEFT JOIN Directions ON Readers.Direction = Direction.Direction
WHERE
(Events.EventNum IN (1, 22)) AND
(Users.[Name] = "firstName") AND
(Users.Surname = "Surname") AND
(Transactions.Timestamp >=#2017-04-10 01:00:00#) AND
(Transactions.Timestamp <=#2017-05-09 14:57:30#)
ORDER BY Transactions.Timestamp
The result is the same as Sturgus says, but adding them this way makes sense to me. For layout purposes, you might move each closing parenthesis to the line above it.
I am new to JOINS and testing my query, but it's just not working for me...
The situation:
The database has got the following columns:
links (contains unique data)
cl_link (contains the relation between links & cats)
cats (cat. descriptions
images (contains multiple images of one link)
cfvalues (contains the values of the multiple custom fiels
customfields (contains the multiple customfields)
I am using the following query, but the Joins are not working for me. Because I only get one image while sometimes there are multiple. And I only get one customfield instead of multiple and I get none cfvalues.
I guess something is wrong with the JOINS, but I am not sure. Can somebody help me out here?
The SQL
SELECT DISTINCT
rqypj_mt_links.link_name,
rqypj_mt_links.link_desc,
rqypj_mt_links.address,
rqypj_mt_links.city,
rqypj_mt_links.state,
rqypj_mt_links.country,
rqypj_mt_links.postcode,
rqypj_mt_links.telephone,
rqypj_mt_links.fax,
rqypj_mt_links.email,
rqypj_mt_links.website,
rqypj_mt_links.price,
rqypj_mt_links.lat,
rqypj_mt_links.lng,
rqypj_mt_links.zoom,
rqypj_mt_cats.cat_name,
rqypj_mt_images.filename,
rqypj_mt_cfvalues.value,
rqypj_mt_customfields.caption
FROM rqypj_mt_links
LEFT JOIN rqypj_mt_cl
ON rqypj_mt_links.link_id = rqypj_mt_cl.link_id
LEFT JOIN rqypj_mt_cats
ON rqypj_mt_cl.cat_id = rqypj_mt_cats.cat_id
LEFT JOIN rqypj_mt_images
ON rqypj_mt_links.link_id = rqypj_mt_images.link_id
LEFT JOIN rqypj_mt_cfvalues
ON rqypj_mt_links.link_id = rqypj_mt_cfvalues.link_id
LEFT JOIN rqypj_mt_customfields
ON rqypj_mt_customfields.cf_id = rqypj_mt_customfields.cf_id LIMIT 100
Thanks in advance!
Jelte
your last condition doesn't look right:
on rqypj_mt_customfields.cf_id = rqypj_mt_customfields.cf_id
translates to 1=1
Shouldn't it be:
on rqypj_mt_customfields.cf_id = rqypj_mt_cfvalues.cf_id
Probably because you don't have an order by and are using limit.
Change it to
order by rqypj_mt_links.link_id, rqypj_mt_cl.cat_id
limit 100
and then your multiple pictures for the same link should be together.
Also please consider use of alias to make your code easier to read:
SELECT DISTINCT
links.link_name,
links.link_desc,
links.address,
links.city,
links.state,
links.country,
links.postcode,
links.telephone,
links.fax,
links.email,
links.website,
links.price,
links.lat,
links.lng,
links.zoom,
cats.cat_name,
images.filename,
cfvalues.value,
--custom.caption
FROM rqypj_mt_links links
LEFT JOIN rqypj_mt_cl cl ON links.link_id = cl.link_id
LEFT JOIN rqypj_mt_cats cats ON cl.cat_id = cats.cat_id
LEFT JOIN rqypj_mt_images images ON links.link_id = images.link_id
LEFT JOIN rqypj_mt_cfvalues cfvalues ON links.link_id = cfvalues.link_id
--LEFT JOIN rqypj_mt_customfields custom ON custom.cf_id = custom.cf_id
ORDER BY links.link_id, cats.cat_id
LIMIT 100
I am new to SQL so any help is greatly appreciated. I have a query that seems to be working that has old style joins, and I need to change it to new style joins. the current query is like:
SELECT
STAR.V_DISASTER_DIMENSIONS .DISASTER_NUMBER,
STAR.PA_PROJECT_DIMENSIONS .PW_NUMBER,
STAR.PA_PROJECT_SITE_DIMENSIONS.SITE_NUMBER,
STAR.PA_PROJECT_FACTS .PROJECT_AMOUNT,
STAR.PA_MITIGATION_DIMENSIONS .MITIGATION_ACTIVITY_STATUS
FROM
STAR.V_DISASTER_DIMENSIONS,
STAR.PA_PROJECT_DIMENSIONS,
STAR.PA_PROJECT_SITE_DIMENSIONS,
STAR.PA_MITIGATION_DIMENSIONS,
STAR.PA_PROJECT_FACTS,
STAR.PA_PROJECT_SITE_FACTS
WHERE
( STAR.PA_PROJECT_DIMENSIONS.PA_PROJECT_ID = STAR.PA_PROJECT_FACTS.PA_PROJECT_ID )
AND
( STAR.PA_PROJECT_FACTS.DISASTER_ID = STAR.V_DISASTER_DIMENSIONS.DISASTER_ID )
AND
( STAR.PA_MITIGATION_DIMENSIONS.PA_MITIGATION_ID = STAR.PA_PROJECT_FACTS.PA_PROJECT_ID )
AND
( STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_ID = STAR.PA_MITIGATION_DIMENSIONS.PA_MITIGATION_ID )
AND
( STAR.PA_PROJECT_SITE_FACTS.DISASTER_ID = STAR.V_DISASTER_DIMENSIONS.DISASTER_ID )
AND
( STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_ID = STAR.PA_PROJECT_DIMENSIONS.PA_PROJECT_ID )
AND
( STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_SITE_ID = STAR.PA_PROJECT_SITE_DIMENSIONS.PA_PROJECT_SITE_ID )
My attempt to convert is below. I don't know where to put the extra conditions because they are not 1 to 1 with tables.
FROM
STAR.V_DISASTER_DIMENSIONS
JOIN STAR.PA_PROJECT_SITE_FACTS ON STAR.PA_PROJECT_SITE_FACTS.DISASTER_ID = STAR.V_DISASTER_DIMENSIONS.DISASTER_ID
JOIN STAR.PA_PROJECT_DIMENSIONS ON STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_ID = STAR.PA_PROJECT_DIMENSIONS.PA_PROJECT_ID
JOIN STAR.PA_PROJECT_SITE_DIMENSIONS ON STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_SITE_ID = STAR.PA_PROJECT_SITE_DIMENSIONS.PA_PROJECT_SITE_ID
JOIN STAR.PA_MITIGATION_DIMENSIONS ON STAR.PA_PROJECT_SITE_FACTS.PA_PROJECT_ID = STAR.PA_MITIGATION_DIMENSIONS.PA_MITIGATION_ID
JOIN STAR.PA_PROJECT_FACTS ON (
STAR.PA_PROJECT_FACTS .DISASTER_ID = STAR.V_DISASTER_DIMENSIONS.DISASTER_ID AND
STAR.PA_MITIGATION_DIMENSIONS.PA_MITIGATION_ID = STAR.PA_PROJECT_FACTS .PA_PROJECT_ID AND
STAR.PA_PROJECT_DIMENSIONS .PA_PROJECT_ID = STAR.PA_PROJECT_FACTS .PA_PROJECT_ID
)
Change , to INNER JOINs with ON condition:
SELECT
STAR.V_DISASTER_DIMENSIONS.DISASTER_NUMBER,
STAR.PA_PROJECT_DIMENSIONS.PW_NUMBER,
STAR.PA_PROJECT_SITE_DIMENSIONS.SITE_NUMBER,
STAR.PA_PROJECT_FACTS.PROJECT_AMOUNT,
STAR.PA_MITIGATION_DIMENSIONS.MITIGATION_ACTIVITY_STATUS
FROM
STAR.PA_PROJECT_DIMENSIONS PD
INNER JOIN STAR.PA_PROJECT_FACTS PF ON PD.PA_PROJECT_ID=PF.PA_PROJECT_ID
INNER JOIN STAR.V_DISASTER_DIMENSIONS DD ON DD.DISASTER_ID=PF.DISASTER_ID
INNER JOIN STAR.PA_MITIGATION_DIMENSIONS MD ON MD.PA_MITIGATION_ID=PF.PA_PROJECT_ID
INNER JOIN STAR.PA_PROJECT_SITE_FACTS PSF ON PSF.PA_PROJECT_ID=MD.PA_MITIGATION_ID
AND PSF.DISASTER_ID=DD.DISASTER_ID
AND PSF.PA_PROJECT_ID=PD.PA_PROJECT_ID
INNER JOIN STAR.PA_PROJECT_SITE_DIMENSIONS PSD ON PSD.PA_PROJECT_SITE_ID=PSF.PA_PROJECT_SITE_ID
Select * from
a,b
where a.z = b.y
would be written as
Select * from
a
INNER JOIN
b
ON a.z = b.y
It is easy. Just start with the facts table and join related tables on foreign key = key.
First of all you should use table aliases to get the query more readable. Also use some lowercase letters, too.
Then just write the table names (or the aliases) on paper and draw a line for each condition from one table to the other. Then pick one table to start with, e.g. pa_project_site_dimensions which is only linked to one table.
SELECT
dd.disaster_number,
pd.pw_number,
psd.site_number,
psf.project_amount,
md.mitigation_activity_status
FROM star.pa_project_site_dimensions psd
JOIN star.pa_project_site_facts psf ON psf.pa_project_site_id = psd.pa_project_site_id
JOIN star.v_disaster_dimensions dd ON dd.disaster_id = psf.disaster_id
JOIN star.pa_mitigation_dimensions md ON md.pa_mitigation_id = psf.pa_project_id
JOIN star.pa_project_dimensions pd ON pd.pa_project_id = psf.pa_project_id
JOIN star.pa_project_facts pf ON pf.disaster_id = dd.disaster_id
AND pf.pa_project_id = md.pa_mitigation_id
AND pf.pa_project_id = pd.pa_project_id
;
However, this is a strange query. First of all there is no limiting condition, you simply join all records, rather than retrieving data for, say, one particular project.
Moreover, you deal with several dimensions. Obviously a project has facts (pa_project_facts) and dimensions (pa_project_dimensions). With 5 facts and 3 dimensions you'd get 15 rows with all their combinations. Then there are also project sites it seems (maybe a table pa_project_sites we don't see in the query). Either that project site has facts on its own (pa_project_site_facts) that you also combine with all rows, or a project site is linked to a project fact via pa_project_site_facts, but then pa_project_facts wouldn't have to be joined by pa_project_id only, but also by some fact ID.
Also this looks strange: md.pa_mitigation_id = psf.pa_project_id. Is a mitigation the same as a project?
So after all have a look at all columns that need to be joined on. Think about how the tables are related and if you are not building combinations that make no sense.
I have 2 tables
tableAssembly, with columns SerialNumber, Brick1SerialNumber, Brick2SerialNumber,DateInserted
tableBricks, with columns SerialNumber, Lot, Weight, DateMeasured
In VB.NET(WinForms) I have been able to get the reportviewer control to work and print out information from both tables and also to enable/disable columns, and apply filters such as LIKE
In case it isn't clear tableAssembly.Brick1SerialNumber = tableBricks.SerialNumber
What I now want to do is when a user prints out a report from tableAssembly, I want them to be able to filter based on Brick1SerialNumber.Lot or Brick2SerialNumber.Lot or Brick1SerialNumber.DateMeasured or Brick2SerialNumber.DateMeasured
I understand I need to INNER JOIN tableAssembly.Brick1SerialNumber = tableBricks.SerialNumber AND tableAssembly.Brick2SerialNumber = tableBricks.SerialNumber
Do I also need to INNER JOIN the other columns from tableBricks to columns in tableAssembly? or does the INNER JOIN of Brick1SerialNumber = SerialNumber and Brick2SerialNumber = Serial make it so I can filter based on .Lot?
There are two ways to approach this. 1) join to tableBricks twice (probably not necessary for this condition) or attach it once, using both foreign keys (works for this condition, but not for all conditions).
Try this query:
SELECT tableAssembly.*
FROM tableAssembly INNER JOIN tableBricks
ON tableAssembly.Brick1SerialNumber = tableBricks.SerialNumber
OR tableAssembly.Brick2SerialNumber = tableBricks.SerialNumber
WHERE tableBricks.Lot = 99 --actually means Brick1.Lot or Brick2.Lot
AND tableBricks.DateMeasured = '1/1/2000'
If you need a specific Lot or DateMeasured for Brick1 and Brick2 (not the same values), then try this query:
SELECT tableAssembly.*
FROM tableAssembly INNER JOIN tableBricks AS tableBricks1
ON tableAssembly.Brick1SerialNumber = tableBricks1.SerialNumber
INNER JOIN tableBricks AS tableBricks2
ON tableAssembly.Brick2SerialNumber = tableBricks2.SerialNumber
WHERE tableBricks1.Lot = 98
AND tableBricks2.Lot = 99
AND tableBricks1.DateMeasured = '1/1/2000'
AND tableBricks2.DateMeasured = '1/2/2000'
I'm trying to retrieve a list of components via my computer_system, BUT if a computer system's graphics card is set to null (I.e. It has an onboard), the row isn't returned by my select statement.
I've been trying to use COALESCE without results. I've also tried with and OR in my WHERE clause, which then just returns my computer system with all different kinds of graphic cards.
Relevant code:
SELECT
computer_system.cs_id,
computer_system.cs_name,
motherboard.name,
motherboard.price,
cpu.name,
cpu.price,
gfx.name,
gfx.price
FROM
public.computer_case ,
public.computer_system,
public.cpu,
public.gfx,
public.motherboard,
public.ram
WHERE
computer_system.cs_ram = ram.ram_id AND
computer_system.cs_cpu = cpu.cpu_id AND
computer_system.cs_mb = motherboard.mb_id AND
computer_system.cs_case = computer_case.case_id AND
computer_system.cs_gfx = gfx.gfx_id; <-- ( OR computer_system.cs_gfx IS NULL)
Returns:
1;"Computer1";"Fractal Design"; 721.00; "MSI Z87"; 982.00; "Core i7 I7-4770K "; 2147.00; "Crucial Gamer"; 1253.00; "ASUS GTX780";3328.00
Should I use Joins? Is there no easy way to say return the requested row, even if there's a bloody NULL value. Been struggling with this for at least 2 hours.
Tables will be posted if needed.
EDIT: It should return a second row:
2;"Computer2";"Fractal Design"; 721.00; "MSI Z87"; 982.00; "Core i7 I7-4770K "; 2147.00; "Crucial Gamer"; 1253.00; "null/nothing";null/nothing
You want a LEFT OUTER JOIN.
First, clean up your code so you use ANSI joins so it's readable:
SELECT
computer_system.cs_id,
computer_system.cs_name,
motherboard.name,
motherboard.price,
cpu.name,
cpu.price,
gfx.name,
gfx.price
FROM
public.computer_system
INNER JOIN public.computer_case ON computer_system.cs_case = computer_case.case_id
INNER JOIN public.cpu ON computer_system.cs_cpu = cpu.cpu_id
INNER JOIN public.gfx ON computer_system.cs_gfx = gfx.gfx_id
INNER JOIN public.motherboard ON computer_system.cs_mb = motherboard.mb_id
INNER JOIN public.ram ON computer_system.cs_ram = ram.ram_id;
Then change the INNER JOIN on public.gfx to a LEFT OUTER JOIN:
LEFT OUTER JOIN public.gfx ON computer_system.cs_gfx = gfx.gfx_id
See PostgreSQL tutorial - joins.
I very strongly recommend reading an introductory tutorial to SQL - at least the PostgreSQL tutorial, preferably some more material as well.
It looks like it's just a bracket placement issue. Pull the null check and the graphics card id comparison into a clause by itself.
...
computer_system.cs_case = computer_case.case_id AND
(computer_system.cs_gfx IS NULL OR computer_system.cs_gfx = gfx.gfx_id)
Additionally, you ask if you should use joins. You are in fact using joins, by virtue of having multiple tables in your FROM clause and specifying the join criteria in the WHERE clause. Changing this to use the JOIN ON syntax might be a little easier to read:
FROM sometable A
JOIN someothertable B
ON A.somefield = B.somefield
JOIN somethirdtable C
ON A.somefield = C.somefield
etc
Edit:
You also likely want to make the join where you expect the null value to be a left outer join:
SELECT * FROM
first_table a
LEFT OUTER JOIN second_table b
ON a.someValue = b.someValue
If there is no match in the join, the row from the left side will still be returned.