Better way to find the latest change in sql value - php

I have 2 tables, one of which lists items, the other of which keeps track of when items changed in price.
price_table:
price_id int
item_id int
price_amount float
change_date date // this date is stored as Y-m-d, without a time
and
item_table:
item_id int
item_name char(40)
item_description char(40)
I know that the following finds the item_ids changed at the current date:
SELECT item_id
FROM price_table
WHERE change_date = "$today"
when
$today = date('Y-m-d');
I then run another query for EACH item_id returned (this is why I think my method is inefficient)
SELECT card_price
FROM price_table
WHERE item_id
ORDER BY change_date ASC
And get the last 2 values from the PHP array, comparing them to get the difference in price. My test had an inner join to return the item_name as well, but I've removed that to simplify the question. Is there a better way to do this? And if so, can it be expanded to use the last 2 or 3 days as the change criteria instead?

This is what triggers are for, I'm also going to talk about DATE_SUB and date intervals
Using triggers will allow you to keep the current price in the price table and keep historical prices in a separate table.
For instance, consider creating a new table called price_table_old
price_table_old:
price_id int
item_id int
price_amount float
change_date date
On the price_table, create an 'update' trigger...
insert into price_table_old ( `item_id`, `price_amount` , `change_date ` )
VALUES ( OLD.`item_id` , OLD.`price_amount` , NOW() )
To help you with trigger syntax, I copied the trigger I used for a staff table below
When ever someone changes a price, the price_table_old table is automatically updated.
From here, things are a little more logical to get the current price and comparisons of previous prices.
You could run a query like....
SELECT
price_table.item_id ,
price_table.price_amount ,
(price_table.price_amount - price_table_old.price_amount ) AS pricediff ,
price_table_old.change_date
FROM
price_table
LEFT JOIN
price_table_old ON price_table_old.item_id = price_table.item_id
ORDER BY price_table.item_id , price_table_old.change_date DESC
or stick a where in there to zone in on a specific item and/or date range and/or a limit to get the last (say) 3 price changes.
For instance, to get a list of price changes for the last 3 days on all items, you could use a statement like this
SELECT
price_table.item_id ,
price_table.price_amount ,
(price_table.price_amount - price_table_old.price_amount ) AS pricediff ,
price_table_old.change_date
FROM
price_table
LEFT JOIN
price_table_old ON price_table_old.item_id = price_table.item_id
WHERE
price_table_old.change_date > ( DATE_SUB( NOW() , INTERVAL 3 DAY ))
ORDER BY price_table.item_id , price_table_old.change_date DESC
Here is my staff table trigger...
CREATE TRIGGER `Staff-CopyOnUpdate` BEFORE UPDATE ON `staff`
FOR EACH ROW insert into staff_old
( `sid` , `title`, `firstname` , `surname` , `type`,
`email` , `notify`, `actiondate`, `action` )
VALUES
( OLD.`sid` , OLD.`title`, OLD.`firstname` , OLD.`surname` , OLD.`type`,
OLD.`email` , OLD.`notify`, NOW() , 'updated' )

Related

Order rows by column whilst keeping rows with a common column together

