I have a database table that tracks all pageviews which are fired in via a php script. The table looks like this:
rowid (AI)
user_id
page_url
visitor_ip
session_id
I want to be able to query my table to "Show the paths (max 5 pages) visitors take to get to X page within a single session". The output would be a table with a URL in each column, so the path is left to right in the order they visited pages with the same session_id ending with a certain page.
Any clue? I've been looking for a reporting tool to help me build these segments but I'm not coming up with anything so I'm trying to see if there is a way to just query it. I'd like to avoid turning to some other tool for collection and just query my DB if I can.
Does something like this give you what you want (warning - untested):
select group_concat(page_url order by rowid separator '->'),session_id
from pageviews group by session_id
?
One idea is to use correlated subqueries in the select list.
If I understood the specification, the argument(parameter) to the query will be a specific `page_url`, given as "X" in the specification.
The outer query will retrieve the rows for that page_url. The subqueries in the SELECT list will get the previous page_url in the session. (We don't see a datetime/timestamp, so we will need to depend on values of `rowid` increasing for subsequent page views (i.e. previous page views will have a "lower" value of `row_id`.
Something like this:
SELECT ( SELECT p5.page_url
FROM pageviews p5
WHERE p5.session_id = t.session_id
AND p5.rowid < t.rowid
ORDER BY p5.rowid DESC
LIMIT 4,1
) AS back_5_page_url
, ( SELECT p4.page_url
FROM pageviews p4
WHERE p4.session_id = t.session_id
AND p4.rowid < t.rowid
ORDER BY p4.rowid DESC
LIMIT 3,1
) AS back_4_page_url
, ( SELECT p3.page_url
FROM pageviews p3
WHERE p3.session_id = t.session_id
AND p3.rowid < t.rowid
ORDER BY p3.rowid DESC
LIMIT 2,1
) AS back_3_page_url
, ( SELECT p2.page_url
FROM pageviews p2
WHERE p2.session_id = t.session_id
AND p2.rowid < t.rowid
ORDER BY p2.rowid DESC
LIMIT 1,1
) AS back_2_page_url
, ( SELECT p1.page_url
FROM pageviews p5
WHERE p1.session_id = t.session_id
AND p1.rowid < t.rowid
ORDER BY p1.rowid DESC
LIMIT 0,1
) AS back_1_page_url
, t.page_url
, t.session_id
, t.row_id
FROM pageviews t
WHERE t.page_url = 'X'
Those subqueries are going to be executed for each row returned by the outer query, so this could eat our lunch in terms of performance. If no suitable indexes are available, it's going to eat our lunch box too.
For the subqueries, we are going to want an index...
ON pageviews (session_id, row_id, page_url)
The outer query will benefit from an index ...
ON pageviews (page_url, row_id, session_id)
As an idea for the start of a different approach, if we were getting the path to every page_url, and not just a specific one...
SET group_concat_max_len = 524288 ;
SELECT t.session_id
, t.page_url
, SUBSTRING_INDEX(
GROUP_CONCAT(t.page_url SEPARATOR '\t' ORDER BY t.rowid DESC)
,'\t',6) AS `last_5_pages`
FROM pageviews t
GROUP
BY t.session_id
, t.page_url
HAVING t.page_url = 'X'
This assumes that the page_url will not contain a tab (0x09) character.
The last_5_pages column would be a tab-delimited list of page_url, the most recent page view first, followed by the previously viewed page_url, etc.
Getting those split out as individual columns would be more work, wrapping that query in an inline view and some combination of SUBSTRING_INDEX, possibly REVERSE, and a function to count the number of page_url in the list... that gets kind of nasty to do in SQL. If I went this approach, I'd prefer to handle parsing out the page_url from the tab delimited list in the client.
Here is what I ended up doing - worked great.
<?php
require_once 'init.php';
// ----------------- PAGE PATH REPORT
$html = "<table>";
$html .= "<tr><th align='left'>PAGE PATHS HITTING GOAL.PHP</th></tr>";
$paths = array();
$sql = "SELECT cookie_uid, page_url FROM pageviews ORDER BY rowid";
$result = mysqli_query($conn, $sql);
$got_rows = mysqli_num_rows($result);
if ($got_rows) {
while ($row = mysqli_fetch_array($result)) {
// Create an array for the cookie_uid if it doesn't exist yet
if ( ! array_key_exists($row['cookie_uid'], $paths) || ! is_array($paths[$row['cookie_uid']])) {
$paths[$row['cookie_uid']] = [];
}
// Add to the array now that we know it exists
array_push($paths[$row['cookie_uid']], $row['page_url']);
}
foreach ($paths as $session => $page) {
$html .= "<tr>";
$html .= '<td>' . implode(' ---> ', $page) . "</td>";
$html .= "</tr>";
}
} else {
$html .= '<td colspan="2">No results</td>' . "";
}
$html .= "</table>";
echo $html;
if (!mysqli_query($conn,$sql)) {
die('Error: ' . mysqli_error($conn));
}
// ----------------- ALL PAGES REPORT
echo "</br></br>";
echo "<tbody><table>";
echo "<tr><th align='left'>UNIQUE PAGES</th></tr>";
$sql = "SELECT distinct page_url FROM pageviews";
$allpages = mysqli_query($conn, $sql);
foreach ($allpages as $page){
echo "<tr>";
echo "<td>" . $page['page_url'] . "</td>";
echo "</tr>";
}
echo "</tbody></table>";
mysqli_close($conn);
error_reporting(E_ALL);
?>
That gives me this:
/analytics/testpage.php ---> /analytics/testpage2.php ---> /analytics/goal.php
Related
in wordpress custom php this code works:
$result = count_users();
echo 'There are ', $result['total_users'], ' total users';
foreach($result['avail_roles'] as $role => $count) {
echo $count, ' are ', $role, 's', ', ' . "<br />";
}
echo '.' . "<br />";
Here is an example of output by that:
There are 14 total users
1 are administrators,
3 are s2member_level1s,
13 are access_s2member_ccap_video,
13 are access_s2member_ccap_saas,
1 are access_s2member_ccap_optins,
10 are subscribers,
So, is there a way to count all users with a role, like say: s2member_level1s, or access_s2member_ccap_saas with just one call, or do I have to do it like that, then count the foreach results, every time?
I am hoping there is a way to just count them with one pull, so it does not take a lot of resources and time for the page to load, everytime we load the page.
Thanks,
-Rich
Since you're looping through the results and print the key of each one, you should be able to access directly the result using the key:
$result = count_users();
echo $result['avail_roles']['s2member_level1s'];
Here's more information on arrays: http://php.net/manual/en/language.types.array.php
You can do this by simply executing mysql query, without running a php loop . You just need to use group by with your column name that defines roles(e.g. roletype).
SELECT count(*), roletype FROM `user` group by roletype
you can change the query this way to get results instantly from database single query
select user_id,
count(*) totalusers,
sum(case when avail_roles = 'administrators' then 1 else 0 end) administratorsCount,
sum(case when avail_roles = 's2member_level1s' then 1 else 0 end) s2member_level1sCount,
............
from userstable
group by user_id
So administratorsCount gives number of administrators etc..
How would it work in something like this? I've even tried using "AND tb1.name !=", but it didn't work.
$area = 0;
$stmt = $dbh->prepare('SELECT * FROM charinfo WHERE current_area != :area ORDER BY current_area');
$stmt->execute(array('area' => $area));
$result = $stmt->fetchAll();
$place = 1;
I run a game server, and on the website I have a Top 30 leaderboard which is working wonders (found the code directly off another topic here), the issue I'm having is not being able to use the JOIN function that everybody is suggesting in order to prevent the ADMIN's characters from being listed as well.
Here's the code that I have on my website right now, it shows the rank number 1-30, character name and the level in a table. Here's it working on my website
<?php
require("srvcs/config.php");
$stmt = $dbh->prepare('SELECT * FROM chars ORDER BY CAST(experience AS UNSIGNED ) DESC LIMIT 30;');
$stmt->execute();
$result = $stmt->fetchAll();
$place = 1;
echo '<table class="justShowme" style="width:600px;height:150px;">
<tr>
<td>Rank</td>
<td>Character Name</td>
<td>Level</td>
</tr>';
foreach ($result as $index => &$item) {
$exp = floor(pow($item['experience'] + 1, 1/4));
$name = $item['name'];
echo '<tr>';
echo "<td><B>" . $place++ . "</B></td>";
echo "<td>" . $name . "</td>";
echo "<td>" . $exp . "</td>";
echo '</tr>';
}
echo '</table></center>';
?>
I'm not very familiar with MySQL, so I'll just start by listing out what I know is necessary...
'chars' table includes the character information
'sID' column is unique and matches the subscriber 'ID' column, whoever owns the character
'subscriber' table includes the account information and admin status
'ID' is the subscriber ID which the 'sID' from chars table refers to
'admin' is the admin status of the account as Y or N
If a character has an sID value of a subscriber ID with the admin value as Y, it should not be listed.
If the character has an sID value of a subscriber ID with the admin value N, it will be listed and the table is listed as DESC and only show 30 rows of results.
How would I go about doing this?
Any help would be greatly appreciated! This is my first post, so tips on future help requests would be nice too :) Thank you in advance!
SELECT tb1.*
FROM chars tb1
JOIN subscriber tb2
ON tb1.sID=tb2.ID
WHERE admin = 'N'
ORDER BY CAST(experience AS UNSIGNED ) DESC
LIMIT 30;
You could use a NOT IN subquery.
See below.
SELECT chars.*
FROM chars
WHERE sID NOT IN (SELECT ID FROM subscriber WHERE subscriber.admin = 'Y')
ORDER BY CAST(experience AS UNSIGNED ) DESC
LIMIT 30;
I have two tables. Player and Stats. In player is username and id. In stats is honor and id. IDs are same in both tables. One player, one id. I would like to order stats by honor and echo it together with username to the table.
Here is my try, but i can't do anything with order.
Player counter is count of player. +1 reason is that it starts from 2
$getPlayerCounter = mysql_query("SELECT `id` FROM `player`");
$playerCounter = mysql_num_rows($getPlayerCounter);
for ($i = 2; $i <= $playerCounter + 1; $i++) {
$username = mysql_query("SELECT `player`.*, `stats`.* FROM `player` INNER JOIN `stats` ON `player`.`id`=$i AND `stats`.`id`=$i") or die(mysql_error());;
$fetch = mysql_fetch_assoc($username);
echo "<tr>";
echo "<td>".$fetch['username']."</td>";
echo "<td>".$fetch['honor']."</td>";
echo "</tr>";
}
Without using your loop. How about a solution where you query a sorted list already? Kind of like:
SELECT * FROM player p
JOIN stats s on p.id = s.id
ORDER BY s.honor DESC
Where honor is the name of your column for the stats value (hopefully it's a column you can sort like a number).
You will get an array of rows that is ordered by the stats value in descending order (maximum value on top). Now you can just fetch row by row in the order it is in the fetched array.
You walk through the array doing this:
$result = mysql_query(the_query_above);
while ($row = mysql_fetch_assoc($result)) {
echo "<tr>";
echo "<td>".$row['username']."</td>";
echo "<td>".$row['honor']."</td>";
echo "</tr>";
}
Well I'm trying to get all the times that a String are repeated in two tables using a comun column and getting all the results with a While, like that:
$result = mysql_query("SELECT * FROM hits") or die(mysql_error());
while($row = mysql_fetch_assoc( $result ))
{
$result=mysql_query("SELECT COUNT(app) FROM info WHERE app='$row[page]'");
while ($row = mysql_fetch_array($result, MYSQL_NUM))
{
$toip = $row[0] ;
}
echo '<td bgcolor="#75D169">';
echo "$toip";
echo '</td>';
}
But it doesn't show it very well as you can see it here: http://ikillcraft.a0001.net/counter/view.php?pass=test
The output would be:
Instalaciones de 4 0
Instalaciones de IkillLauncher 3 3
Instalaciones de gogogoa 3 0
Instalaciones de MasterShell 0 0
But I don't know how to do it. :(
Using a single piece of SQL, something like this would do it:-
$result = mysql_query("SELECT a.page, COUNT(b.app) AS AppCount
FROM hits a
LEFT OUTER JOIN info b
ON a.page = b.app
GROUP BY a.page";
while($row = mysql_fetch_assoc( $result ))
{
echo '<td bgcolor="#75D169">';
echo $row['AppCount'];
echo '</td>';
}
Note I do not know your column names, and you should have all the non aggregate column names in the GROUP BY clause (ie, anything except the ones in the COUNT in this case)
I don't know your table structure, so I am doing guesswork here, but this should work:
SELECT hits.page, appgrouped.apphit
FROM (hits)
LEFT JOIN (SELECT COUNT(app) as apphit, app
FROM info
GROUP BY app) AS appgrouped ON appgrouped.app = hits.page
This would be quite slow - depending on how big:
SELECT COUNT(app) as apphit, app
FROM info
GROUP BY app
This is, if it is big, then the above join will be very slow, and in that case, PHP code would be a better solution (yes it would be faster).
Mysql query and PHP code that I'm using to get users from the database that meet certain criteria is:
$sql = mysql_query("SELECT a2.id, a2.name FROM members a2 JOIN room f ON f.myid = a2.id
WHERE f.user = 1 AND a2.status ='7' UNION SELECT a2.id, a2.name FROM members a2
JOIN room f ON f.user = a2.id WHERE f.myid = 1 AND a2.status ='7' GROUP BY id")
or die(mysql_error());
while ($r = mysql_fetch_array($sql))
{
$temp[] = '"'.$r[0].'"';
}
$thelist = implode(",",$temp);
The query that follows get the list of members with new galleries by using array from the previous query.
$ft = mysql_query("SELECT id, pic1 FROM foto WHERE id IN ($thelist) AND
pic1!='' ORDER BY date DESC LIMIT 10");
while ($f = mysql_fetch_array($ft))
{
echo $f['id']." - ".$f['pic1']."<br/>";
}
These queries working fine but I need to get the name for every user listed in second query. This data is in the first query in the column name. How can I get it listed beside '$f['id']." - ".$f['pic1']'?
While I might just alter the first query to pull the galleries at the same time, or change the second query to join and get the name, you could keep the same structure and change a few things:
In the loop after the first query when building $temp[], also build a lookup table of user id to user name:
$usernames[$r[0]] = $r[1];
Then in your output loop, use the id (assuming they are the same!) from the second query to call up the user name value you stored:
echo $f['id'] . " - " . $f['pic1'] . " - " . $usernames[$f['id']] . "<br/>";