Is it possible to reference a MySQL table dynamically? - php

I can't find anything about dynamically referencing a MySQL table entry in my particular case. Most everything I've read leans towards it not being possible, but I'm hoping someone can prove me wrong.
Essentially, I've got multiple MySQL tables that I'm trying to pull data from on an Android app. I want to access 2 at a time. The 1st Table's name always stays the same, history. The 2nd Table's name, however, may be different at times. It's value is determined within the app and referenced with :job in my php script (I'll use moon for my example). The 2nd table itself is generated dynamically through the app, so I guess I'm trying to set up a reference within a php script I have saved to a server so that I can access the 2nd Table.
Sorry for the confusing description, I hope these tables will help explain what I'm trying to get at.
Table #1: history (always stays the same)
| site | code | hours|
|---------|---------|------|
| moon | first | 1 |
| moon | second | 2 |
| moon | third | 3 |
| earth | fourth | 4 |
Table #2: moon (this one I want to dynamically reference)
| code | hours|
|---------|------|
| first | 10 |
| second | 11 |
| third | 12 |
And my current code:
...
/*** Table #1 ***/
SELECT code,
SUM(hours) AS total, '' AS target
FROM history
WHERE site = :job /* :job ends up being moon in this example */
GROUP BY code
UNION ALL
/*** Table #2 ***/
SELECT code,
'' AS total, SUM(hours) AS target
FROM :job /* <--- I'm trying to do something along these lines and use 'moon', or 'earth', or whatever... */
GROUP BY code
...
And later I get :job from the app: (moon)
$query_params = array(
':job' => $_POST['jobname'],
);
Result I'm Looking For: (works perfect if I directly use Table #2's name (ie moon) in my php file)
| code | hours|target|
|---------|------|------|
| first | 1 | 10 |
| second | 2 | 11 |
| third | 3 | 12 |
The code absolutely works as expected when I replace the :job in the 2nd table with the actual name of the table. I'm wondering if there is some way to still do it dynamically though?
Thanks for any and all advice!
I've done some pretty extensive searching and haven't come up with anything that works for me.
Is it possible to reference a mysql table entry value from a second table entry dynamically?
https://dev.mysql.com/doc/refman/5.7/en/derived-tables.html
MySQL table.* AS reference
Retrieve parent-child hierarchy from a self-referencing mysql table

Related

PHP: Parse column name values from MySQL query statements?

I'm trying to build audit logging for a website. What I want to do is allow existing MySQL queries to go in unchanged (if possible) to a PHP function which parses the data and stores it in the audit log table.
For example, if I have the query
UPDATE members SET name='Bob', age='40' WHERE memberId=123
then I'd like to be able to pull out the table name members, the rowId 123 and both column/data (as if key/value) pairs name:Bob and age:40.
If possible I'd like a solution which also allows for the alternative query format:
UPDATE members (name,age) VALUES ('Bob','40') WHERE memberId=123
These would then go into an audit log table which would look something like this:
+--------+---------+--------+-----+-----------+-----------+
| user | table | column | row | old_value | new_value |
+--------+---------+--------+-----+-----------+-----------+
| admin | members | name | 123 | Joe | Bob |
+--------+---------+--------+-----+-----------+-----------+
| admin | members | age | 123 | 32 | 40 |
+--------+---------+--------+-----+-----------+-----------+
Obviously to populate this table you can see why I need to extract the values. Ideally I'd like to do it from existing MySQL query strings, passed into my PHP function (to handle existing code), but if I need to implement new PHP functions I'm open to suggestions.
Regex seems both complicated and painful since I'd have to format it for each column name for every table. Is there any parsing solution for this problem out there?

Why does this query return different values?

I'm trying to pull information from my database, but the query I'm using is only pulling all of the values after the first one. I have no LIMIT set, but I did try setting a LIMIT 0,30 with no change. In phpMyAdmin, the query returns what I expect. In my PHP file, it returns what I've explained.
The query is :
SELECT * FROM `mainSite_others` WHERE forGame='$gameName'
gameName is previously provided, and I suspect no errors because it does return at least two values. The forGame value in the database is all the same, a constant "+Stellar+Dawn".
The PHP code is:
while ($gameOther = $database->fetchArray($gameOtherQry)) {
echo $gameOther['otherName'];
}
Don't worry about the $database->fetchArray part, that is just my DB class, which works fine as far as I know.
The table I am extracting from looks like this (this is with all the values contained):
id | forGame | otherType | otherName | otherDesc
9 | +Stellar+Dawn | Character | Car | Car
10 | +Stellar+Dawn | Item | Brugson Burson | a guy
11 | +Stellar+Dawn | Item | Space Pie | A pie from space
I am using mySQLi.
Any ideas? Thank you.
You're probably doing a fetch call BEFORE you reach the while loop, which "loses" the first row of the results.

MySQL: SELECT conditional statement with grouping based off of per record parameters possible?

