I am using wordpress to let people sign up for a task.
A normal post (i.e. project) has multiple tasks.
When a user wants to sign up for task1 his/hers usersname goes into the meta table in my database in a meta key field called task1. Multiple users can sign up for the same task.
I managed to display a table with the task titles and all the people who signed up for it. But I don't want to display the same title and the same tasknames multiple times. Can anybody help me out?
The current output is like this
Title Tasks Volunteers
Project 1 Task 1 user3
Project 1 Task 1 user6
Project 1 Task 1 user5
Project 1 Task 2 user2
Project 1 Task 2 user9
Project 2 Task 1 user1
Project 2 Task 2 user8
Project 2 Task 2 user4
And I want it to look like this
Title Tasks Volunteers
Project 1 Task 1 user3
user6
user5
Task 2 user2
user9
Project 2 Task 1 user1
Task 2 user8
user4
Here is my code.
I thought I use an INNER JOIN in my sql query because I need to get values from 2 tables (wp_1_posts and wp_1_postmeta).
<?php
$query = "SELECT *
FROM wp_1_posts
INNER JOIN wp_1_postmeta
ON wp_1_posts.id=wp_1_postmeta.post_id
WHERE wp_1_postmeta.meta_key='task1'
OR wp_1_postmeta.meta_key='task2'
ORDER BY post_title";
$result = mysql_query($query) or die(mysql_error());
?>
<table border='1'>
<tr>
<th>Title</th>
<th>Tasks</th>
<th>Volunteers</th>
</tr>
<?php
while($row = mysql_fetch_assoc($result))
{
$post_title = $row['post_title'];
$meta_key = $row['meta_key'];
$meta_value = $row['meta_value'];
if ($meta_key == 'task1'){
$task_name = "Task 1";
}
else if ($meta_key == 'task2'){
$task_name = "Task 2";
}
echo "<tr>";
echo "<td>". $post_title. "</td>";
echo "<td>". $task_name. "</td>";
echo "<td>". $meta_value. "</td>"; //names of volunteers
echo "</tr>";
}
?>
</table>
You can't get a result that looks like that with SQL. Those "holes" there aren't exactly something you'll have returned like that.
As commented, your INNER vs OUTER JOIN doesn't really make a difference in this case. I usually use LEFT JOIN (an outer join) for hooking up posts with post meta data.
However, I think you're ok. It's just in how you display the results. Remember that you need the title and task columns there in order to ascertain where each of the volunteers belong.
You're saying the same thing, but you're visualizing it differently. You're omitting some repetition for the end user that you are otherwise implying.
You could simply keep an array off to the side that keeps track of which tasks and titles you've already displayed, then on your loop if it it was already displayed, ensure it's set to an empty string.
For example:
$titles = array();
$tasks = array();
while($row = mysql_fetch_assoc($result)) {
// set the $post_title to the actual title or an empty string based on if it was already seen or not
$post_title = !in_array($row['post_title'], $titles) ? $row['post_title']:'';
// record that it has been seen
$titles[] = $row['post_title'];
// ...do the same for tasks and then the rest of your code as you had it
Keep in mind you will need to adjust that logic a little bit if your task names can appear multiple times under each title section. In that case you may want to keep a keyed array with title.task combinations.
Anyway, hopefully that gives you an idea on how to keep track of what you already displayed, in a relatively simple manner, so you can get the output you want.
SQL can't always return the perfect tabular result.
This is also a little problematic if you end up having many tasks:
if ($meta_key == 'task1'){
$task_name = "Task 1";
}
else if ($meta_key == 'task2'){
$task_name = "Task 2";
}
You could instead create a formatter helper function that would use PHP's ucwords() or ucfirst() functions and perhaps string replace or even a little more heavy regular expression to add spaces between numbers and such. I bet $10 you can find some snippet of code out there that can help you with making the titles pretty. The keyword here is "humanize" ... Many frameworks and snippets out there will have such helper utility classes and functions usually by that kinda phrasing.
You may also get lucky with a jQuery plugin of some sort (or some other JavaScript tool) that works with tabular data and can format it in the manner you're after. It might even provide some more advanced sorting options. Then you just might be able to JSON encode the table result and get exactly what you want in the format you need. That might be the closest you get to out of the box SQL result without any sort of formatting with PHP.
Related
I'm trying to set up a for loop to print all data from a database table that starts with a certain letter. For example, let's say from all the usernames in the database, I only want to print every username starting with the letter "b". My end result I want to achieve is something along the lines of this:
A
adam
angel
apple
B
ball
bear
blue
C
car
cell
chris
#
0wen
1uis
3than
.,_
.apple.
,car,
_jeff_
I want to be able to print all usernames under the corresponding character in which they start with. I have the starting characters under heading tags, so all I really need is to print the usernames under them. I figured running a simple for loop under each heading tag that filters that data would do the trick, but I for the life of me can't figure out how to go about doing it. This my code so far (I know this will print every user in the table):
require_once 'important/connect.php';
$query = $link->prepare('select distinct usr from info order by usr');
$query->execute();
$users = $query->fetchAll(PDO::FETCH_OBJ);
foreach ($users as $user)
{
print "<center>{$user->usr}</center>";
}
This code above is merely to show what I'm working with. I'm shooting to keep each username to print as a url as well, so when their username is clicked, it will display more information in a seperate pop up window, however I already have that working. Anyway, how would I implement this or is the way I wanna go about it not possible?
For the loop you can use this PHP syntax:
foreach(range('A','Z') as $letter) {
//your own code
}
range will make you iterate through the letters as you do usually with foreach.
The point about your question is about performance.
If you have a small amount of data to return you can return the whole array and then search with php functions to manipulate arrays only the items that begins with $letter.
If you have many items it is better (IMHO) to run a query for each letter using mysql instruction LIKE (cause this will improve performance):
"SELECT usr FROM info WHERE usr LIKE '$letter%' ORDER BY usr GROUP BY usr"
LIKE will match only those usr that will start with $letter and will have anything else following. % is the wildcard to be used.
Also note that in the query I have used GROUP BY usr instead of SELECT DISTINCT usr to get the same result as this is the right way to get an unique list of users.
As Lelio Faieta pointed out, looping through the user list over and over again might be bad for performance. However, querying the database over and over again might also be bad.
So I would suggest getting the users just once, and getting them in the right order:
SELECT usr FROM info ORDER BY usr GROUP BY usr
Then loop through them, and keep track of what starting letter you're on:
$oldLetter = '';
$newLetter = '';
foreach ($users as $user)
{
$newLetter = strtoupper(substr($user->usr, 0, 1));
if($oldLetter != $newLetter)
{
//We are on a new letter.
//Print a heading for all letters between the old one and the new one.
foreach(range(++$oldLetter, $newLetter) as $letter)
print '<h2>' . $letter . '</h2>';
$oldLetter = $newLetter;
}
//Print the user, as before.
print "<center>{$user->usr}</center>";
}
This will not take care of the last group (titled # in your example) for you. To do that, you will need to check whether the first character is a letter in the SQL and sort on that somehow.
Please note that this code is not copy-paste-ready, you will need to work some on it. For instance if the old letter is Z there might be some problems. I have not tested this, so you should before you put it into production.
Pseudo code for a nasty solution:
$alphabetArray = array("A","B", etc);
foreach($alphabetArray as $letter) {
echo '<h2>'.$letter.'</h2>';
$sql = "SELECT * FROM `usertable` WHERE `username` = '".$letter."%' ORDER BY `username` ASC;"
// execute sql, loop through and output results
}
I have in one database rough 2,000 movies with genres store like this:
MOVIE \ ID \ Genre
Avatar \ 1 \ action ^ adventure ^ scifi
matrix \ 2 \ action ^ scifi
and I was trying to make a system that would sort by genre, and when I researched it I found that I needed a table that would connect the two. (see this question: MySQL select genres problem. (php))
So I know how to implement this, I just dont know how I could transfer the genres into a seperate table.
The only way I could come up with is to go row by row and grab the genre column and explode the text, get the array and test for different genres, but that seemed too much of a hassle and difficult to do on 2k+ rows.
Thanks!
The answer you quote in your question seems to have nailed the database strutcure you require. So really it is about replicating this.
Firstly I would create the Genre table. Again much like in the reference quoted the following structure will suffice.
+------------+---------+
| id | genre |
+------------+---------+
| 1 | action |
+------------+---------+
| 2 | drama |
+------------+---------+
and so on. Once you have created the genre table. You will need to create your movie_genre table where the genres will be stored. So for example
+----------+-----------+---------+
| id |movie_id |genre_id |
+----------+-----------+---------+
The following structure. Again like the reference quoted.
Once you have the database structure ready, the next thing would be to create a simple switch function. For example
public function returnGenId($genre)
{
switch($genre)
{
case "action":
return 1;
break;
case "drama"
return 2;
break;
}
}
Obviously the return will match with the id of the genre in the genre table.
You can now get everything from your database by doing
SELECT * from movies
Which should leave you with an array similar to this
array(
[0]=>array(
[id]=>1
[movie]=>Avatar
[genre]=>action ^ adventure ^ scifi
)
)
Once you have your data then just loop through, exploding the genre and creating your new array for example
foreach($results as $key=>$result)
{
$eplosion = explode(' ^ ',$result['genre'];
foreach($explosion as $exploded)
{
$genres[] = returnGenId($exploded);
}
$data[$result['id']] = array('movie'=>$result['movie'],'genres'=>$genres);
}
This should leave with an array like so (as an example, depending on the switch statement)
array(
[1]=>array(
[movie]=>Avatar
[genres]=>array(
[1]=>1
[2]=>3
)
)
)
Now you can loop through the data as put it into the relevant database
foreach($data as $key=>$film)
{
foreach($film['genre'] as $dbGenre)
{
}
}
In the second foreach do a mysql insert into movie_genre using $key as the movie_id and then $dbGenre as the genre_id. By doing this one script you can sort your database tables out and structure them correctly. Although this can be a hassle. Once you have got it out of the way, things will be infinitely easier. Let me know how you get on
Update
I've run these kind of scripts on databases with a couple of hundred thousand rows, so I can't see an issue for a couple of thousand. But this also depends on the memory size set on your server.
Increasing your memory limit may be an idea if need be, see this link:
http://davidwalsh.name/increase-php-memory-limit-ini_set
Another idea is to hash out the last foreach loop and do a print of the data ie
print_r($data);
/*
foreach($data as $key=>$film)
{
foreach($film['genre'] as $dbGenre)
{
}
}
/*
Before executing the script add this as close to the very top (preferably the first line)
$start = time();
Then add this to the bottom. Again as close the to the bottom of the script.
$end = time();
$wait = $end – $start;
echo 'The script took ' . $wait . ' seconds to run';
This will show you the amount of time taken.
Always before messing with the database though, dump the database for safety. Maybe even clone it and run this on a test database for peace of mind.
I think you can create a new table for genres first than you might use SUBSTRING_INDEX(field, '^', 1) in your query to select genres DISTINCT and write it to your new DB table in a single query.(INSERT INTO .... (SELECT ...). Than all you need to do is relate your new table of genres to your movies table. Here is a link for SUBSTRING_INDEX.
This might be a simple question, but I can't find a definitive answer I can understand. I use PHP loops alot, I'm fairly new to PHP so they are usually simple like so:
<?php
$result = mssql_query("SELECT Price FROM Window_Extras WHERE ExtraID = '4' ");
while ($row = mssql_fetch_array($result)) {
?>
<a title="<?php echo $row['Colour']; ?>"></a>
<?php }?>
Is a really simple example, that doesn't make much sense, but I hope it shows how I use them. The question I wanted to ask was if $row and $result have to be named that for it to work, could they for example be named $priceresult and $pricerow?
This is because sometimes I would like to use multiple queries for a single loop, for example:
<?php
$result = mssql_query("SELECT Price FROM Extras WHERE ExtraID = '4' ");
$colourresult = mssql_query("SELECT ColourID FROM Colours WHERE Type = '8' ");
while ($row = mssql_fetch_array($result, $colourresult)) {
?>
This however didn't work, when I tried to echo out:
<?php echo $row['ColourID']; ?>
Can anyone tell me how I should be approaching this, and if I am at all on the correct track. Sorry if I havn't explained it very well.
To answer your first question:
Yes, you can use any variable name you like for the result and row variables. PHP doesn't care what you call them, and in fact it's perfectly possible to have several of them in use at any given time, in which case they obviously need to have different names.
You then followed up that question by asking why the following code doesn't work:
$result = mssql_query("SELECT Price FROM Extras WHERE ExtraID = '4' ");
$colourresult = mssql_query("SELECT ColourID FROM Colours WHERE Type = '8' ");
while ($row = mssql_fetch_array($result, $colourresult)) {
....
}
The reason for this is that the _fetch_array() function can only work with one set of results at a time. You would need to fetch a separate row array for each of them.
It's not clear what you're trying to do with these two queries, and why you would want to put them into the same loop together in the way you've shown.
I'm going to assume that the two queries are linked in some way that makes it logical for you to use them together like this? Perhaps the Extra item you're loading has a known Colour; ie you know that the Extra item numbered 4 is coloured with the Colour numbered 8?
Typically a program wouldn't be written with this knowledge; it would be part of the data. So in the Extras table, you would have a ColourID field, which would contain the value 8. The program would load the Extras record, see that the ColourID was set, and then load the matching Colours record according to what it saw.
Thus, your code could look something like this:
$result = mssql_query("SELECT Price FROM Extras WHERE ExtraID = '4' ");
while ($row = mssql_fetch_array($result)) {
$colourresult = mssql_query("SELECT ColourID FROM Colours WHERE Type = '".$row['colourID']."' ");
while ($row2 = mssql_fetch_array($result)) {
....
}
}
Inside the inner while loop, you could then access fields from either query, using $row or $row2 respectively (again, you can name these as you see fit).
However, that's not the end of the story, because SQL actually has the ability to merge these two queries into one without needing all that PHP code, using a thing call a SQL JOIN.
Now we can write a more complex query, but go back to having simpler PHP code:
$result = mssql_query("SELECT Extras.Price, Colours.ColourName FROM Extras WHERE ExtraID = '4' INNER JOIN Colours ON Colours.ColourID = Extras.ColourID");
while ($row = mssql_fetch_array($result)) {
....
}
If you're a beginner in PHP and SQL, these concepts are all probably new to you, so I advise trying them out, experimenting with them, and most importantly, reading a few (good quality) tutorials about them before proceeding much further.
Hope that helps. :)
(PS: as I said above, make sure you're reading good tutorials; beware of bad PHP examples and teaching sites -- there's a lot of them out there, teaching poor code and obsolete techniques; make sure you're reading something worthwhile. A good place to start might be http://phpmaster.com/)
This is because mssql_fetch_array can only take one result set. So removing $result and leaving $colourresult should work for you.
See: http://php.net/manual/en/function.mssql-fetch-array.php
Your variables ($...) can be called whatever you want, it's generally better to name them in a way that you can understand, hence most of the examples in the PHP Manual contain variables like $row, $result, $query, etc.
In terms of your database query, you can only pass one query to the mssql_query method. If you have data from different tables that you need to display, you should try and join the tables if possible using SQL rather than looping through multiple result sets.
My first post, tried to be as thorough as possible, apologies in advance if I've gotten something wrong. I'm pretty novice with PHP/SQL as well so please be patient with me. I've found a couple of similar questions about loops within loops but I'm not sure the solutions apply in my case.
I have two tables, tws_workshopNames and tws_workshops. The primary key from tws_workshopNames is used as a foreign key in tws_workshops to relate the two tables. The reason I've split this into two tables is there are many cases where the same workshop name/price/description is offered on multiple dates/times.
Can't submit a screenshot but here's a simplified outline of the table design in SQL Server:
tws_workshopNames:
workshopNameID (pri)
description
price
etc.
tws_workshops:
workshopID (pri)
workshopNameID (foreign)
date
time
etc.
What I want to happen is basically this:
query tws_workshopNames table and display workshopName/price/description/etc.
for each workshopName go through the tws_workshops table and display all records that have the same workshopNameID
In other words, go through tws_workshopNames and display the first workshopName, then go through tws_workshops and display all records that are related to that workshopName, then go to next workshopName in tws_workshopNames, display all records related to that workshopName etc.
I'm able to achieve the desired result by using a while loop within a while loop wherein the outer loop does a call to tws_workshopNames and the nested loop does a call to the tws_workshops table. However I've been reading a lot about this and it's clear this is not a good approach as it results in a lot of calls to the db, but I'm having a hard time understanding any alternatives.
Desired output:
Workshop 1
price
description
date (of workshop 1)
time (of workshop 1)
...
Workshop 2
price
description
first date (of workshop 2)
first time (of workshop 2)
second date (of workshop 2)
second time (of workshop 2)
third date (of workshop 2)
third time (of workshop 2)
...
Workshop 3
price
description
date (of workshop 3)
time (of workshop 3)
...
etc.
Here is the current code that works with nested while loops:
<?php
// query workshopNames table, what types of workshops are available?
$query = mssql_init("tws_sp_workshopNames", $g_dbc);
// pull up result
$result = mssql_execute($query);
$numRows = mssql_num_rows($result);
while($row = mssql_fetch_array($result)) {
echo "<div style=\"...\">
<span class=\"sectionHeader\">" . $row['workshopName'] . "</span><br />
<span class=\"bodyText\"><strong>" . $row['price'] . "</strong></span><br />
<span class=\"bodyText\">" . $row['description'] . "</span>";
$workshopNameID = $row['workshopNameID'];
// query workshops table, what are the dates/times for each individual workshop?
$query2 = mssql_init("tws_sp_workshops", $g_dbc);
mssql_bind($query2, "#workshopNameID", $workshopNameID, SQLVARCHAR);
//pull up result
$result2 = mssql_execute($query2);
$numRows2 = mssql_num_rows($result2);
while($row2 = mssql_fetch_array($result2)) {
echo $row2[date] . " ";
echo $row2[time] . "<br />";
};
echo "</div><br />";
};
?>
The stored procedures are very simple:
tws_sp_workshopNames = "SELECT workshopNameID, workshopName, description, location, price FROM tws_workshopNames"
tws_sp_workshops = "SELECT date, time, maxTeachers, maxStudents, teachersEnrolled, studentsEnrolled FROM tws_workshops WHERE workshopNameID=#workshopNameID"
Hope that's all relatively clear, all I'm really looking for is a better way to get the same result, i.e. a solution that does not involve a db call within the loops.
Thanks in advance for any help, been a few days straight banging my head against this one...
You are correct to avoid usage of looping queries in this case (since the desired result can be achieved with just a simple JOIN in one query).
I would avoid using GROUP_CONCAT() as well because there is a character limit (by default, you can change it), plus you have to parse the data it outputs, which is kind of a pain. I would just get all the data you need by joining and get every row. Then load the data into arrays using the workshop ID as the key but leave the array open to append each of your time data as a new array:
$workshops[$workshop_name][] = $timesArray;
Then on your output you can loop, but you don't have to hit the database on each call:
foreach ($workshops as $workshop_name => $times)
{
echo $workshop_name;
foreach ($times as $time)
{
echo $time;
}
echo "<br>";
}
This is not the exact code, and as you've pointed out in your question, you want to keep/display some other information about the workshops - just play around with the array structure until you get all the data you need in a hierarchy. You can use something like http://kevin.vanzonneveld.net/techblog/article/convert_anything_to_tree_structures_in_php/ if you are trying to build a deep tree structure, but I think that's overkill for this example.
Since this is what I would call an "Intermediate Level" question, I think you should try to work through it (THIS is what makes you a good programmer, not copy/paste) using my suggestions. If you get stuck, comment and I'll help you further.
I don't see anything wrong with the way you're doing things. I suppose you could concatenate the result and then manipulate the output in your application using one query. Your query might looks something like
SELECT
n.workshopNameId,
n.price,
n.description,
GROUP_CONCAT(w.date) as dates,
GROUP_CONCAT(w.time) as times
FROM tws_workshopNames n
INNER JOIN tws_workshops w USING(workshopNameID)
GROUP BY n.workshopNameID
hello im just curious. about how they do stuff. what i assume they do something like this
#someone1 im stacking on stackoverflow RT #someone2 : hello guys what are you doing?
before i do it in my way i want to tell you about my database scheme
// CID = COMMENT ID, BID = BLOG ID, UID = USER ID
CID BID UID COMMENT
1 1 1 #someone1 im stacking on stackoverflow RT #someone2 : ....
2 1 4 #someone1 im stacking on stackoverflow RT #someone2 : ....
3 1 12 #someone1 im stacking on stackoverflow RT #someone2 : ....
they use regex to do like this to take the #someones name
preg_match_all("/#[a-zA-Z0-9_]+/", $text, $matches);
then they get the # off each name
foreach ($matches as $value) {
foreach ($value as $value) {
$usernames[] = substr($value, 1);
}
}
then they get the UID from the database from doing something like this
foreach ($username as $value) {
# insert database one by one ? so it will be like the example above
}
then we can just output the comment buy geting the UID.
then somhow we can get all the comments in the blog. ( without a same comment ) where blog buid = 1 and give them an notification on every user by where uid = :uid.
is there any better way doing this ? something like twitter or convore ?
Thanks for looking in
Adam Ramadhan
I have done something similar to this with an in-house application that we use for communication.
Basically, you are going to have two tables: status_updates and mentions. Each status update has many mentions. Whenever someone creates a status update, you save it to the status_updates table. During this process, you can also use Regex to detect any #username "mentions". When you find a mention, you add it to your mentions table. For example, your mentions table might look something like this:
mention_id (Auto-incrementing key) | status_message_id | username_id
That way if you want to see if someone is mentioned in a status message you can do a quick lookup in the status_messages table, as opposed to loading up the status message and running the Regex each time. The other nice thing about this approach is that it allows you to have multiple mentions in each status message. Just create a record in mentions for each.
That's the basic way that we have set it up.
EDIT: If you wanted to pull an "activity feed" for a given user, showing only the status updates in which they have been mentioned, it would be as simple as:
SELECT * FROM mentions m LEFT JOIN status_messages s ON m.status_message_id = s.id WHERE m.username_id = $username_id
I should note that this is not how they do it at Twitter, because they are dealing with issues of scale that would make this simple way of doing things impossible. However, I think this is the simplest solution that works well if you aren't worried about scaling to hundreds of thousands of users. If you are, then you probably have more issues on your hands than this.
You can use it like bb codes but instead of taken it like [foo] [/foo] you take the # and end it at the space ... before it's insert into your database you take another script and break the # after the space. and put the mention into a separate column then use bbcodes to make the mention on the fly
Example..
if ( strstr("$status", "#") ) {
$explodeat = explode("#", $status);
$explodeat1 = explode(" ", $explodeat[1]);
$status=$explodeat1[0];
}
and insert $status into your mentions column in your database... The BB code for it after that won't be so hard
I think in MySQL, you can use DISTINCT to avoid duplicates rows:
Something link this:
SELECT `CID`, `BID`, DISTINCT `COMMENT`
FROM comments
WHERE UID = :uid
AND ##Others clauses for bloc here##