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
Related
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
I'm trying to write a simple interface for a list of companies using MySQL and PHP. So, I want to fetch some information from my database.
Here are my tables:
companies_data - only for system information.
corporate_data - here I want to keep information about big companies.
individual_data - and here I want to keep information about little companies.
So, here is the tables
And here is the query that I've written:
SELECT
a.id,
a.user_id,
a.added,
a.`status`,
a.company_id,
a.company_type,
a.deposit,
a.individual_operations_cache,
a.corporate_operations_cache,
a.physical_operations_cache,
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
FROM
companies_data a
LEFT OUTER JOIN corporate_data b
ON (a.company_id = b.id) AND a.company_type = 0
LEFT OUTER JOIN individual_data c
ON (a.company_id = c.id) AND a.company_type = 1
WHERE
a.user_id = 3
This is just the code for a test, I'll expand it soon.
As you see, I've got result with extra fields like %field_name%1, %another_field_name%1 and so on. Of course it is not the mysql error - what I've asked that I've got - but I want to remove this fields? It's possible or I must convert this output on the application side?
thos %field_name%1, %another_field_name%1 , are visible since you are selecting them in your query:
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
When you use fields with the same name in distinct tables, then the result column name come with this identifier field1, field2, fieldn... in order to distinguish from which table does the field come from.
If you want to avoid this names, you can use aliases as follows:
[...]
b.full_name as corporate_full_name,
[...]
Probably, if every common fields are coincident, you won´t need to show them all, so just remove them from the select.
Hope being usefull for you.
Br.
I have a SQL SELECT statement in which I'm using 3 tables.
I'm using INNER JOINs to join the tables, however I've come across a bit of an issue because two of the columns that I'd like the join conditional to be based on are different data types;
One is an integer - the id of the products table and can be seen below as p.id.
The other is a comma delimited string of these id's in the order table. customers can order more than one product at a time, so the product id's are stored as a comma delimited list.
here's how far I've gotten with the SQL:
"SELECT o.transaction_id, o.payment_status, o.payment_amount, o.product_id, o.currency, o.payment_method, o.payment_time, u.first_name, u.last_name, u.email, p.title, p.description, p.price
FROM orders AS o
INNER JOIN products AS p ON ( NEED HELP HERE--> p.id IN o.product_id comma delimited list)
INNER JOIN users AS u ON ( o.user_id = u.id )
WHERE user_id = '39'
ORDER BY payment_time DESC
LIMIT 1";
Perhaps I could use REGEX? currently the comma delimited list reads as '2,1,3' - however the number of characters isn't limited - so I need a conditional to check if my product id (p.id) is in this list of o.product_id?
What you have is a perfect example for one-to-many relationship where you have one order and several items attached to it. You should have a link table like
order_product - which makes the connection between a orderid and productid where you can also put specific data for the relationship between the two (like when the item was added, quantity, etc)
Then you make the join using this table and you have same field types everywhere.
simple example:
select
/* list of products */
from
order o,
order_product op,
product p
where
o.id = 20
and o.id = op.orderid
and op.productid = p.id
This in one of those very common nightmares when working with legacy database.
The rule is simple: never ever store multiple values in one table columns. This is known as first normal form.
But how to deal with that in existing DB?
The good thing™
If you have the opportunity to refactor your DB, extract the "comma separated values" to their own table. See http://sqlfiddle.com/#!2/0f547/1 for a basic example how to do that.
Then to query the tables you will have to use a JOIN as explained in elanoism's answer.
The bad thing™
I you can't or don't want do that, you probably have to rely on the FIND_IN_SET function.
SELECT * FROM bad WHERE FIND_IN_SET(target_value, comma_separated_values) > 0;
See http://sqlfiddle.com/#!2/29eba/2
BTW, why is this bad thing™? Because as you see, it is not easy to write query against multi-valued columns -- but, probably more important, you are not able to use index on that columns, nor, as a consequence, to easily perform join operations or enforce referential integrity.
The so-so thing™
As a final note, if the set of possible value is small (less that 65), an alternative approach would be to change the column type to a SET().
I've been scratching my head at this problem all day and I simple just can't work it out. This is the first time I've attempted to try and use SQL Joining, while we do kinda get taught the basics I'm more into pushing a little more into the advanced stuff.
Basically I'm making my own forum, and I have two tables. f_topics (The threads) and f_groups (The forums, or categories). There is a relationship between topicBase in f_topics and groupID in f_groups, this shows which group each topic belongs to. Each topic has a unique ID called topicID and same for the groups, called groupID.
Basically, I'm trying to get all these columns into a single SELECT statement - The title of the topic, the date the topic was posted, the ID of the group the topic belongs in, and the name of that group. This is what I was trying to use, but the group always comes back as 1, even if the topic is in groupID 2:
$query=mysqli_query($link, "
SELECT `topicName`, `topicDate`, `groupName`, `groupID`
FROM `f_topics`
NATURAL JOIN `f_groups`
WHERE `f_topics`.`topicID`='$tid';
") or die("Failed to get topic detail E: ".mysqli_error());
var_dump(mysqli_fetch_assoc($query));
Sorry if this doesn't make much sense, and if my entire logic is completely wrong, if so could you suggest an alternate method?
Thanks for reading!
To join tables, you need to map the foreign keys. Assuming your groups table has an groupID field, this is how you'd join them:
SELECT `topicName`, `topicDate`, `groupName`, `groupID`
FROM `f_topics`
LEFT JOIN `f_groups`
ON `f_topics`.`groupID` = `f_groups`.`groupID`
WHERE`f_topics`.`topicID`='$tid';
So from what I gather there is a column in f_topics named "topicBase" which references the groupID column from the f_groups table.
Based on that assumption, you can perform either an INNER JOIN or a LEFT JOIN. INNER requires there be an entry in both tables while LEFT requires there only be data in f_topics.
SELECT
f_topics.topicName,
f_topics.topicDate
f_groups.groupName
f_groups.groupID
FROM
f_topics
INNER JOIN
f_groups
ON
f_topics.topicBase = f_groups.groupID
WHERE
f_topics.topicID = '$tid'
I recommend you avoid NATURAL JOIN.
Primarily because a working query can be broken by the addition of a new column in a referenced table, which matches a column name in the other referenced table.
Secondly, for any reader (reviewer) of the SQL, which columns are being matched to which columns is not clear, without a careful review of both tables. (And, if someone has added a column that has broken the query, it makes it even more difficult to figure out what the JOIN criteria used to be, before the column was added.
Instead, I recommend you specify the column names in a predicate in the ON clause.
It's also good practice to qualify all column references by table name, or preferably, a shorter table alias.
For simpler statements, I agree that this may look like unnecessary overhead. But once statements become more complicated, this pattern VASTLY improves the readability of the statement.
Absent the definitions of the two tables, I'm going to have to make assumptions, and I "guess" that there is a groupID column in both of those tables, and that is the only column that is named the same. But you specify that its the topicBase column in f_topics that matches groupID in f_groups. (And the NATURAL JOIN won't get you that.)
I think the resultset you want will be returned by this query:
SELECT t.`topicName`
, t.`topicDate`
, g.`groupName`
, g.`groupID`
FROM `f_topics` t
JOIN `f_groups` g
ON g.`groupID` = t.`topicBase`
WHERE t.`topicID`='$tid';
If its possible for the topicBase column to be NULL or to contain a value that does not match a f_groups.GroupID value, and you want that topic returned, with the columns from f_group returned as NULL (when there is no match), you can get that with an outer join.
To get that behavior, in the query above, add the LEFT keyword immediately before the JOIN keyword.
I'm currently struggling to figure out a solution to a situation I'm in. I'll give you some background on the database structure:
Table 'Entities' has columns Ent_ID (AI, Int), Ent_Type (Enum, 'Locations, Characters, Houses, Armies') and Obj_ID (Int).
The Obj_ID matches the ID from the relevant table. For example the 'Locations' table has columns Obj_ID (AI, Int), Loc_Name (Text) and Loc_Desc (Text).
Characters can be in different locations depending on the date, so I have a 'TimeSensitives' table to store these kind of things. This table has columns TS_ID (AI, Int), Ent_ID (Int), TS_Start (Float), TS_End (Float) and TS_Content (Text). The Ent_ID is which character is being some information (such as location). The Ent_ID of the location goes into TS_Content.
So, as you might imagine, I have many locations each with different characters there at different dates. My problem is that I want to be to search for a location that either has a name LIKE the search filter or has a character there that has a name LIKE the search filter.
Up until now I have looped through each Entity with type 'Locations' and then for each one I have looped through all characters currently there. This is my code:
SELECT *
FROM Entities
INNER JOIN Locations
ON Entities.Obj_ID=Locations.Obj_ID
WHERE Entities.Ent_Type='Locations'
AND Loc_Name LIKE ?
Then for each one:
SELECT *
FROM TimeSensatives
INNER JOIN Entities
ON TimeSensatives.Ent_ID=Entities.Ent_ID
INNER JOIN Characters
ON Entities.Obj_ID=Characters.Obj_ID
WHERE TS_Type='Location'
AND TS_Content=?
AND TS_Start<=?
AND TS_End>=?
So as you might have seen, this currently only takes the search filter into account when selecting places.
I would now like to implement the search filter onto the characters too but it needs to show places that don't necessarily have matching name but do have a character there with a matching name. I hope that makes sense. My current code wont select any locations without a matching name so there's no chance to search for matching character names.
Is there any way to combine this into one sql statement? Or can somebody think of a way to achieve what I'm trying to do?
Any help would be greatly appreciated and if you need any more info then please ask :)
Kallum
SELECT
Entities.*,
Locations.Loc_Name as Loc_Name
FROM
Entities
INNER JOIN Locations ON Entities.Obj_ID=Locations.Obj_ID
WHERE
Entities.Ent_Type='Locations'
AND Loc_Name LIKE ?
UNION
SELECT
LocEnt.*,
Locations.Loc_Name as Loc_Name
FROM
TimeSensatives
INNER JOIN Entities AS CharEnt ON TimeSensatives.Ent_ID=CharEnt.Ent_ID
INNER JOIN Characters ON Entities.Obj_ID=Characters.Obj_ID
INNER JOIN Entities AS LocEnt ON TS_Content.Ent_ID=LocEnt.Ent_ID
INNER JOIN Locations ON LocEnt.Obj_ID=Locations.Obj_ID
WHERE
TS_Type='Location'
AND TS_Start<=?
AND TS_End>=?
AND Char_Name LIKE ?
will give you all needed locations plus their names