I'm running MariaDB 5.5 which is equivalent to MySQL 5.5.
I have the following test data.
CREATE TABLE `dev_test` (
`id` INT NOT NULL AUTO_INCREMENT ,
`date` DATE NOT NULL ,
`venue` INT NOT NULL ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
INSERT INTO `dev_test` (`id`, `date`, `venue`) VALUES (NULL, '2019-08-01', '2'),
(NULL, '2019-09-01', '1'), (NULL, '2019-10-01', '2');
INSERT INTO `dev_test` (`id`, `date`, `venue`) VALUES (NULL, '2019-11-01', '3');
I wish to order the venues and the events so that the venue of the next event is first, then all other events at that venue. Then the next event that's not all ready listed.
So with this data I want:
Event ID 1 - 2019-08-01 Venue 2
Event ID 3 - 2019-10-01 Venue 2
Event ID 2 - 2019-09-01 Venue 1
Event ID 4 - 2019-11-01 Venue 3
I could just grab all the events in any order then order them using PHP.
Or I could select with
SELECT venue FROM `dev_test` GROUP BY venue ORDER BY date;
Then using PHP get the venues one at a time ordered by date
SELECT * FROM `dev_test` WHERE venue = 2 ORDER BY date;
SELECT * FROM `dev_test` WHERE venue = 1 ORDER BY date;
But is there a nice way in pure MySQL (MariaDB) to do this?
Maybe some way of giving all venues a temp date column that is the same as the earliest date at that venue?
Or should I just do it in PHP?
Sorry for the title gore. I tried to make "Order events by date whilst keeping events at the same location together" more generic.
Try this query, I think this helps you
SELECT t2.* FROM
(SELECT venue FROM dev_test GROUP BY venue ORDER BY date) AS t1
LEFT JOIN (SELECT * FROM dev_test ORDER BY date) AS t2 ON t1.venue=t2.venue
Here result
You can use window functions with CTEs to achieve this (note: Requires MySQL 8+:
WITH added_initial_date
AS (SELECT id,
date,
venue,
FIRST_VALUE(date)
OVER(
PARTITION BY venue
ORDER BY date) AS 'initial_date'
FROM dev_test),
ranked
AS (SELECT id,
date,
venue,
initial_date,
RANK()
OVER(
ORDER BY initial_date ASC) AS position
FROM added_initial_date)
SELECT id,
date,
venue
FROM ranked
ORDER BY position ASC, date ASC;
Maybe it is not most effective, but it works.
UPDATE: added ordering by date to get closest record first. Here is db-fiddle: https://www.db-fiddle.com/f/puYqhDF53ocRiRys61XH53/0
You can try below - using order by venue desc, date asc
SELECT * FROM `dev_test`
order by venue desc, date asc
E.g. :
SELECT a.*
FROM dev_test a
LEFT
JOIN (SELECT * FROM dev_test ORDER BY date DESC LIMIT 1) b
ON b.venue = a.venue
ORDER
BY b.id IS NULL, date DESC;
This assumes dates are UNIQUE, as per the sample data

PHP / MySQL nth row with DATE_SUB, etc

So I have a table, I use to graph some data.
id, agcid, ooscount, date
Data is samples every 5 minutes for 18 objects
ID is auto increment, agcid represents the ID of the object, date is self explanitory and ooscount is an integer column.
I'm currently using this:
$sql2 = "SELECT ooscount, date
FROM ooscount
where agcid = '".$agcid."' AND date >= DATE_SUB(NOW( ),INTERVAL 7 HOUR)
order by date DESC";
Simple, yet effective, however I have an extremely large dataset which complicates the graphs.
I tried adding AND ooscount.id mod 3 = 0 to this however, the data sets on each agcid seemed very random, some had only a couple, others had 5x more than they should.
I then tried an approach like this:
$sql2 = "set #row:=-1;
SELECT ooscount.*
FROM
ooscount
INNER JOIN
(
SELECT id
FROM
(
SELECT #row:=#row+1 AS rownum, id
FROM
(
SELECT id, date, ooscount FROM ooscount where agcid = '".$agcid."' and date >= DATE_SUB(NOW( ),INTERVAL 12 HOUR) order by date DESC
) AS sorted
) as ranked
WHERE rownum % 3 = 0
) AS subset
ON subset.id = ooscount.id;";
It's commented out, forgive the //'s. However this approach while it works in phpMyAdmin, gives me this:
PHP Fatal error: Call to a member function fetch_assoc()
on a non-object in /var/www/khamlin/oosagc.php on line 145
Lastly, the portion of code to display this:
$result2 = $conn->query($sql2);
$rowsarray = "";
//var_dump($result2);
while ($row2 = $result2->fetch_assoc()) {
I believe the problem is that examples I've found don't expect multiple data sets on a single table, and thus ID would be incremental (ie: 1,2,3,4,5,6, etc), rather than ( 168006, 168023, 168041, 168060 ).
How would I go about changing this to suit my needs here?
Structure
CREATE TABLE IF NOT EXISTS `ooscount` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`agcid` int(3) NOT NULL,
`ooscount` int(10) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=168580 ;
````
Sample Data: https://jpst.it/1bfFt
Current Output: a data point of ooscount and date every 5 minutes (ie:
data: [{"ooscount":"4907","date":"2018-02-21 09:40:01"},{"ooscount":"4905","date":"2018-02-21 09:35:02"},{"ooscount":"4910","date":"2018-02-21 09:30:02"},{"ooscount":"4904","date":"2018-02-21 09:25:01"},{"ooscount":"4900","date":"2018-02-21 09:20:02"},{"ooscount":"4898","date":"2018-02-21 09:15:02"},{"ooscount":"4989","date":"2018-02-21 09:10:01"},{"ooscount":"5008","date":"2018-02-21 09:05:02"},{"ooscount":"4933","date":"2018-02-21 09:00:01"},{"ooscount":"4915","date":"2018-02-21 08:55:01"},{"ooscount":"4903","date":"2018-02-21 08:50:02"},{"ooscount":"4898","date":"2018-02-21 08:45:01"},{"ooscount":"4899","date":"2018-02-21 08:40:01"},{"ooscount":"4909","date":"2018-02-21 08:35:01"},{"ooscount":"4918","date":"2018-02-21 08:30:01"},{"ooscount":"4906","date":"2018-02-21 08:25:02"},{"ooscount":"4906","date":"2018-02-21 08:20:01"},{"ooscount":"4900","date":"2018-02-21 08:15:02"},{"ooscount":"4895","date":"2018-02-21 08:10:01"},{"ooscount":"4883","date":"2018-02-21 08:05:02"},{"ooscount":"4881","date":"2018-02-21 08:00:01"},{"ooscount":"4879","date":"2018-02-21 07:55:02"},{"ooscount":"4883","date":"2018-02-21 07:50:02"},{"ooscount":"4875","date":"2018-02-21 07:45:02"},{"ooscount":"4875","date":"2018-02-21 07:40:01"},{"ooscount":"4879","date":"2018-02-21 07:35:02"},{"ooscount":"4882","date":"2018-02-21 07:30:01"},{"ooscount":"4888","date":"2018-02-21 07:25:01"},{"ooscount":"4877","date":"2018-02-21 07:20:01"},{"ooscount":"4879","date":"2018-02-21 07:15:02"},{"ooscount":"4884","date":"2018-02-21 07:10:02"},{"ooscount":"4886","date":"2018-02-21 07:05:02"},{"ooscount":"4904","date":"2018-02-21 07:00:02"},{"ooscount":"4906","date":"2018-02-21 06:55:01"},{"ooscount":"4904","date":"2018-02-21 06:53:32"},{"ooscount":"4904","date":"2018-02-21 06:50:02"},{"ooscount":"4892","date":"2018-02-21 06:45:01"},{"ooscount":"4795","date":"2018-02-21 06:40:02"},{"ooscount":"4793","date":"2018-02-21 06:35:01"},{"ooscount":"4799","date":"2018-02-21 06:30:02"},{"ooscount":"4797","date":"2018-02-21 06:25:02"},{"ooscount":"4802","date":"2018-02-21 06:20:01"},{"ooscount":"4801","date":"2018-02-21 06:15:01"},{"ooscount":"4792","date":"2018-02-21 06:10:02"},{"ooscount":"4798","date":"2018-02-21 06:05:02"},{"ooscount":"4797","date":"2018-02-21 06:00:01"},{"ooscount":"4798","date":"2018-02-21 05:55:02"},{"ooscount":"4794","date":"2018-02-21 05:50:01"},{"ooscount":"4796","date":"2018-02-21 05:45:02"},{"ooscount":"4804","date":"2018-02-21 05:40:02"},{"ooscount":"4803","date":"2018-02-21 05:35:01"},{"ooscount":"4809","date":"2018-02-21 05:30:02"},{"ooscount":"4811","date":"2018-02-21 05:25:01"},{"ooscount":"4810","date":"2018-02-21 05:20:01"},{"ooscount":"4831","date":"2018-02-21 05:15:02"},{"ooscount":"4839","date":"2018-02-21 05:10:02"},{"ooscount":"4859","date":"2018-02-21 05:05:01"},{"ooscount":"4859","date":"2018-02-21 05:00:01"},{"ooscount":"4858","date":"2018-02-21 04:55:02"},{"ooscount":"4858","date":"2018-02-21 04:50:02"},{"ooscount":"4863","date":"2018-02-21 04:45:02"},{"ooscount":"4868","date":"2018-02-21 04:40:01"},{"ooscount":"4872","date":"2018-02-21 04:35:01"},{"ooscount":"4868","date":"2018-02-21 04:30:02"},{"ooscount":"4867","date":"2018-02-21 04:25:02"},{"ooscount":"4870","date":"2018-02-21 04:20:01"},{"ooscount":"4866","date":"2018-02-21 04:15:02"},{"ooscount":"4864","date":"2018-02-21 04:10:02"},{"ooscount":"4863","date":"2018-02-21 04:05:01"},{"ooscount":"4874","date":"2018-02-21 04:00:02"},{"ooscount":"4881","date":"2018-02-21 03:55:01"},{"ooscount":"4850","date":"2018-02-21 03:50:02"},{"ooscount":"4846","date":"2018-02-21 03:45:01"},{"ooscount":"4847","date":"2018-02-21 03:40:02"},{"ooscount":"4850","date":"2018-02-21 03:35:02"},{"ooscount":"4845","date":"2018-02-21 03:30:01"},{"ooscount":"4847","date":"2018-02-21 03:25:02"},{"ooscount":"4848","date":"2018-02-21 03:20:01"},{"ooscount":"4847","date":"2018-02-21 03:15:02"},{"ooscount":"4852","date":"2018-02-21 03:10:03"},{"ooscount":"4854","date":"2018-02-21 03:05:01"},{"ooscount":"4864","date":"2018-02-21 03:00:01"},{"ooscount":"4867","date":"2018-02-21 02:55:02"},{"ooscount":"4868","date":"2018-02-21 02:50:01"},{"ooscount":"4862","date":"2018-02-21 02:45:02"}],
The goal is to have less data points over the same amount of time.
I found a solution that works for me.
$sql2 = "SELECT *
FROM (
SELECT #row := #row +1 AS rownum, ooscount.*
FROM (
SELECT #row :=0) r, ooscount where agcid='".$agcid."' and date >= DATE_SUB(NOW( ),INTERVAL 12 HOUR)
) ranked
WHERE rownum %3 =1";

PHP Mysql display necessary records from two table

I have two tables, brp_consume_log and brp_transfer_log, I joined both table with query:
SELECT *
FROM brp_consume_log a
JOIN brp_transfer_log b ON b.acct_id = a.acct_id
WHERE a.acct_id ='1657'
result output as:
I would like to show the results in below format:
is it possible?
UPDATE
I've been able to use query below to output results that I want:
SELECT
datetime,
remark,
intake,
consume
FROM
(
SELECT datetime, remark, points_intake intake, NULL consume, 1 ord
FROM brp_transfer_log
WHERE acct_id = '1657'
UNION ALL
SELECT datetime, remark, NULL, point_consume, 2
FROM brp_consume_log
WHERE acct_id = '1657'
) q
ORDER BY datetime DESC, ord
By the way, how do I add another field for balance points and do necessary subtraction or addition from last balance points?
Please advise. thanks!

Echo SQL data in order of date added using Union All

So I am working with a client to implement a similar system as the "badges and privileges system" on StackExchange. Although in her system, she is looking to use points and rewards for her staff. It's the same basic principle. The users are rewarded points for good team work and gain rewards from these points. I thought it would be handy to add the same kind of feature which SE uses to display these in the top nav bar, where it shows your rep and badges in order of the date you have earned either of them. This is my issue, I have found help retrieving the data together from the two separate tables but am not sure how I would display these results in order of date earned? As an example:
User ID #1 has earned 50 points on 18/12/2015 would be in ap_user_points table
User ID #1 has earned 'The Gift Voucher' reward on '17/12/2015'
If I simply:
echo $row8['reward'] . $row8['points_added']
It would echo as:
The Gift Voucher 50
Where I need it in order by date as:
50
The Gift Voucher
If you look at your rep and badge icon in the nav bar you'll see what I'm getting at here, it's a similar system.
<?php
$user_id = $_SESSION['userid'];
$sql8 = "
SELECT r.reward_id,
r.user_id,
r.reward as reward,
r.date_earned as date_earned,
r.badge_desc,
NULL AS points_added,
NULL AS added_for,
NULL AS date_added
FROM ap_user_rewards as r
WHERE r.user_id = '$user_id'
UNION ALL
SELECT NULL,
NULL,
NULL,
NULL,
NULL,
p.points_added AS points_added,
p.added_for AS added_for,
p.date_added AS date_added
FROM ap_user_points as p
WHERE p.user_id = '$user_id' ORDER BY date_earned DESC, date_added DESC;";
$result8 = $conn->query($sql8);
if ($result8->num_rows > 0) {
// output data of each row
while($row8 = $result8->fetch_assoc()) {
////// NOT SURE WHAT TO ECHO HERE?
}
}
?>
Add another column to the result set. In that new column, populate it from both queries... looks like it would be the date_added expression in the first query and the date_earned expression in the second query. When those are in the same column, then ordering is easy. (This also assumes that these expressions are of the same or compatible datatypes, preferably DATE, DATETIME or TIMESTAMP.)
Then you can order by ordinal position, e.g. ORDER BY 2 to order by the second column in the resultset.
SELECT a1
, b1
, NULL
, NULL
, a1 AS sortexpr
FROM ...
UNION ALL
SELECT NULL
, NULL
, x2
, y2
, x2 AS sortexpr
FROM ...
ORDER BY 5 DESC
That's just one possibility. If you can't add an extra column, to line up the expressions from the two queries, then you need a way to discriminate which query is returning the row. I typically include a literal as a discriminator column.
Then you can use implicit-style UNION syntax, wrapping the queries in parens...
( SELECT 'q1' AS `source`
, a1
, b1 AS date_earned
, NULL
, NULL AS date_added
FROM ...
)
UNION ALL
( SELECT 'q2' AS `source`
, NULL
, NULL AS date_earned
, x2
, y2 AS date_added
FROM ...
)
ORDER BY IF(`source`='q1',date_earned,date_added) DESC
Followup
I may have misunderstood the question. I though the question was how to get the rows from a UNION/UNION ALL returned in a particular order.
Personally, I would write the query to include a discriminator column, and then line up the columns as much as I could, so they would be processed the same.
As an example:
SELECT 'reward' AS `source`
, r.date_earned AS `seq`
, r.user_id AS `user_id`
, r.date_earned AS `date_earned`
, r.reward_id
, r.reward
, r.badge_desc
, NULL AS `points_added`
, NULL AS `added_for`
FROM r ...
UNION ALL
SELECT 'points' AS `source`
, p.date_added AS `seq`
, p.user_id AS `user_id`
, p.date_added AS `date_earned`
, NULL
, NULL
, NULL
, p.points_added AS `points_added`
, p.added_for AS `added_for`
FROM p ...
ORDER BY 2 DESC, 1 DESC
(It's probably not really necessary to return user_id, since we already know what the value will be. I've returned it here to demonstrate how the columns from the two resultsets can be "lined up".)
Then, when I fetched the rows...
if ( $row8['source'] == 'points' ) {
# process columns from a row of 'points' type
echo $row8['badge_desc'];
echo $row8['user_id'];
} elsif ( $row8['source'] == 'reward' ) {
# process columns from a row of 'reward' type
echo $row8['added_for'];
echo $row8['user_id'];
}
That's how I would do it.

SQL position of row(ranking system) WITHOUT same rank for two records

so I'm trying to create a ranking system for my website, however as a lot of the records have same number of points, they all have same rank, is there a way to avoid this?
currently have
$conn = $db->query("SELECT COUNT( * ) +1 AS 'position' FROM tv WHERE points > ( SELECT points FROM tv WHERE id ={$data['id']} )");
$d = $db->fetch_array($conn);
echo $d['position'];
And DB structure
`id` int(11) NOT NULL,
`name` varchar(150) NOT NULL,
`points` int(11) NOT NULL,
Edited below,
What I'm doing right now is getting records by lets say
SELECT * FROM tv WHERE type = 1
Now I run a while loop, and I need to make myself a function that will get the rank, but it would make sure that the ranks aren't duplicate
How would I go about making a ranking system that doesn't have same ranking for two records? lets say if the points count is the same, it would order them by ID and get their position? or something like that? Thank you!
If you are using MS SQL Server 2008R2, you can use the RANK function.
http://msdn.microsoft.com/en-us/library/ms176102.aspx
If you are using MySQL, you can look at one of the below options:
http://thinkdiff.net/mysql/how-to-get-rank-using-mysql-query/
http://www.fromdual.ch/ranking-mysql-results
select #rnk:=#rnk+1 as rnk,id,name,points
from table,(select #rnk:=0) as r order by points desc,id
You want to use ORDER BY. Applying on multiple columns is as simple as comma delimiting them: ORDER BY points, id DESC will sort by points and if the points are the same, it will sort by id.
Here's your SELECT query:
SELECT * FROM tv WHERE points > ( SELECT points FROM tv WHERE id ={$data['id']} ) ORDER BY points, id DESC
Documentation to support this: http://dev.mysql.com/doc/refman/5.0/en/sorting-rows.html
Many Database vendors have added special functions to their products to do this, but you can also do it with straight SQL:
Select *, 1 +
(Select Count(*) From myTable
Where ColName < t.ColName) Rank
From MyTable t
or to avoid giving records with the same value of colName the same rank, (This requires a key)
Select *, 1 +
(Select Count(Distinct KeyCol)
From myTable
Where ColName < t.ColName or
(ColName = t.ColName And KeyCol < t.KeyCol)) Rank
From MyTable t

Categories