SQL MAX() in multiple query string - php

I have a string of 3 queries that are designed to
Find which messages have other messages with the same id which represents replies
Find which messages of the results from the first query have the specified user as entering the first message of that string of messages (min timestamp)
Find the latest message of that string of messages (max timestamp)
The problem comes with the third query. I get the expected results up to the second query, then when the third is executed, without the MAX(timestamp) as max, I get the expected results. When I add that, I only get the first message for each string of messages when it should be the last, regardless of whether I use min or max and the row count says 1 row returned when there is 2 rows shown. Anyone got any ideas on where I went wrong?
$sql="SELECT reply_chunk_id
FROM messages
GROUP BY reply_chunk_id
HAVING count(reply_chunk_id) > 1 ";
$stmt16 = $conn->prepare($sql);
$result=$stmt16->execute(array('specified_user'));
while($row = $stmt16->fetch(PDO::FETCH_ASSOC)){
$sql="SELECT user,reply_chunk_id, MIN(timestamp) AS grp_timestamp
FROM messages WHERE reply_chunk_id=?
GROUP BY reply_chunk_id HAVING user=?";
$stmt17 = $conn->prepare($sql);
$result=$stmt17->execute(array($row['reply_chunk_id'],'specified_user'));
while($row2 = $stmt17->fetch(PDO::FETCH_ASSOC)){
$sql="SELECT message, MAX(timestamp) as max FROM messages WHERE reply_chunk_id=?";
$stmt18 = $conn->prepare($sql);
$result=$stmt18->execute(array($row2['reply_chunk_id']));
while($row3 = $stmt18->fetch(PDO::FETCH_ASSOC)){
echo '<p>'.$row3['message'];
}
}
}
echo ' '.$stmt18->rowCount();
Create table view of messages, as requested:
CREATE TABLE IF NOT EXISTS `messages` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`timestamp` int(11) NOT NULL,
`user` varchar(25) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT 'anonimous',
`message` varchar(2000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`topic_id` varchar(35) NOT NULL,
`reply_chunk_id` varchar(35) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

Since message isn't grouped, exactly which message from the group you'll get isn't defined. If you want the message with the maximum timestamp, you'll need to explicitly select it:
SELECT message, timestamp AS max
FROM messages
WHERE reply_chunk_id=:rcid
AND timestamp=(SELECT MAX(timestamp)
FROM messages
WHERE reply_chunk_id=:rcid)
or:
SELECT message, timestamp AS max
FROM messages
WHERE reply_chunk_id=?
ORDER BY timestamp DESC, id
LIMIT 1
The second query breaks ties (in the very unlikely but possible situation that more than one person posts at the same time) by also selecting the message with the highest id.
General feedback
Many of the variables you set within the loops are invariant, and thus should be moved outside the loop.
$stmt17 will return at most 1 result. Moreover, $stmt18 will return always return exactly one result. Rewriting the second inner while loop (for $stmt17) as an if statement, and simply fetching the result from $stmt18 would be equivalent and clearer as to purpose.
try {
$threadSql="SELECT reply_chunk_id
FROM messages
GROUP BY reply_chunk_id
HAVING count(reply_chunk_id) > 1 ";
$firstUserSql="SELECT user, MIN(timestamp) AS grp_timestamp
FROM messages WHERE reply_chunk_id=?
GROUP BY reply_chunk_id HAVING user=?";
$lastMsgSql="SELECT message, MAX(timestamp) as max FROM messages WHERE reply_chunk_id=?";
$threadQuery = $conn->prepare($threadSql);
$threadQuery->setFetchMode(PDO::FETCH_ASSOC);
$firstUserQuery = $conn->prepare($firstUserSql);
$lastMsgQuery = $conn->prepare($lastMsgSql);
$result=$threadQuery->execute(array('specified_user'));
foreach ($threadQuery AS $thread){
$result=$firstUserQuery->execute(array($thread['reply_chunk_id'],'specified_user'));
if (FALSE !== ($firstUser = $firstUserQuery->fetch(PDO::FETCH_ASSOC))) {
$result=$lastMsgQuery->execute(array($thread['reply_chunk_id']));
$lastMsg = $lastMsgQuery->fetch(PDO::FETCH_ASSOC);
echo '<p>'.$lastMsg['message'].'</p>';
}
}
echo ' ' . $lastMsgQuery->rowCount();
} catch (PDOException $exc) {
...
}
Lastly, a single SQL statement can replace much of the PHP code:
SELECT mchunk.reply_chunk_id,
muser.user, MIN(muser.`timestamp`) AS grp_timestamp,
mmax.message, mmax.`timestamp` AS max
FROM messages AS mchunk
JOIN messages AS muser
ON mchunk.reply_chunk_id = muser.reply_chunk_id
JOIN messages AS mmax
ON mchunk.reply_chunk_id = mmax.reply_chunk_id
WHERE mmax.timestamp=(SELECT MAX(timestamp) FROM messages AS m WHERE m.reply_chunk_id=mchunk.reply_chunk_id)
GROUP BY mchunk.reply_chunk_id, muser.user
HAVING count(mchunk.reply_chunk_id) > 1
AND muser.user IN ('steve', '0010')
;
This selects all threads started by a specified user that have responses, along with the most recent response.

Related

How to use mysql JOIN in CodeIgniter?

So I have same problems with my query. I havent use JOIN so far so I am pretty sure I have something wrong with it but not sure. I have tried this for hours and can not understand - It seems that code is correct but I do not get desired results.
Here are my two tables:(wrote them by hand so If there is some error dont bother)
CREATE TABLE messages (
msg_id INT(11) PRIMARY KEY,
sender_id INT(11),
msg text,
conversation_id INT(11),
seen datetime,
)
CREATE TABLE conversations (
conversation_id INT(11) PRIMARY KEY,
user1_id INT(11),
user2_id INT(11),
requested datetime,
)
I am working now on notification module. I want to get list of conversation_id where user with $user_id have at least one unread message so that I could notify to him that you have new messages in conversation #:
public function getMsgNotifications($user_id)
{
$this->db->select('conversations.conversation_id');
$this->db->distinct('conversations.conversation_id');
$this->db->from('conversations');
$this->db->join('messages','conversations.conversation_id = messages.conversation_id','INNER');
$this->db->where('conversations.user1_id', $user_id);
$this->db->or_where('conversations.user2_id', $user_id);
$this->db->where('messages.sender_id !=', $user_id);
$this->db->where('messages.seen', '');
$query = $this->db->get();
return $query->result();
}
However I get absolutelly random results. I have spent hours looking at values in database and trying to understand problem but the results I get has no patern.
Would appreciate if someone could point out where there are mistakes in my query.

PHP mySQL Time Between Rows

Hello I am stuck on this. Looking to be able to pull the time between 2 start/stop rows from a mySQL database. My table looks like this
fillercdown_ndx | time | B3_4_5
1 | 2016-06-16 14:59:45 | 0
2 | 2016-06-16 15:03:11 | 1
Basically when its recorded as 0 the machine stopped and when the record is 1 the machine restarted. I need to be able to calculate the amount of time the machine was down between certain times like 8AM-5PM. Was going to use PHP to display it upon users time entered, but have no idea on the SQL command.
Anyone know the best way to be able to find this?
Create table
CREATE TABLE `fillercdown` (
`fillercdown_ndx` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime DEFAULT NULL,
`B3_4_5` int(11) DEFAULT NULL,
PRIMARY KEY (`fillercdown_ndx`),
KEY `fillercdowntimendx` (`time`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1
UPDATE:
There will be hundreds of these "start" and "stop" entries. My end all goal would be in php to give the user a small form asking them to provide a time range like 8AM-5PM and then be able to calculate all the time where the machine was "stopped" (which is when B3_4_5 is at 0). I just can't seem to figure out the right SQL call to get the time differences between each 0 and 1 and add them together between a set time range
This is what I am currently using to do the same thing it sounds like you're trying to do. I'm using PDO but this could be adapted fairly easily for mysqli if you need to use that. This depends on alternating off/on values by time. If there are any consecutive off or on rows for whatever reason, the expected result becomes ambiguous.
// First select everything in the requested range ordered by time.
$sql = "SELECT `time`, B3_4_5 FROM your_table WHERE `time` BETWEEN ? AND ? ORDER BY `time`";
$stmt = $pdo->prepare($sql);
$stmt->execute([$query_start, $query_end]);
// Initialize two DateTime objects (start and end) used to calculate the total time.
$total_start = new DateTime('00:00');
$total_end = clone $total_start;
$off = null;
while ($row = $stmt->fetchObject()) {
if ($row->B3_4_5) {
$on = new DateTime($row->time);
// increment total end time with difference between current off/on times
if ($off) {
$total_end->add($on->diff($off));
}
} else {
$off = new DateTime($row->time);
}
}
// total downtime is difference between start and end DateTimes
$total_downtime = $total_start->diff($total_end);
$total_downtime is a DateInterval object. You can get return a message using its format method:
echo $total_downtime->format('Total downtime: %h hours, %i minutes, %s seconds.');
This is the basic idea... It selects the result into a single row andcolumn, which you can then fetch withPHP`...
This solution assumes that stops & starts come in pairs, i.e: the ID of a start will be +1 that of a stop. Otherwise you should SELECT/JOIN the ID that is > that of the stopped one, and LIMIT it to 1.
A self join might not yield optimal performance wise, so be careful and measure execution times with some data to be on the safe side.
http://sqlfiddle.com/#!9/de72bf/1
CREATE TABLE fillercdown (
`fillercdown_ndx` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime DEFAULT NULL,
`B3_4_5` int(11) DEFAULT NULL,
PRIMARY KEY (`fillercdown_ndx`),
KEY `fillercdowntimendx` (`time`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
INSERT INTO fillercdown
(`time`, `B3_4_5`)
VALUES
('2016-06-16 14:00:45', 0),
('2016-06-16 14:01:00', 1),
('2016-06-16 16:00:00', 0),
('2016-06-16 16:01:00', 1)
;
SELECT SUM(TIMEDIFF(g.`time`, f.`time`))
FROM fillercdown f
INNER JOIN fillercdown g
ON g.`fillercdown_ndx` = (f.`fillercdown_ndx` + 1)
AND g.`B3_4_5` = 1
AND TIME(g.`time`) BETWEEN '08:00:00' AND '17:00:00'
WHERE f.`B3_4_5` = 0
AND TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00'
If you want to include times where the machine stopped but has not yet restarted, you can do something like this:
http://sqlfiddle.com/#!9/d113b9/9
INSERT INTO fillercdown
(`time`, `B3_4_5`)
VALUES
('2016-06-16 14:00:45', 0),
('2016-06-16 14:01:00', 1),
('2016-06-16 16:00:00', 0),
('2016-06-16 16:01:00', 1),
('2016-06-16 16:02:00', 0)
;
SELECT SUM(TIMEDIFF(COALESCE(g.`time`, '2016-06-16 16:02:01'), f.`time`))
FROM fillercdown f
LEFT JOIN fillercdown g
ON g.`fillercdown_ndx` = (f.`fillercdown_ndx` + 1)
AND g.`B3_4_5` = 1
AND TIME(g.`time`) BETWEEN '08:00:00' AND '17:00:00'
WHERE TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00' AND f.`B3_4_5` = 0
You could replace 2016-06-16 16:02:01 with NOW() or something based on f.time, it depends on your application needs of course.
If you never want to get NULL but rather 0, if there are no matching rows, then do something like: COALESCE(SUM(...), 0).
If you prefer a scripted solution you can always do something like this:
SELECT f.`time`, f.`B3_4_5`
FROM fillercdown f
WHERE TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00'
And then compute the sum, like so (psuedocode):
stopped = null;
pairs = []
for ( row in rows )
if ( row.isStopped ) stopped = row
else
pairs += [stopped, row]
stopped = null
sum = 0
for ( pair in pairs )
sum += duration( pair[0], pair[1] )

MySQL procedures - incrementally recalculate rows

I have a quite trivial task of calculating budget entries (income/outcome/balance). There can be thousands of entries and I can change any of them in the middle. As the result, all later entries balance must be recalculated.
Right now I am doing it in PHP by iterating through array of all entries, and updating rows that changed. It takes too much time that way - my server stops responding for several minutes.
I suppose that it happens because PHP calls MySQL for every entry update, though for PHP itself this task of array iteration and recalculation is pretty cheap. I think that there must be a way to throw this task at MySQL, so it does the iteration/recalculation/update itself, which might be cheap as well.
I am not an expert in MySQL at all, but I heard that there are stored procedures that might be the cure.
Here is my MySQL (5.5.33) table:
CREATE TABLE `entry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`is_income` tinyint(1) NOT NULL DEFAULT '0',
`income` decimal(20,2) DEFAULT NULL,
`outcome` decimal(20,2) DEFAULT NULL,
`balance` decimal(20,2) DEFAULT NULL,
PRIMARY KEY (`id`)
)
Here is my PHP (5.3.27):
//...
//$DB is a class for operating DB
$entries = $DB->get_all('entry'); //retrieves all entries from 'entry' table, sorted by date
$balance = 0;
foreach ($entries as $e) {
if ($e['is_income']) {
$balance += $e['income'];
} else {
$balance -= $e['outcome'];
}
if ($balance <> $e['balance']) {
$e1 = $e;
$e1['balance'] = $balance;
$DB->update('entry', $e1); //update the row by doing query('UPDATE `entry` ... WHERE id=' . $entry_id);
}
}
Can you point me the right direction? Thanks!
I think you can do this in a single SQL UPDATE query, no procedure needed.
UPDATE entry AS e1
JOIN (SELECT * FROM entry ORDER BY date) AS e2 ON e1.id = e2.id
CROSS JOIN (SELECT #balance := 0) AS var
SET e1.balance = (#balance := #balance + IF(e2.is_income, e2.income, -e2.outcome))
The user variable #balance serves the same purpose as the PHP variable $balance. The subquery is needed because MySQL doesn't allow use of ORDER BY in a multi-table UPDATE query, so you need to join with a subquery that produces the IDs in date order.
The "proper" way is to do the summation when displaying the report, and not store it in the table.
For only "thousands", it should not be a performance problem.

PHP: multiple SELECT COUNT with

I am making multiple count using the same parameter. I would like to know if there is a way of doing only one request (to be more efficient)?
My COUNT are as follows:
// Get number of MEMBERS in roster of this roster manager
$stmt = $mysqli->prepare("SELECT DISTINCT COUNT(rm.id_membre)
FROM roster_par_membre rm
JOIN roster_par_membre rm2
WHERE rm.id_roster = rm2.id_roster
AND rm2.level = 1
AND rm2.id_membre = ?");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_membre);
$stmt->fetch();
// Get number of Alerts for this roster manager
$stmt = $mysqli->prepare("SELECT COUNT(id_alerte)
FROM alerte
WHERE modified_by = ?");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_alerts);
$stmt->fetch();
// Get number of Rosters for this roster manager
$stmt = $mysqli->prepare("SELECT COUNT(id_roster)
FROM roster_par_membre
WHERE id_membre = ?
AND level = 1");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_rosters);
$stmt->fetch();
If you don't want to keep the 3 queries separate, you can create a separate table to handle these statistics:
CREATE TABLE `statistics` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`members_in_roster` int(11) DEFAULT '0',
`number_of_alerts` int(11) DEFAULT '0',
`number_of_roster_per_manager` int(11) DEFAULT '0',
`id_membre` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
You'll link these numbers to a id_membre.
Also, you can use MySQL Triggers to update these numbers automatically to avoid doing 3 queries. You'll have to use one query to get all the results you need.
For instance, you can use the following trigger every time the Alerts table is updated:
DELIMITER $$
CREATE TRIGGER before_alerts_update
BEFORE UPDATE ON alerts
FOR EACH ROW BEGIN
UPDATE statistics
SET number_of_alerts = IFNULL(number_of_alerts, 0) + 1
WHERE modified_by = NEW.id_membre;
END$$
DELIMITER ;
If you want to keep the calculations in PHP instead of using MySQL triggers, it's not wrong. Keep in mind that using multiple triggers might slow down your database's performance. For instance, in the example mentioned above, every time the "alerts" table is updated, an UPDATE query is triggered. If you don't need to have these statistics numbers in real-time, this solution is an "overkill". It'll be better if you do the query once the user requests these numbers.

get columns default values as they are a row of a resultset

is there a way to get default values of columns as they are a row of a resultset?
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`state` tinyint(2) NOT NULL DEFAULT '22',
`pubdate` datetime NOT NULL DEFAULT '2012-01-01 00:00:00',
for instance a table like this should return this record:
id->NULL (?)
state->22
pubdate->2012-01-01 00:00:00
in practice, when some user opens edit.php?id=44 he will get the row 44 (update mode), but if he opens edit.php?id=0 (insert mode) I want that the fields contain default values as place holders
thank you in advance
There is a DEFAULT function
SELECT DEFAULT( id ) , DEFAULT( EXAMPLE ) FROM test LIMIT 1
With above query, it seems that you need to have atleast one record in the table as it returns no records otherwise. For current timestamp, it return a timestamp formatted string of 0s.
Sure, using the information_schema database (which stores all the information about your database structure), you can do something like:
SELECT
COLUMN_NAME,
COLUMN_DEFAULT
TABLE_NAME
FROM information_schema.COLUMNS
WHERE
TABLE_NAME='your_table_name'
AND TABLE_SCHEMA='your_database_name'
If you have a limited number of columns, you can collect them into a row using a construct like:
SELECT
id.defaultval AS id_default,
state.defaultval AS state_default,
pubdate.defaultval AS pubdate_default
FROM
(SELECT TABLE_NAME, COLUMN_DEFAULT AS defaultval FROM information_schema.COLUMNS WHERE TABLE_NAME='your_table' AND TABLE_SCHEMA='your_database' AND COLUMN_NAME='id') id
JOIN (SELECT COLUMN_DEFAULT AS defaultval FROM information_schema.COLUMNS WHERE TABLE_NAME='your_table' AND TABLE_SCHEMA='your_database' AND COLUMN_NAME='state') state ON id.TABLE_NAME = state.TABLE_NAME
JOIN (SELECT COLUMN_DEFAULT AS defaultval FROM information_schema.COLUMNS WHERE TABLE_NAME='your_table' AND TABLE_SCHEMA='your_database' AND COLUMN_NAME='pubdate') pubdate ON id.TABLE_NAME = pubdate.TABLE_NAME
Use DESCRIBE http://dev.mysql.com/doc/refman/5.0/en/describe.html
DESCRIBE sometable [somefield]
Here is php example for single field:
$resource = mysql_query("DESCRIBE sometable somefield");
$schema = mysql_fetch_assoc($resource);
$default = $schema['default'];
And here is the php example for few fields:
$resource = mysql_query("DESCRIBE sometable");
while ($schema = mysql_fetch_assoc($resource)) {
$default_list[$schema['Field']] = $schema['Default'];
}
I see no use for such a behavior and find it wrong.
It is not convenient to use. Imagine I want to enter my own state value. I'd have to delete default 22 first.
Even worse with date. Instead of setting current datetime, you are going to make me edit whole date. Why?
And for the id it is just impossible.
Why can't you just check the input fields and if empty - not to insert at all, letting database set these defaults
You just overthinked it, I believe.

Categories