I have a database table for fuel with the following details
The fID is the fuel id, vID is the vehicle id, volume is the amount of fuel in litres, price is the price of fuel per litre, date is the date of filling up the vehicle, type is the type/grade of fuel.
I wanted to make a query that would return the cost of fuel by multiplying the cost of fuel per litre with the volume of fuel filled up, plus another field for distance which should be the difference between the previous fill up meter and the latest meter. In the table, there will be a lot of cars, so I just want to return records of a specific vehicle (the vID will be repeated but the fID is unique).
I have the following query so far but it returns empty if there's only one fuel entry in the database, plus I can't figure out how to calculate the cost in Mysql.
Select
t1.*, t2.meter - t1.meter as distance
From fuel t1 left join fuel t2
on t1.date > t2.date
where t1.vID = t2.vID
order by t1.date desc
How should I go about it the right way?
Here is the schema:
CREATE TABLE IF NOT EXISTS `fuel` (
`fID` int(11) NOT NULL AUTO_INCREMENT,
`vID` int(11) DEFAULT NULL,
`volume` varchar(100) DEFAULT NULL,
`price` varchar(100) DEFAULT NULL,
`meter` varchar(100) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`vendorID` int(11) DEFAULT NULL,
`notes` text,
`type` varchar(50) DEFAULT NULL,
PRIMARY KEY (`fID`)
) ENGINE = MyISAM DEFAULT CHARSET = latin1;
INSERT INTO `fuel` (`fID`, `vID`, `volume`, `price`, `meter`, `date`, `vendorID`, `notes`, `type`)
VALUES
(7, 28, '15', '800', '5000', '2018-05-27 05:53:00', NULL, 'Entry number one for this vehicle', 'Unleaded'),
(6, 27, '5', '1000', '2000', '2018-05-27 05:50:00', NULL, 'This is the second fill up for this vehicle', 'Leaded'),
(5, 27, '15', '1200', '1200', '2018-05-27 04:58:00', NULL, 'Hey there vendors!', 'Leaded'),
(9, 26, '25', '750', '4500', '2018-05-27 05:57:00', NULL, NULL, 'Leaded'),
(10, 26, '20', '750', '6000', '2018-05-27 05:58:00', NULL, NULL, 'Leaded');
This is how I want to output the data that will be returned. This picture takes an example of getting all fuel history logs for a vehicle vID 27. The first entry should return distance 0. The second one should subtract its current meter reading from the previous record that also has a vID of 27 (here its 800)... Any ideas how I can achieve this?
Following Nick's answer, I implemented the following in PHP, but it throws an error. However, when I run it in mysql sql command it returns the results as expected...
$vID = 27;
$pdo = $this -> con -> query("
select date_format(f1.date, '%y-%m-%d %H:%i:%s') as date,
f1.meter as mileage,
case when f2.meter is null then 0
else f1.meter - f2.meter end as distance,
f1.volume, f1.volume * f1.price as cost from fuel f1
left join fuel f2
on f2.date = (select max(date)
from fuel where fuel.vID = f1.vID and fuel.date < f1.date)
where f1.vID = ? order by f1.date ");
if($pdo -> execute([$vID]) && $pdo -> rowCount()) {
$res = $pdo -> fetchAll(5);
$this -> response($this -> json($res), 200); // send fuel logs
} else {
$this -> response('', 204); // If no records "No Content" status
}
Here is the error I get after executing the code through php.
This query will give you the individual rows that you want. The query works by joining fuel to itself using the latest fill-up date for this vehicle prior to the current fill-up date. If there is no prior fill-up date, the CASE expression produces a 0 result for distance.
SELECT DATE_FORMAT(f1.date, '%y-%m-%d %H:%i:%s') AS date,
f1.meter AS mileage,
CASE WHEN f2.meter IS NULL THEN 0
ELSE f1.meter - f2.meter
END AS distance,
f1.volume,
f1.volume * f1.price AS cost
FROM fuel f1
LEFT JOIN fuel f2
ON f2.date = (SELECT MAX(date)
FROM fuel
WHERE fuel.vID = f1.vID AND fuel.date < f1.date)
WHERE f1.vID = 27
ORDER BY f1.date
Output:
date mileage distance volume cost
18-05-27 04:58:00 1200 0 15 18000
18-05-27 05:50:00 2000 800 5 5000
Demo
If you don't want to sum the rows in PHP, the query can produce a summary row with a minor change to the query (adding an aggregation function and a GROUP BY WITH ROLLUP clause):
SELECT DATE_FORMAT(f1.date, '%y-%m-%d %H:%i:%s') AS date,
f1.meter AS mileage,
CASE WHEN f2.meter IS NULL THEN 0
ELSE f1.meter - f2.meter
END AS distance,
f1.volume,
SUM(f1.volume * f1.price) AS cost
FROM fuel f1
LEFT JOIN fuel f2
ON f2.date = (SELECT MAX(date)
FROM fuel
WHERE fuel.vID = f1.vID AND fuel.date < f1.date)
WHERE f1.vID = 27
GROUP BY f1.date WITH ROLLUP
Output:
date mileage distance volume cost
18-05-27 04:58:00 1200 0 15 18000
18-05-27 05:50:00 2000 800 5 5000
(null) 2000 800 5 23000
You can detect the summary row in PHP by the fact that the date column is null. Demo
Nicks solution will work, but the given join condition is a sub-query which runs for every join. You can also try this one, for the expected result.
select f3.date, f3.meter, f3.cost, f3.volume, f3.price, min(f3.distance) as distance from (
select f1.date,
f1.meter,
case when f2.`meter` is NULL then 0
else f1.meter - f2.meter end
as distance,
(f1.price * f1.volume) as cost,
f1.`volume`,
f1.`price`,
f1.fID
from fuel f1
left join fuel f2
on f1.vid = f2.vid and f1.`date` > f2.date
where f1.vid = 27
group by f1.fID, distance
order by date, distance asc) f3
group by f3.fID;
#Nick thanks for the feedback. Hope this query will work.
OK SQL can also achieve what you wanted, but it is more intuitive through store procedure. (i will also mention that controller and view i.e. PHP and jquery could also achive what you want but much more hard that way. In jquery case; mysql just needs to return that vehcle recordset.)
Here is the code
-- Proceuodo code
-- create a temporary table
-- set next_mileage = 0;
-- set pre_mileage = 0;
-- get a recordset of the desire vehicle
-- now loop through this recordset based on mileage
-- if pre_mileage = 0 then pre_mileage = mileage(i);
-- set next_mileage = mileage(i);
-- calculate distance (by subtracting next_mileage from previous_mileage) and other fields
DELIMITER ##
DROP PROCEDURE get_vehicle_info ##
CREATE PROCEDURE get_vehicle_info
(IN vid INT)
BEGIN
DECLARE v_vID BIGINT;
DECLARE v_volume BIGINT;
DECLARE v_price BIGINT;
DECLARE v_meter BIGINT;
DECLARE v_date datetime;
DECLARE done INT DEFAULT FALSE;
DECLARE cur1 CURSOR FOR SELECT vID, volume, price, meter, `date` FROM vehicle_records;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
DROP TEMPORARY TABLE IF EXISTS vehcle_info;
CREATE TEMPORARY TABLE vehcle_info (`Date` datetime, Mileage BIGINT, Distance BIGINT, Volume BIGINT, Cost BIGINT);
SET #sqlStr = CONCAT('CREATE OR REPLACE view vehicle_records as SELECT vID, volume, price, meter, `date` FROM fuel WHERE vID = ', vid, ' ORDER BY meter');
PREPARE stmt FROM #sqlStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #pre_mileage = 0;
SET #next_mileage = 0;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO v_vID, v_volume, v_price, v_meter, v_date;
IF done THEN
LEAVE read_loop;
END IF;
IF #pre_mileage = 0 THEN
SET #pre_mileage = v_meter;
END IF;
SET #next_mileage = v_meter;
INSERT INTO vehcle_info VALUES (v_date, v_meter, #next_mileage - #pre_mileage, v_volume, v_price * v_volume);
SET #pre_mileage = v_meter;
END LOOP;
CLOSE cur1;
SELECT * FROM vehcle_info;
DROP VIEW IF EXISTS vehicle_records;
DROP TEMPORARY TABLE vehcle_info;
END ##
DELIMITER ;
CALL get_vehicle_info(27);
Related
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";
Below is my column
1-1
1-2
2-1
2-4
Is It possible to sort in this way - First Column should be be ascending and Second column should be descending
So the result will be
1-4
1-2
2-1
2-1
I don't care if the row mismatch. How can I achieve this (Something like view)
CREATE TABLE IF NOT EXISTS `pipo_orders` (
`ClientID` int(8) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT ,
);
--
-- Dumping data for table `pipo_orders`
--
INSERT INTO `pipo_orders` (`ClientID`, `created_at`, `updated_at`) VALUES
(17, '2014-11-26 16:21:36', '2014-11-26 10:51:36'),
(17, '2014-11-26 16:46:24', '2014-11-26 11:16:24'),
(17, '2014-12-04 16:45:28', '2014-12-04 11:15:28'),
(17, '2014-12-26 12:58:13', '2014-12-26 07:28:13'),
(17, '2014-12-30 14:29:31', '2014-12-30 08:59:31'),
(17, '2014-12-30 14:31:05', '2014-12-30 09:01:05'),
(17, '2015-01-02 12:20:54', '2015-01-02 06:50:54');
If this is what you want it's weird, and I can't think of a reason why you'd want it. In fact, it goes against the fundamental logic of a database...
Nevertheless:
SELECT t1.ClientID
, t1.created_at
, t2.updated_at
FROM pipo_orders t1
JOIN pipo_orders t2
ON 1 = 1
ORDER BY t1.created_at ASC
, t2.update_at DESC
That looks like a horrible thing to do. The values created_at and updated_at are related by being in the same row. You override this relation and join completely unrelated values.
However to technically do this, you would have to order the two columns separately (giving them row numbers) and then join them again:
select
col1.created_at,
col2.updated_at
from
(
select created_at, #rn1 := #rn1 + 1 as rownum
from pipo_orders po
cross join (select #rn1 := 0) as r
order by created_at
) as col1
from
(
select updated_at, #rn2 := #rn2 + 1 as rownum
from pipo_orders po
cross join (select #rn2 := 0) as r
order by updated_at desc
) as col2 using (rownum)
order by rownum;
This post is taking a substantial amount of time to type because I'm trying to be as clear as possible, so please bear with me if it is still unclear.
Basically, what I have are a table of posts in the database which users can add privacy settings to.
ID | owner_id | post | other_info | privacy_level (int value)
From there, users can add their privacy details, allowing it to be viewable by all [privacy_level = 0), friends (privacy_level = 1), no one (privacy_level = 3), or specific people or filters (privacy_level = 4). For privacy levels specifying specific people (4), the query will reference the table "post_privacy_includes_for" in a subquery to see if the user (or a filter the user belongs to) exists in a row in the table.
ID | post_id | user_id | list_id
Also, the user has the ability to prevent some people from viewing their post in within a larger group by excluding them (e.g., Having it set for everyone to view but hiding it from a stalker user). For this, another reference table is added, "post_privacy_exclude_from" - it looks identical to the setup as "post_privacy_includes_for".
My problem is that this does not scale. At all. At the moment, there are about 1-2 million posts, the majority of them set to be viewable by everyone. For each post on the page it must check to see if there is a row that is excluding the post from being shown to the user - this moves really slow on a page that can be filled with 100-200 posts. It can take up to 2-4 seconds, especially when additional constraints are added to the query.
This also creates extremely large and complex queries that are just... awkward.
SELECT t.*
FROM posts t
WHERE ( (t.privacy_level = 3
AND t.owner_id = ?)
OR (t.privacy_level = 4
AND EXISTS
( SELECT i.id
FROM PostPrivacyIncludeFor i
WHERE i.user_id = ?
AND i.thought_id = t.id)
OR t.privacy_level = 4
AND t.owner_id = ?)
OR (t.privacy_level = 4
AND EXISTS
(SELECT i2.id
FROM PostPrivacyIncludeFor i2
WHERE i2.thought_id = t.id
AND EXISTS
(SELECT r.id
FROM FriendFilterIds r
WHERE r.list_id = i2.list_id
AND r.friend_id = ?))
OR t.privacy_level = 4
AND t.owner_id = ?)
OR (t.privacy_level = 1
AND EXISTS
(SELECT G.id
FROM Following G
WHERE follower_id = t.owner_id
AND following_id = ?
AND friend = 1)
OR t.privacy_level = 1
AND t.owner_id = ?)
OR (NOT EXISTS
(SELECT e.id
FROM PostPrivacyExcludeFrom e
WHERE e.thought_id = t.id
AND e.user_id = ?
AND NOT EXISTS
(SELECT e2.id
FROM PostPrivacyExcludeFrom e2
WHERE e2.thought_id = t.id
AND EXISTS
(SELECT l.id
FROM FriendFilterIds l
WHERE l.list_id = e2.list_id
AND l.friend_id = ?)))
AND t.privacy_level IN (0, 1, 4))
AND t.owner_id = ?
ORDER BY t.created_at LIMIT 100
(mock up query, similar to the query I use now in Doctrine ORM. It's a mess, but you get what I am saying.)
I guess my question is, how would you approach this situation to optimize it? Is there a better way to set up my database? I'm willing to completely scrap the method I have currently built up, but I wouldn't know what to move onto.
Thanks guys.
Updated: Fix the query to reflect the values I defined for privacy level above (I forgot to update it because I simplified the values)
Your query is too long to give a definitive solution for, but the approach I would follow is to simply the data lookups by converting the sub-queries into joins, and then build the logic into the where clause and column list of the select statement:
select t.*, i.*, r.*, G.*, e.* from posts t
left join PostPrivacyIncludeFor i on i.user_id = ? and i.thought_id = t.id
left join FriendFilterIds r on r.list_id = i.list_id and r.friend_id = ?
left join Following G on follower_id = t.owner_id and G.following_id = ? and G.friend=1
left join PostPrivacyExcludeFrom e on e.thought_id = t.id and e.user_id = ?
(This might need expanding: I couldn't follow the logic of the final clause.)
If you can get the simple select working fast AND including all the information needed, then all you need to do is build up the logic in the select list and where clause.
Had a quick stab at simplifying this without re-working your original design too much.
Using this solution your web page can now simply call the following stored procedure to get a list of filtered posts for a given user within a specified period.
call list_user_filtered_posts( <user_id>, <day_interval> );
The whole script can be found here : http://pastie.org/1212812
I haven't fully tested all of this and you may find this solution isn't performant enough for your needs but it may help you in fine tuning/modifying your existing design.
Tables
Dropped your post_privacy_exclude_from table and added a user_stalkers table which works pretty much like the inverse of user_friends. Kept the original post_privacy_includes_for table as per your design as this allows a user restrict a specific post to a subset of people.
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
username varbinary(32) unique not null
)
engine=innodb;
drop table if exists user_friends;
create table user_friends
(
user_id int unsigned not null,
friend_user_id int unsigned not null,
primary key (user_id, friend_user_id)
)
engine=innodb;
drop table if exists user_stalkers;
create table user_stalkers
(
user_id int unsigned not null,
stalker_user_id int unsigned not null,
primary key (user_id, stalker_user_id)
)
engine=innodb;
drop table if exists posts;
create table posts
(
post_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
privacy_level tinyint unsigned not null default 0,
post_date datetime not null,
key user_idx(user_id),
key post_date_user_idx(post_date, user_id)
)
engine=innodb;
drop table if exists post_privacy_includes_for;
create table post_privacy_includes_for
(
post_id int unsigned not null,
user_id int unsigned not null,
primary key (post_id, user_id)
)
engine=innodb;
Stored Procedures
The stored procedure is relatively simple - it initially selects ALL posts within the specified period and then filters out posts as per your original requirements. I have not performance tested this sproc with large volumes but as the initial selection is relatively small it should be performant enough as well as simplifying your application/middle tier code.
drop procedure if exists list_user_filtered_posts;
delimiter #
create procedure list_user_filtered_posts
(
in p_user_id int unsigned,
in p_day_interval tinyint unsigned
)
proc_main:begin
drop temporary table if exists tmp_posts;
drop temporary table if exists tmp_priv_posts;
-- select ALL posts in the required date range (or whatever selection criteria you require)
create temporary table tmp_posts engine=memory
select
p.post_id, p.user_id, p.privacy_level, 0 as deleted
from
posts p
where
p.post_date between now() - interval p_day_interval day and now()
order by
p.user_id;
-- purge stalker posts (0,1,3,4)
update tmp_posts
inner join user_stalkers us on us.user_id = tmp_posts.user_id and us.stalker_user_id = p_user_id
set
tmp_posts.deleted = 1
where
tmp_posts.user_id != p_user_id;
-- purge other users private posts (3)
update tmp_posts set deleted = 1 where user_id != p_user_id and privacy_level = 3;
-- purge friend only posts (1) i.e where p_user_id is not a friend of the poster
/*
requires another temp table due to mysql temp table problem/bug
http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html
*/
-- the private posts (1) this user can see
create temporary table tmp_priv_posts engine=memory
select
tp.post_id
from
tmp_posts tp
inner join user_friends uf on uf.user_id = tp.user_id and uf.friend_user_id = p_user_id
where
tp.user_id != p_user_id and tp.privacy_level = 1;
-- remove private posts this user cant see
update tmp_posts
left outer join tmp_priv_posts tpp on tmp_posts.post_id = tpp.post_id
set
tmp_posts.deleted = 1
where
tpp.post_id is null and tmp_posts.privacy_level = 1;
-- purge filtered (4)
truncate table tmp_priv_posts; -- reuse tmp table
insert into tmp_priv_posts
select
tp.post_id
from
tmp_posts tp
inner join post_privacy_includes_for ppif on tp.post_id = ppif.post_id and ppif.user_id = p_user_id
where
tp.user_id != p_user_id and tp.privacy_level = 4;
-- remove private posts this user cant see
update tmp_posts
left outer join tmp_priv_posts tpp on tmp_posts.post_id = tpp.post_id
set
tmp_posts.deleted = 1
where
tpp.post_id is null and tmp_posts.privacy_level = 4;
drop temporary table if exists tmp_priv_posts;
-- output filtered posts (display ALL of these on web page)
select
p.*
from
posts p
inner join tmp_posts tp on p.post_id = tp.post_id
where
tp.deleted = 0
order by
p.post_id desc;
-- clean up
drop temporary table if exists tmp_posts;
end proc_main #
delimiter ;
Test Data
Some basic test data.
insert into users (username) values ('f00'),('bar'),('alpha'),('beta'),('gamma'),('omega');
insert into user_friends values
(1,2),(1,3),(1,5),
(2,1),(2,3),(2,4),
(3,1),(3,2),
(4,5),
(5,1),(5,4);
insert into user_stalkers values (4,1);
insert into posts (user_id, privacy_level, post_date) values
-- public (0)
(1,0,now() - interval 8 day),
(1,0,now() - interval 8 day),
(2,0,now() - interval 7 day),
(2,0,now() - interval 7 day),
(3,0,now() - interval 6 day),
(4,0,now() - interval 6 day),
(5,0,now() - interval 5 day),
-- friends only (1)
(1,1,now() - interval 5 day),
(2,1,now() - interval 4 day),
(4,1,now() - interval 4 day),
(5,1,now() - interval 3 day),
-- private (3)
(1,3,now() - interval 3 day),
(2,3,now() - interval 2 day),
(4,3,now() - interval 2 day),
-- filtered (4)
(1,4,now() - interval 1 day),
(4,4,now() - interval 1 day),
(5,4,now());
insert into post_privacy_includes_for values (15,4), (16,1), (17,6);
Testing
As I mentioned before I've not fully tested this but on the surface it seems to be working.
select * from posts;
call list_user_filtered_posts(1,14);
call list_user_filtered_posts(6,14);
call list_user_filtered_posts(1,7);
call list_user_filtered_posts(6,7);
Hope you find some of this of use.
I have a table with 4 columns: place_id, username, counter, last_checkin
I'm writing a check-in based system and I'm trying to get a query that will give me the "mayor" of each place. The mayor is the one with most check-ins, and if there is more than 1 than the minimum last_checkin wins.
For example, if I have:
place_id, username, counter, last_checkin
123, tom, 3 , 13/4/10
123, jill, 3, 14/4/10
365, bob, 2, 15/4/10
365, alice, 1, 13/4/10
I want the result to be:
123, tom
365, bob
I'm using it in PHP code
Here is the test data:
CREATE TABLE `my_table` ( `place_id` int(11), `username` varchar(50), `counter` int(11), `last_checkin` date);
INSERT INTO `my_table` VALUES (123,'tom',3,'2010-04-13'),(123,'jill',3,'2010-04-14'),(365,'bob',2,'2010-04-15'),(365,'alice',1,'2010-04-13');
How about..
SELECT
place_id,
(SELECT username
FROM my_table MT2
WHERE MT2.place_id = MT1.place_id
ORDER BY counter DESC, last_checkin ASC
LIMIT 1) AS mayor
FROM my_table MT1
GROUP BY place_id;
Edited as Unreason suggests to have ascending order for last_checkin.
Brian's correlated query is something I would write. However I found this different take and it might perform differently depending on the data
SELECT
mt1.place_id,
mt1.username
FROM
my_table mt1 LEFT JOIN my_table mt2
ON mt1.place_id = mt2.place_id AND
(mt1.counter < mt2.counter OR
(mt1.counter = mt2.counter AND mt1.last_checkin > mt2.last_checkin)
)
WHERE
mt2.place_id IS NULL
Which uses left join to get to the top records according to certain conditions.
$data = query("SELECT max(counter) counter,username FROM table GROUP By place_id ORDER By last_checkin DESC");
I have a mysql table with items in relation to their order.
CREATE DATABASE IF NOT EXISTS `sqltest`;
USE `sqltest`;
DROP TABLE IF EXISTS `testdata`;
CREATE TABLE `testdata` (
`orderID` varchar(10) DEFAULT NULL,
`itemID` varchar(10) DEFAULT NULL,
`qtyOrdered` int(10) DEFAULT NULL,
`sellingPrice` decimal(10,2) DEFAULT NULL
)
INSERT INTO `testdata`(`orderID`,`itemID`,`qtyOrdered`,`sellingPrice`)
values ('1','a',1,'7.00'),('1','b',2,'8.00'),('1','c',3,'3.00'),('2','a',1,'7.00'),('2','c',4,'3.00');
Intended Result:
A = (1+1)2
B = 2
C = (2+4)6 <- most popular
How do I add up all the qty's for each item and result the highest one?
It should be fairly strait forward but I'm new to SQL and I can't work this one out :S
Solution needs to be mysql and or php.
I guess there needs to be some sort of temporary tally variable for each item ID,
but that seems like it could get messy with too many items.
ANSWER:
(thanks nuqqsa)
SELECT itemID, SUM(qtyOrdered) AS total FROM testdata GROUP BY itemID ORDER BY total DESC LIMIT 1;
How about this:
SELECT itemID, SUM(qtyOrdered) AS total FROM testdata GROUP BY itemID ORDER BY total DESC;
SELECT itemID, SUM(qtyOrdered) as blah FROM sqltest GROUP BY itemID ORDER BY blah DESC should do it
SELECT *
FROM testdata
ORDER BY SUM(gtyOrdered) DESC
GROUP BY itemID
SELECT SUM( qtyOrdered ) AS sum_ordered, itemID
FROM testdata
GROUP BY itemID
ORDER BY sum_ordered
select count(qtyOrdered), qtyOrdered from testdata group by qtyOrdered