PHP/Mysql: read data field value from lookup tables (split array) - php

I have 1 Mysql database with 2 tables:
DOCUMENTS
...
- staffID
.....
STAFF
- ID
- Name
The DOCUMENTS table assigns each document to a single or multiple users from the STAFF table therefore the staffID in the DOCUMENTS table consists of a comma separated array of staff ID's for example (2, 14).
I managed to split the array into individual values:
2
14
but rather than having the ID numbers I would like to have the actual names from the STAFF table - how can I achieve this. Any help would be greatly appreciated - please see my current code below.
$result = mysql_query("SELECT
organizations.orgName,
documents.docName,
documents.docEntry,
documents.staffID,
staff.Name,
staff.ID
FROM
documents
INNER JOIN organizations ON (documents.IDorg = organizations.IDorg)
INNER JOIN staff ON (documents.staffID = staff.ID)
")
or die(mysql_error());
while($row = mysql_fetch_array($result)){
$splitA = $row['staffID'];
$resultName = explode(',', $splitA );
$i=0;
for($i=0;$i<count($resultName);$i++)
{
echo "<a href='staffview.php?ID=".$row['docName'].
"'>". $resultName[$i]."</a><br>";
}
echo '<hr>';
}

It looks like your existing code might work where documents.staffID = staff.ID - that is where there is just a single staffID associated with the document?
You'd be better off adding a table to model the relationships between documents and staff separately from either, and removing or deprecating the staffID field in the documents table. You'd need something like
CREATE TABLE document_staff (
document_id <type>,
staff_id <type>
)
You can include compound indexes with ( document_id, staff_id ) and ( staff_id, document_id ) if you have lots of data and/or you want to traverse the relationship efficiently in both directions.
(You don't mention data types for your identity fields, but documents.staffID appears to be some sort of varchar based on what you say - perhaps you could use an integer type for these instead?)
But you can probably achieve what you want using the existing schema and the MySQL FIND_IN_SET function:
SELECT
organizations.orgName,
documents.docName,
documents.docEntry,
documents.staffID,
staff.Name,
staff.ID
FROM
documents
INNER JOIN organizations ON (documents.IDorg = organizations.IDorg)
INNER JOIN staff ON ( FIND_IN_SET( staff.ID, documents.staffID ) > 0 )
MySQL set types have limitations - maximum membership size of 64 for example - but may be sufficient for your needs.
If it was me though, I'd change the model rather than use FIND_IN_SET.

Thank you so much for you answer - greatly appreciated!
My table setup is:
DOCUMENTS:
CREATE TABLE documents (
docID int NOT NULL,
docTitle mediumblob NOT NULL,
staffID varchar(120) NOT NULL,
Author2 int,
IDorg int,
docName varchar(150) NOT NULL,
docEntry int AUTO_INCREMENT NOT NULL,
/* Keys */
PRIMARY KEY (docEntry)
) ENGINE = MyISAM;
STAFF:
CREATE TABLE staff (
ID int AUTO_INCREMENT NOT NULL,
Name varchar(60) NOT NULL,
Organization varchar(20),
documents varchar(150),
Photo mediumblob,
/* Keys */
PRIMARY KEY (ID)
) ENGINE = MyISAM;
The DOCUMENTS table reads via a lookup table (dropdown) from the STAFF table so that I can assign multiple staff members to a document. So I can access the staffID array in the DOCUMENTS table and split that and I wonder if there is a way to then associate the staffID with the staff.Name and print out the staff Name rather than the ID in the results of the query. Thanks again!

Related

Show all columns in MySQL tables without null values in PHP

I have a database of historical personas with the following three tables:
t_name
t_occupation
t_location
t_name includes all the bio-infos, e.g. name, surname, dateofbirth, etc.
t_occupation lists possible occupations (writer, singer, etc.) and is the foreign key to the t_name column "id_occupation"
t_location lists all the locations with plz and links as foreign key to a couple of columns in t_name table, e.g. id_birthplace, id_placeofdeath, id_placeofliving, etc.
I'd like to generate an output of the whole database which lists all historical personas with all the columns that are not null (so for example, Persona 1 may have an id_birthplace but id_placeofdeath is unknown, so id_placeofdeath should not be listed, but the rest should).
In PHP 4.0, I used variables to store the info and worked with if-statements to generate the list (mysql_result), but this is no longer possible with PHP 7.0.
How do I do this in the latest version?
I tried the following code:
$res = mysqli_query($con,"SELECT * FROM ( SELECT t_name AS table, IF(COUNT(name), NOT NULL, name) AS column
FROM t_name
UNION ALL
SELECT t_location AS table, IF(COUNT(location), NOT NULL, location) AS column FROM t_ort
UNION ALL)
WHERE column IS NULL");
while ($dsatz = mysqli_fetch_assoc($res))
{
echo $dsatz["name"] . ", "
.$satz["location"] . "<br /> <br />";
}
The only result I get is: records found but no listing. What did I get wrong?
SELECT * FROM ( SELECT tableA AS table, IF(COUNT(column_a), NOT NULL, column_a) AS column
FROM tableA
UNION ALL
SELECT tableB AS table, IF(COUNT(column_b), NOT NULL, column_b) AS column FROM tableB
UNION ALL -- etc. ) t
WHERE column IS NULL

Is there a way to strictly search for strings in mysql query

Suppose I have a table name 'employee' as bellow:
| name | skills |
|------------------------|
| john | PHP, HTML |
| RIcky | HTML5, PHP |
| ROman | HTML5, HTML|
I want to search for HTML strictly. I tried:
select * from employee where skills like %HTML% // show all result. But it should only display row with name 'john' and 'ROman'.
select * from employee where skills like %HTML5% // show all result. But it should only display row with name 'RIcky' and 'ROman'.
How can i do this directly from mysql query.
Updated:
I couldnot normalize the table because I am working in automatic form builder. Means skills are not static. There may be any values. Like in place of skills user may make other fields with options. Same like survey form biulder.
This would be proper to use.
SELECT * FROM employee WHERE FIND_IN_SET( 'HTML' , REPLACE(skills, SPACE(1), '') ) > 0;
Not exactly what you've asked for:
You could normalize your database table(s) and instead of storing complex datatypes (a list of strings) in one field create three tables:
1) for the properties (skills)
2) for the entities that "have" certain properties (employees having certain skills)
3) a junction table where you store the information (a reference) which entity has which properties
This way your relational database system has a much better chance of using indices to find the appropriate data (using LIKE, string functions et al in a WHERE/ON clause usually causes a full table scan and for that you hardly need a database - you can do that with a flat file almost as easily).
E.g. (I didn't pay attention to the indices though)
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);
// Which employees have the skill 'HTML' ?
$query = "
SELECT
e.name
FROM
skills as s
JOIN
employee_skills as x
ON
s.id=x.id_skill
JOIN
employees as e
ON
x.id_employee=e.id
WHERE
s.name = 'HTML'
";
foreach( $pdo->query($query) as $row ) {
echo $row['name'], "\r\n";
}
/* creating temporary test tables
and inserting sample data
*/
function setup($pdo) {
$pdo->exec('
CREATE TEMPORARY TABLE employees (
id int auto_increment,
name varchar(32),
primary key(id)
)
');
$pdo->exec('
CREATE TEMPORARY TABLE skills (
id int auto_increment,
name varchar(32),
primary key(id)
)
');
$pdo->exec('
CREATE TEMPORARY TABLE employee_skills (
id_employee int,
id_skill int,
unique(id_employee,id_skill)
)
');
$pdo->exec("
INSERT INTO employees (id, name) VALUES
(1, 'John'), (2,'Ricky'), (3,'Roman')
");
$pdo->exec("
INSERT INTO skills (id, name) VALUES
(1, 'PHP'), (2,'HTML'), (3,'HTML5')
");
$pdo->exec("
INSERT INTO employee_skills (id_employee, id_skill) VALUES
(1, 1), (1,2),
(2, 3), (2,1),
(3, 3), (3,2)
");
}
Try surrounding the text you are searching for with speechmarks:
select * from employee where skills like "%HTML%"
select * from employee where skills like "%HTML5%"
Try a REGEX-based search
SELECT * FROM employee WHERE skills REGEXP '[[:<:]]HTML[[:>:]]'

MySQL 3 table search with one table not directly related

I have 3 tables :
wp_users - stores main information about all users,
wp_usermeta - stores additional information about users(first/last name/etc),
wp_friends - stores information about friends from third party services related to a specific user from wp_users
If you are not familiar with WordPress, you can see the structure of both tables at http://codex.wordpress.org/images/9/9e/WP3.0-ERD.png
The structure of my custom table wp_friends is as follows:
CREATE TABLE wp_friends (
id bigint(20) unsigned NOT NULL auto_increment,
uid bigint(20) unsigned NOT NULL default '0',
fr_id VARCHAR (60) NOT NULL default '',
service VARCHAR (20) NOT NULL default '',
name VARCHAR (80) NOT NULL default '',
photo VARCHAR (255) NOT NULL default '',
PRIMARY KEY (id),
KEY uid (uid),
KEY fr_id (fr_id),
KEY service (service)
)`
The uid column is corresponds to the ID column in the wp_users table - this is how I determine which record corresponds to which user.
What I'm trying to do is to create a query that will look in all of the three tables for a match against a keyword. Here is what I've come with so far(the first part was generated by a search function of WordPress):
SELECT
wp_users.ID,wp_users.display_name,wp_users.user_login,
wp_users.user_email,fr.fr_id,fr.name,fr.photo,fr.service
FROM wp_users
INNER JOIN wp_usermeta ON (wp_users.ID = wp_usermeta.user_id)
LEFT JOIN wp_socialaccess_friends AS fr ON fr.uid = 2
WHERE
(
(user_login LIKE '%nik%' OR user_nicename LIKE '%nik%')
AND
(wp_usermeta.meta_key = 'wp_user_level' AND CAST(wp_usermeta.meta_value AS CHAR) != '0')
)
OR ( fr.uid = 2 AND (fr.fr_id LIKE '%nik%' OR fr.name LIKE '%nik%'))
GROUP BY wp_users.ID,fr.fr_id ORDER BY user_login ASC
In the above query, the keyword is "nik"(which also matches a user_login column). The fr.uid part is needed so the returned results are only for the current user. The query fails in the following ways:
It returns all rows from the wp_friends table(because the user_login column is matched as well), that have wp_friends.uid = 2
It returns rows that have wp_friends.uid = 2 but matched with users where wp_users.ID != 2
Is it possible to create a single query, that would return the selected columns, but will also prevent duplicates?
What about joining on a sub-select like:
SELECT
wp_users.ID,wp_users.display_name,wp_users.user_login,
wp_users.user_email
FROM wp_users
INNER JOIN wp_usermeta ON (wp_users.ID = wp_usermeta.user_id)
left join(
select fr_id, uid,name,photo,service from wp_socialaccess_friends where
uid = 2 and
(fr_id LIKE '%nik%' OR name LIKE '%nik%')
) AS fr ON wp_users.ID = fr.uid
WHERE
(
(user_login LIKE '%nik%' OR user_nicename LIKE '%nik%')
AND
(wp_usermeta.meta_key = 'wp_user_level' AND CAST(wp_usermeta.meta_value AS CHAR) != '0')
)
GROUP BY wp_users.ID,fr.fr_id ORDER BY user_login ASC

Retrieving Data From Associated Toxi Table

I'm retrieving my data for part of my site with a typical MySQL query and echoing out the results from various fields etc etc from my main table whose structure is not important but which has a unique id which is 'job_id'
In order to have multiple catagories associated with that 'job_id' i have employed a toxi solution which associates catgories to each 'job_id'.
TABLE `tags` (
`tag_id` INT NOT NULL AUTO_INCREMENT,
`tag_name` VARCHAR(20) NOT NULL,
PRIMARY KEY (`tag_id`)
)
CREATE TABLE `tag_relational` (
`job_id` INT NOT NULL,
`tag_id` INT NOT NULL
)
What i want to do is, when i echo out the info from the main table (using 'job_id') i also want to echo all the catagories which that job_id is matched against.
The query below only returns the first catagory(tag_name) that the job_id is listed against, when it should be up to six (at the moment):
$query = "SELECT * FROM tags t
JOIN tag_relational r
ON t.tag_id=r.tag_id
WHERE r.job_id = $job_id";
$result=mysql_query($query) or die(mysql_error());
$cats=mysql_fetch_assoc($result);
In my code i'm using this to echo out the matched catagories:
<?php echo $cats['tag_name'];?>
Can someone explain how i can get ALL the catagory names to echo out rather than just the first?
Thanks
Dan
BTW, apologies to mu is too short who kindly answered my question when i had dummy/less complete information above.
If you just want to list the category names, then you could use group_concat sort of like this:
select b.*,
group_concat(c.category_name order by c.category_name separator ' ,') as cats
from business b
join tbl_works_categories w on b.id = w.bus_id
join categories c on w.category_id = c.category_name
where ...
group by b.id
You'd need a proper WHERE clause of course. That will give you the usual stuff from business and the category names as a comma delimited list in cats.
If you need the category IDs as well, then two queries might be better: one to get the business information and a second to collect the categories:
select w.bus_id, c.category_id, c.category_name
from tbl_works_categories w
join categories c
where w.bus_id IN (X)
where X is a comma delimited list of business ID values. Then you'd patch things up on the client side.

Multi-tiered Comment Replies: Display and Storage

So I'm trying to create a comment system in which you can reply to comments that are already replies (allowing you to create theoretically infinite threads of replies). I want them to display in chronological order (newest on top), but of course the replies should be directly underneath the original comment. If there are multiple comments replying to the same comment, the replies should also be in chronological order (still underneath the original comment). I also want to limit the number of comment groups (a set of comments with a single comment that is not a reply at all) to, say, 25. How should I set up the MySQL table, and what sort of query would I use to extract what I want?
Here's a simplified version of my DB:
ID int(11) NOT NULL AUTO_INCREMENT,
DatePosted datetime NOT NULL,
InReplyTo int(11) NOT NULL DEFAULT '0',
Sorry if this is kind of confusing, I'm not sure how to word it any differently. I've had this problem in the back of my mind for a couple months now, and every time I solve one problem, I end up with another...
There are many ways. Here's one approach that I like (and use on a regular basis).
The database
Consider the following database structure:
CREATE TABLE comments (
id int(11) unsigned NOT NULL auto_increment,
parent_id int(11) unsigned default NULL,
parent_path varchar(255) NOT NULL,
comment_text varchar(255) NOT NULL,
date_posted datetime NOT NULL,
PRIMARY KEY (id)
);
your data will look like this:
+-----+-------------------------------------+--------------------------+---------------+
| id | parent_id | parent_path | comment_text | date_posted |
+-----+-------------------------------------+--------------------------+---------------+
| 1 | null | / | I'm first | 1288464193 |
| 2 | 1 | /1/ | 1st Reply to I'm First | 1288464463 |
| 3 | null | / | Well I'm next | 1288464331 |
| 4 | null | / | Oh yeah, well I'm 3rd | 1288464361 |
| 5 | 3 | /3/ | reply to I'm next | 1288464566 |
| 6 | 2 | /1/2/ | this is a 2nd level reply| 1288464193 |
... and so on...
It's fairly easy to select everything in a useable way:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
ordering by parent_path, date_posted will usually produce results in the order you'll need them when you generate your page; but you'll want to be sure that you have an index on the comments table that'll properly support this -- otherwise the query works, but it's really, really inefficient:
create index comments_hier_idx on comments (parent_path, date_posted);
For any given single comment, it's easy to get that comment's entire tree of child-comments. Just add a where clause:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
the added where clause will make use of the same index we already defined, so we're good to go.
Notice that we haven't used the parent_id yet. In fact, it's not strictly necessary. But I include it because it allows us to define a traditional foreign key to enforce referential integrity and to implement cascading deletes and updates if we want to. Foreign key constraints and cascading rules are only available in INNODB tables:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Managing The Hierarchy
In order to use this approach, of course, you'll have to make sure you set the parent_path properly when you insert each comment. And if you move comments around (which would admittedly be a strange usecase), you'll have to make sure you manually update each parent_path of each comment that is subordinate to the moved comment. ... but those are both fairly easy things to keep up with.
If you really want to get fancy (and if your db supports it), you can write triggers to manage the parent_path transparently -- I'll leave this an exercise for the reader, but the basic idea is that insert and update triggers would fire before a new insert is committed. they would walk up the tree (using the parent_id foreign key relationship), and rebuild the value of the parent_path accordingly.
It's even possible to break the parent_path out into a separate table that is managed entirely by triggers on the comments table, with a few views or stored procedures to implement the various queries you need. Thus completely isolating your middle-tier code from the need to know or care about the mechanics of storing the hierarchy info.
Of course, none of the fancy stuff is required by any means -- it's usually quite sufficient to just drop the parent_path into the table, and write some code in your middle-tier to ensure that it gets managed properly along with all the other fields you already have to manage.
Imposing limits
MySQL (and some other databases) allows you to select "pages" of data using the the LIMIT clause:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Unfortunately, when dealing with hierarchical data like this, the LIMIT clause alone won't yield the desired results.
-- the following will NOT work as intended
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted
LIMIT 25 OFFSET 0;
Instead, we need to so a separate select at the level where we want to impose the limit, then we join that back together with our "sub-tree" query to give the final desired results.
Something like this:
select
a.*
from
comments a join
(select id, parent_path
from comments
where parent_id is null
order by parent_path, post_date DESC
limit 25 offset 0) roots
on a.parent_path like concat(roots.parent_path,roots.id,'/%') or a.id=roots.id)
order by a.parent_path , post_date DESC;
Notice the statement limit 25 offset 0, buried in the middle of the inner select. This statement will retrieve the most recent 25 "root-level" comments.
[edit: you may find that you have to play with stuff a bit to get the ability to order and/or limit things exactly the way you like. this may include adding information within the hierarchy that's encoded in parent_path. for example: instead of /{id}/{id2}/{id3}/, you might decide to include the post_date as part of the parent_path: /{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/. This would make it very easy to get the order and hierarchy you want, at the expense of having to populate the field up-front, and manage it as the data changes]
hope this helps.
good luck!
You should consider nesting your comments in a tree - I'm not that well familiar with data trees, but I can accomplish something relatively easy - I'm open to any suggestions (and explanations) for optimizing the code - but an idea would be something like this:
<?php
$mysqli = new mysqli('localhost', 'root', '', 'test');
/** The class which holds the comments */
class Comment
{
public $id, $parent, $content;
public $childs = array();
public function __construct($id, $parent, $content)
{
$this->id = $id;
$this->parent = $parent;
$this->content = $content;
}
public function addChild( Comment $obj )
{
$this->childs[] = $obj;
}
}
/** Function to locate an object from it's id to help nest the comments in a hieraci */
function locateObject( $id, $comments )
{
foreach($comments as $commentObject)
{
if($commentObject->id == $id)
return $commentObject;
if( count($commentObject->childs) > 0 )
return locateObject($id, $commentObject->childs);
}
}
/** Function to recursively show comments and their nested child comments */
function showComments( $commentsArray )
{
foreach($commentsArray as $commentObj)
{
echo $commentObj->id;
echo $commentObj->content;
if( count($commentObj->childs) > 0 )
showComments($commentObj->childs);
}
}
/** SQL to select the comments and order dem by their parents and date */
$sql = "SELECT * FROM comment ORDER BY parent, date ASC";
$result = $mysqli->query($sql);
$comments = array();
/** A pretty self-explainatory loop (I hope) */
while( $row = $result->fetch_assoc() )
{
$commentObj = new Comment($row["id"], $row["parent"], $row["content"]);
if($row["parent"] == 0)
{
$comments[] = $commentObj;
continue;
}
$tObj = locateObject($row["parent"], $comments);
if( $tObj )
$tObj->addChild( $commentObj );
else
$comments[] = $commentObj;
}
/** And then showing the comments*/
showComments($comments);
?>
I hope you get the general idea, and I'm certain that some of the other users here can provide with some experienced thoughts about my suggestion and helt optimize it.
In database, you may create a table with foreign key column (parent_comment), which references to comments table itself. For example:
CREATE TABLE comments (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
parent_comment INT FOREIGN KEY REFERENCES comments(id),
date_posted DATETIME,
...)
In order to show comments to single item, you'll have to select all comments for particular item, and parse them recursively in your script with depth-first algorithm. Chronological order should be taken into account in traversal algorithm.
I would consider a nested set, for storing this type of hierarchical data. See http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ for an example.
You might find this method helpful which involves a single call to a non-recursive stored procedure.
Full script can be found here : http://pastie.org/1259785
Hope it helps :)
Example stored procedure call:
call comments_hier(1);
Example php script:
<?php
$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
$result = $conn->query(sprintf("call comments_hier(%d)", 3));
while($row = $result->fetch_assoc()){
...
}
$result->close();
$conn->close();
?>
SQL script:
drop table if exists comments;
create table comments
(
comment_id int unsigned not null auto_increment primary key,
subject varchar(255) not null,
parent_comment_id int unsigned null,
key (parent_comment_id)
)engine = innodb;
insert into comments (subject, parent_comment_id) values
('Comment 1',null),
('Comment 1-1',1),
('Comment 1-2',1),
('Comment 1-2-1',3),
('Comment 1-2-2',3),
('Comment 1-2-2-1',5),
('Comment 1-2-2-2',5),
('Comment 1-2-2-2-1',7);
delimiter ;
drop procedure if exists comments_hier;
delimiter #
create procedure comments_hier
(
in p_comment_id int unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_comment_id smallint unsigned,
comment_id smallint unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier select parent_comment_id, comment_id, v_depth from comments where comment_id = p_comment_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from comments c inner join hier on c.parent_comment_id = hier.comment_id and hier.depth = v_depth) then
insert into hier
select c.parent_comment_id, c.comment_id, v_depth + 1 from comments c
inner join tmp on c.parent_comment_id = tmp.comment_id and tmp.depth = v_depth;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
select
c.comment_id,
c.subject,
p.comment_id as parent_comment_id,
p.subject as parent_subject,
hier.depth
from
hier
inner join comments c on hier.comment_id = c.comment_id
left outer join comments p on hier.parent_comment_id = p.comment_id
order by
hier.depth, hier.comment_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
call comments_hier(1);
call comments_hier(5);

Categories