MySQL filtering with several inner joins - php

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

Related

Searching multiple tables in MySQL database

I have several different tables in my database(mySQL).
Here are the relevant coumns for the tables
table_tournaments
tournamentId
tournamnetName
tournamentStatus
table_tournament_results
tournamentId
playerId
playerName
playerRank
tournamnetParticipants
table_players
playerId
playerName
The tournaments table contains the information about the tournament, the tournament results table shows the results from that table
I want to search the tournaments table by name and then with the returned results get the information from the tournament results table.
SELECT * FROM `tournaments` `WHERE` `tournamentName` LIKE "%Query%"
I'm not sure how to go about this, maybe I need to do something via PHP, any and all help is appreciated.
You can get the results you want with a join operation.
This is an example of an outer join, returning all rows from t that have the string 'foo' appearing as part of tournament_name, along with any matching rows from r.
A relationship between rows in the two tables is established by storing a common value in the tournamentId column of the two tables. The predicate in the ON clause specifies the condition that determines if a row "matches".
SELECT t.tournamentId
, t.tournamentName
, t.tournamentStatus
, r.playerId
, r.playerName
, r.playerRank
FROM table_tournaments t
LEFT
JOIN table_tournament_results r
ON r.tournamentId = t.tournamentId
WHERE t.tournament_name LIKE '%foo%'
ORDER
BY t.tournamentId
, r.playerId
The t and r that appear after the table names are table aliases, we can qualify references to the columns in each table by prefacing the column name with the table alias and a dot. This makes the column reference unambiguous. (In the case of tournamentId, MySQL doesn't know if you are referring to the column in t or r, so we qualify it to make it explicit. We follow this same pattern for all column references. Then, someone reading the statement doesn't need to wonder which table contains the column playerId.
Your Query may be like this
SELECT a.*, b.tournamnetName FROM table_tournament_results a
left join table_tournaments on a.tournamentId=b.tournamentId
WHERE b.tournamnetName LIKE "%Query%"

Joining two tables together with two foreign keys

I have a Description table which contains certain descriptions along with a unique ID. I have another table that contains two foreign keys to this table. So far i have the following query:
SELECT
Description.description AS Description,
Object.objID AS ID,
Description.description AS Location
FROM
Object
INNER JOIN
Description
ON
Object.objDescID=Description.descID
AND
Object.objLocID=Description.descID;
However this is not working, please can someone point me in the right direction?
If I understand right you want to join to the Description table twice for the same object. Give this a shot and see if it gets you what you're after:
SELECT
Object.objID AS ID,
od.description AS Description,
ld.description AS Location
FROM Object
INNER JOIN Description AS od
ON Object.objDescID=od.descID
INNER JOIN Description AS ld
ON Object.objLocID=ld.descID;
Edit: A word of advice, if you allow for null foreign keys you should use a LEFT JOIN instead of an INNER JOIN, that way if one of them is null it doesn't keep the entire record from showing.
Try Running This (might need minor adjustments):
SELECT
Description.description AS Description,
Object.objID AS ID,
Description.description AS Location
FROM
Object
INNER JOIN
Description AS Object.objDescID=Description.descID
INNER JOIN
Description AS Object.objLocID=Description.descID;
Looks like you need two references to the Description table. Each reference will be joined using one of the foreign key columns.
For example:
SELECT o.objID AS `ID`
, d.description AS `Description`
, l.description AS `Location`
FROM Object o
JOIN Description d
ON d.descID = o.objDescID
JOIN Description l
ON l.descID = o.objLocID
We assign the short alias d to the source we get the Description value from.
We assign the short alias l to the source we get the Location value from.
We reference columns from each table using the short alias, rather than the table name.
Essentially, think of the references to the Description table like it's two different tables, even though it's really the same table.
Note that we have to assign an alias to at least one of the references to Description, so that we can distinguish between them. (Otherwise, MySQL won't know which one we're talking about if we just said Description.description.)
Note that if the foreign key column objDescID or objLocID has a NULL value, or a matching value doesn't exist in the referenced table, the query won't return the row from Object.
To ensure you get a row from Object even when the matching values aren't found, you can use an OUTER join operation by including the LEFT keyword.
For example:
SELECT o.objID AS `ID`
, d.description AS `Description`
, l.description AS `Location`
FROM Object o
LEFT
JOIN Description d
ON d.descID = o.objDescID
LEFT
JOIN Description l
ON l.descID = o.objLocID
Note that only one alias is actually required, but I tend to assign short aliases to all row sources in a query. This makes the statement more decipherable, and really helps if I later need to add another reference to a table that is already used, or if I need to replace one of the table names with a different table name or an inline view (or subquery), I can leave the alias the same, and change just the rowsource. The other aliases don't make any difference in the actual execution of the statement, they are just there because I follow the same pattern for simple queries that I follow for more complex queries.

MySQL joining tables with join

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.

Sql table with 3 columns of a second table ids

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

Joining multiple (4) tables in MYSQL

I have four tables I want to join and get data from. The tables look something like...
Employees (EmployeeID, GroupID[fk], EmployeeName, PhoneNum)
Positions (PositionID, PositionName)
EmployeePositions (EployeePositionID, EmployeeID[fk], PositionID[fk])
EmployeeGroup (GroupID, GroupName)
[fk] = foreign key
I want to create a query that will return all the information about an employee(given by EmployeeID). I want a query that will return the given employees Name, position(s), and group in one row.
I think it needs to involve joins, but I am not sure how to format the queries. MYSQL's manual is technical beyond my comprehension. I would be very grateful for any help.
It seems you have trouble with SQL, in general, rather than with mySQL in particular. The documentation of mySQL provides details about the various SQL expressions, but generally assume some familiarity with SQL. To get a quick start on SQL you may consider this W3schools.com primer.
The query you need is the following.
SELECT EmployeeName, PositionName, GroupName
FROM Employees E
LEFT JOIN EmployeePositions EP ON EP.EmployeeID = E.EmployeeID
LEFT JOIN Positions P ON P.PositionID = EP.PositionId
LEFT JOIN EmployeeGroup EG ON EG.GroupId = E.GroupId
WHERE E.EmployeeId = some_value
A few things to note:
The 'LEFT' in 'LEFT JOIN' will result in producing NULL in lieu of PositionName or GroupName when the corresponding tables do not have a value for the given FK. (Should only happen if the data is broken, say if for example some employees have GroupId 123 but somehow this groupid was deleted from the EmployeeGroup table.
The query returns one line per employee (1). You could use an alternative search criteria, for example WHERE EmployeeName = 'SMITH', and get a listing of all employees with that name. Indeed without a WHERE clause, you'd get a list of all employees found in Employees table.
(1) that is assuming that each employee can only have one position. If somehow some employees have more than one position (i.e. multiple rows in EmployeePositions for a given EmployeeID), you'd get several rows per employee, the Name and Group being repeated and a distinct PostionName.
Edit:
If a given employee can have multiple positions, you can use the query suggested by Tor Valamo, which uses a GROUP BY construct, with GROUP_CONCAT() to pivot all the possible positions in one single field value in the returned row.
SELECT e.EmployeeID, e.EmployeeName, e.PhoneNum,
g.GroupName, GROUP_CONCAT(p.PositionName) AS Positions
FROM Employees e
LEFT JOIN EmployeeGroup g ON g.GroupID = e.GroupID
LEFT JOIN EmployeePositions ep ON ep.EmployeeID = e.EmployeeID
LEFT JOIN Positions p ON p.PositionID = ep.PositionID
WHERE e.EmployeeID = 1
GROUP BY e.EmployeeID
Returns positions in a comma separated string on one row.

Categories