I have a report I'm rewriting for an application using MySQL as the database. Currently, the report is using a lot of grunt work coming from php, which creates arrays, re-stores them into a temp database then generates results from that temp DB.
One of the main goals from rewriting a bulk of all this code is to simplify and clean a lot of my old code and am wondering whether the below process can be simplified, or even better done solely on MySQL to let php just handle the dstribution of the data to the client.
I will use a made up scenario to describe what I am attempting to do:
Let's assume the following table (please note in real app, this table's information is actually pulled from several tables, but this should get the point across for clarity):
+----+-----------+--------------+--------------+
| id | location | date_visited | time_visited |
+----+-----------+--------------+--------------+
| 1 | place 1 | 2012-04-20 | 11:00:00 |
+----+-----------+--------------+--------------+
| 2 | place 2 | 2012-04-20 | 11:06:00 |
+----+-----------+--------------+--------------+
| 3 | place 1 | 2012-04-20 | 11:06:00 |
+----+-----------+--------------+--------------+
| 4 | place 3 | 2012-04-20 | 11:20:00 |
+----+-----------+--------------+--------------+
| 5 | place 2 | 2012-04-20 | 11:21:00 |
+----+-----------+--------------+--------------+
| 6 | place 1 | 2012-04-20 | 11:22:00 |
+----+-----------+--------------+--------------+
| 7 | place 3 | 2012-04-20 | 11:23:00 |
+----+-----------+--------------+--------------+
The report I need requires me to first list each location and then the number of visits made to that place. However, the caveat and what makes the query difficult for me is that there needs to be a time interval met for the visit to count whithin this report.
For example: Let's say the interval between visits to any given place is 10 minutes.
The first entry is locked in automatically because there are no previous entries, and so is the second since there are no other entries for 'place 2' yet. However on the third entry, place 1 is checked for the last time it was visited, which was less than the interval defined (10 minutes), therefore the report would ignore this entry and move along to the next one.
In essence, we are checking on a case by case scenario where the time interval is not from the last entry, but from the last entry from the same location.
The results from the report should look something like this in the end:
+----+-----------+--------+
| id | location | visits |
+----+-----------+--------+
| 1 | place 1 | 2 |
+----+-----------+--------+
| 2 | place 2 | 2 |
+----+-----------+--------+
| 3 | place 3 | 1 |
+----+-----------+--------+
My current implementation on a basic level goes through the following steps to acquire the above result set:
MySQL query creates one temp table with a list of all the required locations and their ID.
MySQL query selects all the visit data whithin the specified time frame and passes it to PHP.
PHP & MySQL populate the temporary table with the visits data, PHP does the grunt work here.
MySQL selects data from temporary table and returns it to client for display.
My question is. Is there a way to do most of this with MySQL alone? What I've been trying to find is a way to write a MySQL query which can parse through the select statement and select only the visits which meet the above criteria and then finally groups it by location and provides me with a COUNT(*) of each group.
I really don't know if it's possible and am in hopes that one of the database gurus out there might be able to shed some light on how to do this.
Suppose you have a table (probably temporary) of a slightly different structure:
CREATE TABLE `visits` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`location` varchar(45) NOT NULL,
`visited` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `loc_vis` (`location`,`visited`)
) ENGINE=InnoDB;
INSERT INTO visits (location, visited) VALUES
('place 1', '2012-04-20 11:00:00'),
('place 2', '2012-04-20 11:06:00'),
('place 1', '2012-04-20 11:06:00'),
('place 3', '2012-04-20 11:20:00'),
('place 2', '2012-04-20 11:21:00'),
('place 1', '2012-04-20 11:22:00'),
('place 1', '2012-04-20 11:23:00');
which, as you see, has an index on (location,visited). Then the following query will use the index, that is read data in the order of the index, and return the results you expected:
SELECT
location,
COUNT(IF(#loc <> #loc:=location,
#vis:=visited,
IF(#vis + INTERVAL 10 MINUTE < #vis:=visited,
visited,
NULL))) as visit_count
FROM visits,
(SELECT #loc:='', #vis:=FROM_UNIXTIME(0)) as init
GROUP BY location;
Result:
+----------+-------------+
| location | visit_count |
+----------+-------------+
| place 1 | 2 |
| place 2 | 2 |
| place 3 | 1 |
+----------+-------------+
3 rows in set (0.00 sec)
Some explanation:
The key of the solution is that it fades out the functional nature of SQL, and uses MySQL implementation specifics (they say it is bad, never do it again!!!).
If a table has an index (an ordered representation of column values) and the index is used in a query, that means that the data from the table is read in the order of the index.
GROUP BY operation will benefit from an index (since the data is already grouped there) and will choose it if it is applicable.
All aggregating functions in SQL (except for COUNT(*) which has a special meaning) check each row, and use the value only if it is not NULL (the expression within COUNT above returns NULL for wrong conditions)
The rest is just a hacky representation of procedural iteration over a list of rows (which is read in the order of the index, that is ordered by location asc, visisted asc): I initialize some variables, if location differs from the previous row - I count it, if not - I check the interval and return NULL if it is wrong.
You can populate the temporary table using a INSERT / SELECT statement.
See manual. http://dev.mysql.com/doc/refman/5.0/en/insert-select.html
I'd use the GROUP BY in the SELECT statement to narrow down the places.
For the visits column that can be populated as a COUNT operation, and I think it might be possible to perform that as also part of the INSERT / SELECT.
See manual. http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html
So your SQL might look something like this.
INSERT INTO temp
SELECT * FROM (
SELECT *,COUNT('visits')
FROM source AS table1
GROUP BY location
WHERE date_visited > xxxx AND date_visited < xxxx
)
AS table2
Seriously, that is off the top of my head but it should give you some ideas on how SQL can be structured. But you likely can do the report using just one good query.

Is there a way to get nested data out of MySQL without using recursion?

So let us say that I have a menu system with all the navigation items stored in a MySQL table like so:
Table: Menu
-------------------------------------------------------
| id | title | url | parent_id |
-------------------------------------------------------
| 1 | Home | /home | 0 |
| 2 | About | /about | 0 |
| 3 | History | /about/history | 2 |
| 4 | Location | /about/location | 2 |
| 5 | Staff | /about/staff | 2 |
| 6 | Articles | /blog | 0 |
| 7 | Archive | /blog/archive | 6 |
| 8 | Tags | /blog/tags | 6 |
| 9 | Tag Name 1 | /blog/tags/tag-name-1 | 8 |
| 10 | Tag Name 2 | /blog/tags/tag-name-2 | 8 |
-------------------------------------------------------
As you can see this table is quite simple with the only complication being the self referencing column parent_id, which defines how the menu should be nested.
So this would produce the following menu:
- Home
- About
- History
- Location
- Staff
- Articles
- Archive
- Tags
- Tag Name 1
- Tag Name 2
Is there a way to get this structure from the aforementioned table without making use of a recursive function in PHP (but it could be Python, Java or any other language) that queries the database with each iteration?
Ideally this could be handled with one MySQL query. Perhaps the table structure needs to be changed to accommodate this - if so how?
You could pull all of it out in one single pull, and then work with it recursively in PHP. That way you save some of the query time, but gain a little scripting time.
I would do something like this:
Get all data, ordered by parent id
Put row into $data[$parent_id][]
define function to build menu, takes one param which is id
get $data[$id] and work with that array, building the array.
while looping through the items, check if size of $data[current-item-id] > 0
if so, call above function with 0 as param
This way, you only query the database once, but use a little more of the servers ram.
If you're fetching the whole tree and you can't or don't want to change the table structure, take a look at https://stackoverflow.com/a/8325451/4833
This can be done in sql query, take a look at this resource which explains recursion in a query
http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html.
MySQL don't have an default function to do that.
You can make an procedure with loop to get the data result you want, or create an function and use in your sql select.
Anyway you will use loop.
Example:
DROP PROCEDURE IF EXISTS famsubtree;
DELIMITER go
CREATE PROCEDURE famsubtree( root INT )
BEGIN
DROP TABLE IF EXISTS famsubtree;
CREATE TABLE famsubtree
SELECT childID, parentID, 0 AS level
FROM familytree
WHERE parentID = root;
ALTER TABLE famsubtree ADD PRIMARY KEY(childID,parentID);
REPEAT
INSERT IGNORE INTO famsubtree
SELECT f.childID, f.parentID, s.level+1
FROM familytree AS f
JOIN famsubtree AS s ON f.parentID = s.childID;
UNTIL Row_Count() = 0 END REPEAT;
E ND ;
go
DELIMITER ;
And use to query:
call famsubtree(1); -- from the root you can see forever
SELECT Concat(Space(level),parentID) AS Parent, Group_Concat(childID ORDER BY childID) AS Child
FROM famsubtree
GROUP BY parentID;

MYSQL Comma Delimited list, possible to add and remove values?

I have a comma delimited list that im storing in a varchar field in a mysql table.
Is it possible to add and remove values from the list directly using sql queries? Or do I have to take the data out of the table, manipulate in PHP and replace it back into mysql?
There is no way to do it in InnoDB and MyIsam engines in mysql. Might be in other engines (check CSV engine).
You can do it in a stored procedure, but, not recommended.
What you should do to solve such an issue is to refactor your code and normalize your DB =>
original table
T1: id | data | some_other_data
1 | gg,jj,ss,ee,tt,hh | abanibi
To become:
T1: id | some_other_data
1 | abanibi
T2: id | t1_id | data_piece
1 | 1 | gg
2 | 1 | jj
3 | 1 | ss
4 | 1 | ee
5 | 1 | tt
6 | 1 | hh
and if data_piece is a constant value in the system which is reused a lot, you need to add there a lookup table too.
I know it looks more work, but then it will save you issues like you have now, which take much more time to solve.

Categories