I have two tables, sections and articles. Originally it was a one-to-many relationship, therefore articles has a sectionID column. Then one day it was decided to allow each article to belong to two sections. Rather than making another 'articles-in-section' table, I opted to not deal with refactoring the data, and I just added an additionalSectionID column onto the articles table. Now I have a problem.
I want to show all the articles associated with a given section, whether as its primary or secondary section. Essentially, I'm looking for some sort of double join between two tables.
These two questions have answers to the same issue - 1,2, but with a different db server. Is there are a way to do this in PHP/MySQL?
Tables' structure is basically like this:
-- SECTIONS:
id title description moderatorID url
-- ARTICLES:
id title shortDesc fullText photo sectionID additionalSectionID
See below.
SELECT s.*, a.*
FROM sections s
LEFT JOIN articles a
ON s.id = a.sectionID
OR s.id = a.additionalSectionID
WHERE s.title = 'My Section';
You might try two inner joins on separate table aliases, along the lines of:
SELECT
*,
s1.section_name AS sectionA,
s2.section_name AS sectionB
FROM articles
INNER JOIN sections s1 ON articles.sectionID = s1.sectionID
INNER JOIN sections s2 ON articles.additionalSectionID = s2.sectionID
Related
I am making a website which stores data in mysql.
Now the situation is that I want a structure in which there should be a table with catogories (cat_id, cat_name) and a recipe table (r_id,r_name and so on). But the condition is that is that a single recipe can have more than one category.And I have to show that all recipes that comes under one categories through php.
All I need is a proper structure.
I am new to DBMS. A help will be appreciated.
You may add a junction table to your database schema, something like this:
category_recipe (cat_id, r_id)
The purpose of this table is to allow a given category to map to more then one recipe. If you also want to enforce that each recipe may only appear once, then you may add a unique index on the r_id column in category_recipe.
To find all the recipes that fall under a certain category, you may use the following query:
SELECT
r.r_id, r.r_name
FROM categories c
INNER JOIN category_recipe cr
ON c.cat_id = cr.cat_id
INNER JOIN recipe r
ON cr.r_id = r.r_id
WHERE
c.cat_name = 'some category';
i'm new to sql, and am bulding a mysql and php application.
i might be planning the tables themselves wrong, and if so, would thank any ideas.
i have blocks contaning some columns, including title, text, slogan.
both title and text are more complex then seem, because i need font, size, and also approval on each of the texts. so i created a texts table with these columns.
these are the tables:
blocks
id(increment),text(texts.id),title(texts.id),slogan(texts.id),
type(enum),time(datetime),difficulty(int)
texts
id(increment),Text(text),font(varchar),size(int),approval(approval.id)
aprrovals
id(increment), User(varchar), time(datetime)
so here are the questions:
is this a good way to hold this information? should i just have textxs hold the block id they belong to?
how do i show a table contaning all columns of blocks, but showing the actual text from texts.Text instead of the ids?
how do i select the approval.User of a specific text of a specifi block?
Your first question is one where the only person who can truly answer it is you, however from what you describe this seems to be sensible. Naming a column "Text" is not the greatest idea since it's a reserved word in mysql. You'll have to always remember to escape the column name. I'd suggest something like "body" instead. (I believe time and type are as well.)
Your next two questions require an understanding of JOINs. JOINs allow you to join two tables together based on criteria you define. I'm going to answer the questions in reverse order as your second one will be a more complex join.
So 3) Texts and the user who approved them:
SELECT
t.`id`, t.`Text`, a.`User`
FROM
texts t
INNER JOIN
approvals a
ON
t.approval=a.id;
This will return the Texts ID, text and the user name for any rows that have been approved. If you want to also see ones without approvals then change INNER JOIN to LEFT JOIN. This will always return rows from the table on the Left Hand Side of the ON statement.
Now the problem with returning all the blocks is you will need to JOIN the texts table several times. That makes it a bit more complex but it's easy to do:
SELECT
t1.`Text` as 'Title',
t2.`Text` as 'Slogan',
t3.`Text` as 'Body',
b.`type` as 'Type',
b.`time` as 'Time'
b.`difficulty` as 'Difficulty'
FROM
blocks b
INNER JOIN
texts t1
ON
b.`title` = t1.`id`
INNER JOIN
texts t2
ON
b.`slogan` = t2.`id`
INNER JOIN
texts t3
ON
b.`text` = t3.`id`;
If I understand your question correctly, I don't think it's necessary to have the Approvals table. If a Text can have one and only one user approve it, and it can be approved on one and only one date, it's unnecessary to separate Approvals.
should i just have textxs hold the block id they belong to?
I think that depends on your modeling needs. Is it always one text, one title and one slogan, and do all three have to be filled out? If so, it won't matter if you do it like you've done now. However, if you'll often see a block with just a text and no title and slogan, it might make more sense to make Texts reference Blocks.
how do i show a table contaning all columns of blocks, but showing the actual text from texts.Text instead of the ids?
Something like this should get you started:
SELECT b.id, text.text, title.text, slogan.text, b.type, b.time, b.difficulty
FROM blocks AS b
JOIN texts AS text ON b.text = texts.id
JOIN texts AS title ON b.title = texts.id
JOIN texts AS slogan ON b.slogan = texts.id
how do i select the approval.User of a specific text of a specifi block?
SELECT a.user
FROM approvals AS a
JOIN texts AS t ON a.id = t.approval
JOIN blocks AS b ON b.text = t.id
WHERE b.id = idToLookFor
Imagine a table for articles. In addition to the main query:
SELECT * From articles WHERE article_id='$id'
We also need several other queries to get
SELECT * FROM users WHERE user_id='$author_id' // Taken from main query
SELECT tags.tag
FROM tags
INNER JOIN tag_map
ON tags.tag_id=tag_map.tag_id
WHERE article_id='$id'
and several more queries for categories, similar articles, etc
Question 1: Is it the best way to perform these queries separately with PHP and handle the given results, or there is way to combine them?
Question 2: In the absence of many-to-many relationships (e.g. one tag, category, author for every article identified by tag_id, category_id, author_id); What the best (fastest) was to retrieve data from the tables.
If all the relationships are one-many then you could quite easily retrieve all this data in one query such as
SELECT
[fields required]
FROM
articles a
INNER JOIN
users u ON a.author_id=u.user_id
INNER JOIN
tag_map tm ON tm.article_id=a.article_id
INNER JOIN
tags t t.tag_id=tm.tag_id
WHERE
a.article_id='$id'
This would usually be faster than the three queries separately along as your tables are indexed correctly as MySQL is built to do this! It would save on two round trips to the database and the associated overhead.
You can merge in the user in the first query:
SELECT a.*, u.*
FROM articles a
JOIN users u ON u.user_id = a.author_id
WHERE a.article_id='$id';
You could do the same with the tags, but that would introduce some redundancy in the answer, because there are obviously multiple tags per article. May or may not be beneficial.
In the absence of many-to-many relationships, this would do the job in one fell swoop and would be superior in any case:
SELECT *
FROM users u
JOIN articles a ON a.author_id = u.user_id
JOIN tag t USING (tag_id) -- I assume a column articles.tag_id in this case
WHERE a.article_id = '$id';
You may want to be more selective on which columns to return. If tags ar not guaranteed to exist, make the second JOIN a LEFT JOIN.
You could add an appropriately denormalized view over your normalized tables where each record contains all the data you need. Or you could encapsulate the SQL calls in stored procedures and call these procs from your code, which should aid performance. Prove both out and get the hard figures; always better to make decisions based on evidence rather that ideas. :)
i have many to many database and i'm there is a query that i just got stuck with and cant do it.
i have 4 tables Artists, Tracks, Albums, Clips
now i'm on the artist page so i need to get them by the artist page, i already got all of them, but not the way i want them.
because some tracks, albums, clips belong to other artists as well (duet) and i need to display their name.
but the problem is that i'm selecting using the artist id so my GROUPC_CONCAT function wont work here is the query that gets the artist albums.
SELECT al.album_name, GROUP_CONCAT(a.artist_name SEPARATOR ', ') AS 'artist_name'
FROM
Albums al
LEFT JOIN
ArtistAlbums art ON art.album_id = al.album_id
LEFT JOIN
Artists a on a.artist_id = art.artist_id
WHERE
a.artist_id = 10
GROUP BY
al.album_id
one of the albums have two artists attached to it, but it does not get the other artist name.
when i select by the album_id i get the two artists.
please note that i'm new to mysql and i did not find any answers on this particular problem almost no resources on many-to-many querying.
how can i tackle this problem.?
any resources or books on many-to-many that show how to deal with the database on the application layer will be much appreciated,
thanks in advance.
Think of table aliases as really being row aliases. That is, for purposes of expressions in the WHERE clause and the select-list, the alias refers to a single row at a time.
Since you've created a condition such that a.artist_id = 10, then only rows for that artist match the condition. What you really want is to match all artists on an album given that one artist is artist_id = 10.
For that, you need another join, so that the row where artist_id = 10 is matched to all the rows for that respective album.
SELECT al.album_name, GROUP_CONCAT(a2.artist_name SEPARATOR ', ') AS `artist_name`
FROM
Albums al
INNER JOIN
ArtistAlbums art ON art.album_id = al.album_id
INNER JOIN
Artists a on a.artist_id = art.artist_id
INNER JOIN
ArtistAlbums art2 ON art2.album_id = al.album_id
INNER JOIN
Artists a2 on a2.artist_id = art2.artist_id
WHERE
a.artist_id = 10
GROUP BY
al.album_id
P.S.: I've also replaced your use of LEFT JOIN with INNER JOIN. There's no reason you needed LEFT JOIN, since you're explicitly looking for albums that have a matching artist, not all albums whether or not it has artist 10. Review the meaning of different types of join.
Re your followup question:
I don't know of a book specifically about many-to-many queries. I'd recommend books like:
SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming, which is my own book and it does cover many-to-many tables.
SQL and Relational Theory to understand joins better.
Joe Celko's SQL Programming Style, which imho is Joe Celko's best book.
I have three tables.They are
tb_albums---->id,title, description
tb_photos---->id,album_id, photo
tb_tags---->id,album_id, tag
Here i want to get the albums details and it photos & it tags through tb_albums.id.
How to use join query here?
Normally, you can use as many tables in JOIN, as you'd like. Just add another JOIN statement. You can refer to k102's answer for the correct syntax (which doesn't produce correct result though).
But in this particular case you don't want to use simple JOIN on all tables, unless you have only one photo per album and only one tag per album. If you have more than one photo per and more than one tag per album, JOIN both tables on album_id in single query will produce Cartesian product of both, in other words all possible combinations of tags and photos from each albums. For N photos and M tags that's N * M results, instead of N + M.
Also, there is no point of joining with tb_albums, as you do not need to repeat information about each album for each photo and each tag.
Proper approach would be to have 3 separate simple SELECTs from each table and combining their result on application level.
If for some awkward reason you'd need to do that with one query, you can do something like:
SELECT * FROM tb_albums as A JOIN
(SELECT 'photo', id, photo as value FROM tb_photos
UNION ALL
SELECT 'tag', id, tag as value FROM tb_tags) as B ON B.album_id = A.id
Note, this is way less optimal than separate SELECTs, you should only do this if you have no other choice.
select * from tb_albums a
join tb_photos p on a.id = p.album_id
join tb_tags t on a.id = t.album_id