SELECT rows WHERE next n rows satisfy CONDITIONS - php

Let's say we have this resource availability table:
+-----------+-----------------------------------------------------------+
| date | obvious, the date |
| timeslot | we have 12 fixed 2-hour timeslots so this will be 1 to 12 |
| r1 | number of resource type 1 available during this timeslot |
| r2 | same, for resource type 2 |
| r3 | same, for resource type 3 |
+-----------+-----------------------------------------------------------+
Now, I want to see all the available timeslots I can use to do job #43. For this job I need 2 units of r1, one unit of r2, and three units of r3. Assuming the job will need one timeslot I can use this query:
SELECT `date`, `timeslot` FROM `resource_availability`
WHERE
`r1` > '1' AND
`r2` > '0' AND
`r3` > '2'
ORDER BY 'date`, `timeslot`;
However, if I have another job, job #86 which takes 3 timeslots to complete and could not be stopped-restarted, then is it possible to get safe start times with a query?
I am currently checking continuity in my while loop, but thought it might be possible to have the query do that.
If that is possible, I would like to know which is quicker and more efficient. For efficacy evaluation, it should be noted that this table, being a sort of bitmap gets updated quite frequently - i.e. with each job being scheduled the resource availability columns get updated.
Also, it is fairly obvious the purpose of this system is to allow examining what-ifs. If my approach is not optimal, what better alternatives there are?
Should the last question be one too many, please ignore it, or let me know in the comments and I'd delete it.

Whew... I put together an idea that may get you what you want. Forgive me if it takes a bit to understand, but I hope you see that it's actually a fairly straightforward solution to a moderately complex problem.
I would build the query (in PHP) to have n self-joins where n is the number of time slots needed for the job. The self-joins join the next consecutive timeslot, and the results are thinned based on resources being available in all the slots. Note that you could move the dynamically-created WHERE clauses into the JOIN conditions... I have seen versions of MySQL which will improve speed that way.
php code:
// $r1, $r3, and $r3 are the required resources for this job.
$join_format = 'JOIN timeslots AS %s ON %date = %s.date AND %s.timeslot+1 = %s.timeslot';
$where_format = '(%s.r1 >= '.$r1.' AND %s.r2 >= '.$r2.' AND %s.r3 >= '.$r3.')';
$joins = array();
$wheres = array("block1.date > CURDATE()",
sprintf($where_format, "block1", "block1", "block1")
);
$select_list = 'block1.date, block1.timeslot as starting_time, block' . $slots_needed . '.timeslot as ending_time';
for($block = 2; $block <= $slots_needed; $block++) {
$join_alias = "block" . $block;
$previous_alias = "block" . ($block-1);
$joins[] = sprintf($join_format, $join_alias, $previous_alias,$join_alias, $previous_alias, $join_alias);
$wheres[] = sprintf($where_format, $join_alias, $join_alias, $join_alias);
}
$query_format = 'SELECT %s FROM timeslots as block1 %s WHERE %s GROUP BY block1.date, block1.timeslot ORDER BY block1.date ASC, block1.timeslot ASC';
$joins_string = implode(' ', $joins);
$wheres_string = implode(' AND ', $wheres);
$query = sprintf($query_format, $select_list, $joins_string, $wheres_string);
To the best of my intention, that should yield a query like this (for 2 needed blocks with 1 each of the resources needed:
resultant SQL:
SELECT
block1.date,
block1.timeslot as starting_time,
block2.timeslot as ending_time
FROM
timeslots AS block1
JOIN timeslots AS block2
ON block1.date = block2.date AND block1.timeslot+1 = block2.timeslot
WHERE
block1.date > CURDATE()
AND (block1.r1 >= 1 AND block1.r2 >= 1 AND block1.r3 >= 1)
AND (block2.r1 >= 1 AND block2.r2 >= 1 AND block2.r3 >= 1)
GROUP BY
block1.date, block1.timeslot
ORDER BY
block1.date ASC, block1.timeslot ASC
and it should yield results such as:
expected result set:
+------------+---------------+-------------+
| date | starting_time | ending_time |
+------------+---------------+-------------+
| 2001-01-01 | 1 | 2 |
+------------+---------------+-------------+
| 2001-01-01 | 2 | 3 |
+------------+---------------+-------------+
| 2001-01-01 | 7 | 8 |
+------------+---------------+-------------+
| 2001-01-01 | 8 | 9 |
+------------+---------------+-------------+
| 2001-01-02 | 4 | 5 |
+------------+---------------+-------------+
Notice that if there are 2 blocks needed, but 3 available (consecutively), the query will return both options (the first and second OR the second and third available times).

Related

Repeat query when conditions aren't met

I am making a kindof quiz. The quiz has 15 questions, for the quiz I need 5 questions of quiztype '1', 5 of quizType '2' and 5 of quizType '3'. Right now I'm counting quiztype '1'and quiztype '2' trough a loop and if conditions outside the loop aren't met, I get 15 new entry's and repeat the loop. I'm wondering, is there a better way to do this inside my query instead of using 2 objects?
This Is my code:
public function checkVariety($quizType, $data)
{
$i=0;
$i2=0;
foreach($quizType as $type) {
if ($type=='1') {
$i++;
}
if ($type=='2') {
$i2++;
}
}
if($i=='5' AND $i2=='5') {
$this->startQuiz($data);
return true;
} else {
$this->getRandom();
return false;
}
}
public function getRandom()
{
$stmt = $this->db->prepare("
SELECT id, quiz_type
FROM quiz
ORDER BY rand()
LIMIT 15
");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$quizType[] = $row['quiz_type'];
$data[] = $row['id'];
}
$this->checkVariety($quizType, $data);
return true;
}
You could also combine this way.
The UNION was easily avoided by noting the difference in the SELECT statements was just to pick form values 1, 2, and 3. In SQL, this is easily done with form IN (1, 2, 3).
The problem with this is we can't easily use LIMIT 5, as you originally did, since all 15 rows are now in the same result.
This is where window functions comes into play. We can now process these rows using window specifications to isolate and operate on groups (by partition) of rows.
The example below is ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq.
In short, this derives a new column (see: derived column), the contents of which is the position (row number) of this row within the group of rows with a matching form value (indicated in the PARTITION BY terms) and in the order specified by the ORDER BY terms of the OVER clause.
Your requirement is complicated slightly by the needed random order. It's not as easy to see how this window function use provides this nice row number ordering. You can test this by replacing the rand() term with something more recognizable ORDER BY exercise, which is the column I chose to represent some exercise identifier.
The WITH clause or Common Table Expression - CTE term is like a derived table or view, but provides more capability, like recursion. We can access it similar to any VIEW, Derived Table, base table, etc.
In the following CTE term, we select all the rows matching the 3 forms, and assign / generate a new seq column containing a row number (1 to n, within each partition), so that later we can just take seq <= 5 to limit the result to just the first 5 rows in each partition (form).
WITH cte AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
FROM exercises
WHERE form IN (1, 2, 3)
)
SELECT * FROM cte
WHERE seq <= 5
ORDER BY form, seq
;
Result with test data:
+----------+------+-----+
| exercise | form | seq |
+----------+------+-----+
| 15 | 1 | 1 |
| 8 | 1 | 2 |
| 10 | 1 | 3 |
| 16 | 1 | 4 |
| 6 | 1 | 5 |
| 29 | 2 | 1 |
| 24 | 2 | 2 |
| 26 | 2 | 3 |
| 20 | 2 | 4 |
| 25 | 2 | 5 |
| 41 | 3 | 1 |
| 46 | 3 | 2 |
| 47 | 3 | 3 |
| 40 | 3 | 4 |
| 51 | 3 | 5 |
+----------+------+-----+
I got it partially working thanks to the UNION method.
$stmt = $this->db->prepare("
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='1' ORDER BY rand() LIMIT 5) as f
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='2' ORDER BY rand() LIMIT 5) as f2
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='3' ORDER BY rand() LIMIT 5) as f3
ORDER BY rand()
");
$stmt->execute();
still having some problems though, but I will try to figure that out on my own first, and if I eventually need to, open another question.

mysql - get closest value to correct value return zero to many depending on result

My site allows users to guess the result of a sports match. At the end of the match the guesses should be compared to the actual result. The winner(s) are the members with the closest correct guess
Im looking for a way to return all members who guessed the correct result and score difference IF NO (zero) member guessed correctly return members who guessed closest to the correct result
See MYSQL FIDLE EXAMPLE
I modified the script to change fixed values taking variables as you can see below
if(isset($_POST['resultBtn'])){
foreach($_POST['winner'] as $id =>$winner){
$winScore = $_POST['score'][$id];
:
:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)";
My problem is if I run this script on the following table:
NOTHING gets displayed even if I put result exactly correct:
My variable values are correct in the sql statment so that is not the problem
Any help will be welcomed...
IMPORTANT THE USER WHO SELECTED CLOSEST CORRECT RESULTS, FOR ALL GAME, DURING THE ROUND IS THE WINNER
example: if user A won 4 of the picks and user B won 5 of the picks then user B is the winner of the round
Why don't you want just
SELECT p.*, abs(p.score-'$winScore') as diff
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND p.pick='$winner'
ORDER BY diff ASC
LIMIT 1
This will return the closest member for the event. Remove the LIMIT if you need a few of them.
Also, never put your parameters directly into the SQL query, even trusted ones (not your case) and even if you're sure they will always be integer or non-string type. Use prepared statements.
In this answer I call a "Best" pick any pick that has chosen the correct winner for a particular match, and has the closest score to the actual match score.
These scripts also respect the different "rounds" in the competition, since that is an important complication.
This answer comes in two parts: first a query that is similar to the one in the question that returns all the "Best" picks for a particular match. To make it easier to run in SQL Fiddle, I have used MySQL variables instead of PHP variables.
Schema with test data:
create table Multiple_Picks (
pick_id int,
member_nr int,
event_id int,
pick varchar(100),
score int
);
insert into Multiple_Picks
values
(11,100,1,'Crusaders',15),
(12,100,2,'Waratahs',10),
(13,100,3,'Chiefs',4),
(21,200,1,'Crusaders',15),
(22,200,2,'Waratahs',10),
(23,200,3,'Lions',4),
(31,300,1,'Crusaders',15),
(32,300,2,'Waratahs',12),
(33,300,3,'Lions',6),
(41,100,4,'Crusaders',20),
(42,100,5,'Waratahs',20),
(43,100,6,'Lions',20)
;
Queries to show all picks and then best picks for a particular match:
set #matchId = 2;
set #winner = 'Waratahs';
set #winScore = 8;
-- Show all picks for a particular match
select * from Multiple_Picks
where event_id = #matchId;
-- Show best picks for a particular match
select p.*
from Multiple_Picks p
where p.event_id = #matchId
and p.pick = #winner
and abs(p.score - #winScore) =
(select min(abs(other.score - #winScore))
from Multiple_Picks other
where other.event_id = #matchId
and other.pick = #winner
)
;
SQL Fiddle to show picks for particular match
-- Show all picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
| 32 | 300 | 2 | Waratahs | 12 |
+---------+-----------+----------+----------+-------+
-- Show best picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
+---------+-----------+----------+----------+-------+
Now we need to work towards finding the winner of each round of the competition.
First we have extra test data that contains the actual scores for Matches in rounds 1 and 2.
create table Matches (
event_id int,
winner varchar(100),
score int,
round int
);
insert into Matches
values
(1,'Crusaders',10,1),
(2,'Waratahs',11,1),
(3,'Lions',4,1),
(4,'Crusaders',20,2),
(5,'Waratahs',20,2),
(6,'Chiefs',20,2)
;
Now select the best picks for all Matches. The subselect (aliased as m) calculates best_diff for each match as the minimum difference between the actual score and every guessed score. This subselect is then joined to every pick so that only "Best" picks are returned.
-- Show all best picks for all Matches
select p.*, m.round
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
order by m.round, p.event_id
;
It is then easy to get a count of Best picks for each player for each round by just grouping the previous query by member_nr and round:
-- Show a count of best picks for each player for each round
select p.member_nr, m.round, count(*) as best_count
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
group by p.member_nr, m.round
order by m.round, count(*) desc
;
SQL Fiddle for all best picks and counts for all matches
-- Show all best picks for all Matches
+---------+-----------+----------+-----------+-------+-------+
| pick_id | member_nr | event_id | pick | score | round |
+---------+-----------+----------+-----------+-------+-------+
| 31 | 300 | 1 | Crusaders | 15 | 1 |
| 21 | 200 | 1 | Crusaders | 15 | 1 |
| 11 | 100 | 1 | Crusaders | 15 | 1 |
| 12 | 100 | 2 | Waratahs | 10 | 1 |
| 32 | 300 | 2 | Waratahs | 12 | 1 |
| 22 | 200 | 2 | Waratahs | 10 | 1 |
| 23 | 200 | 3 | Lions | 4 | 1 |
| 41 | 100 | 4 | Crusaders | 20 | 2 |
| 42 | 100 | 5 | Waratahs | 20 | 2 |
+---------+-----------+----------+-----------+-------+-------+
-- Show a count of best picks for each player for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 300 | 1 | 2 |
| 100 | 1 | 2 |
| 100 | 2 | 2 |
+-----------+-------+------------+
The final stage is to select only those players for each round who have the highest number of Best picks. I tried modifying the above queries, but the nesting becomes two confusing, so my solution was to create a few logical views so that the final query can be more easily understood. The views basically encapsulate the logic of the queries I have explained above:
create view MatchesWithBestDiff as
select m.event_id, m.winner, m.score, m.round,
min(abs(m.score-p.score)) as best_diff
from Matches m
join Multiple_Picks p
on p.event_id = m.event_id and p.pick = m.winner
group by m.event_id, m.winner, m.score, m.round
;
create view BestPicks as
select p.*, m.round
from Multiple_Picks p
join MatchesWithBestDiff m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
;
create view BestPickCount as
select member_nr, round, count(*) as best_count
from BestPicks
group by member_nr, round
;
So that the query that shows the winners of each round is simply:
-- Show the players with the highest number of Best Picks for each round
select *
from BestPickCount p
where best_count =
(
select max(other.best_count)
from BestPickCount other
where other.round = p.round
)
order by round
;
SQL Fiddle for players with most Best picks for each round
-- Show the players with the highest number of Best Picks for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 100 | 2 | 2 |
+-----------+-------+------------+
This whole investigation has reminded me how tricky it can be to get SQL to do much manipulation where records need to be selected depending on maximums and sums. Some of these types of queries can be much easier with window functions (the OVER and PARTITION BY clauses), but they are not available in MySQL.
While designing the above queries, I found a few interesting restrictions:
MySQL does not allow joins to subqueries in views definitions.
ANSI SQL does not allow an aggregate in a subquery to reference both a column from the inner query and a column from the outer query. MySQL seems to sometimes allow this, but I couldn't find clear guidance as to when it is allowed, so I chose to code the above queries to avoid this "feature".
scenario 1: NO USERS SELECTED THE CORRECT TEAM
I believe that result in this situation should be empty result because everyone has made a mistake.
SCORE RETURN MEMBERS WHO SELECTED THE CLOSEST TO CORRECT SCORE AND
RESULT
It seems to be already working in your code example except one mistake in select.
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
Instead of constant 1 (one) it should be variable '$winScore'
and to control the number of users you get, you may limit your results so you will get something like this:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id limit '$numberOfMembers'";
SCENARIO 2: SCENARIO 2: MULTIPLE USERS SELECTED CORRECT TEAM BUT
SCORES ARE DIFFERENT RETURN USER(S) WHO GUESSED CLOSEST TO CORRECT
SCORE
Same as in the previous question.
SCENARIO 3: MULTIPLE USERS SELECTED CORRECT TEAM AND SCORE RETURN ALL
USERS WHO SELECTED CORRECT TEAM AND SCORE
You can achieve this using same query just replace the LIMIT with 'rank' function, and also if you will get several closest scores, but you have to limit their number according to their voting order by id, for this purpose I suggest sorting.
So final query will be:
$sql="select * from (SELECT p.*,
abs(p.score-'$winScore') scr_diff,
#rownum := #rownum + 1 rank
FROM Multiple_Picks p,
(SELECT #rownum := 0) rank_gen
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id
) sq
where sq.scr_diff = 0
or sq.rank < '$numberOfMembers'";
Fiddle.
Best guesser for one match
First find the member(s) who picked the winner and had the closest score guess:
SELECT p.*
FROM
( SELECT MIN(ABS(score-'$winScore')) AS closest
FROM Multiple_Picks
WHERE event_id = '$matchId'
AND pick='$winner'
) AS c
JOIN Multiple_Picks p
WHERE p.event_id = '$matchId'
AND p.pick = '$winner'
AND ABS(score-'$winScore') = c.closest
If that return no results, then what should happen? (It would be because no one picked the winner for a particular event.)
But, I think your question is much more complex. However, the above gives a mapping from (event_id, pick) -> list-of-members who "won". Starting over...
Missing info
There is a mystery -- Where do the event results come from? I will assume this table is already populated:
CREATE TABLE Win (
event_id ..., -- which game
winnner ..., -- who won
score ... -- by what score
)
Best guesser overall
So, create a table of BestGuessers(event_id, member). The details of "all game" and "round" are a bit vague. So I will carry this at least one step further.
CREATE TEMPORARY TABLE BestGuessers(
event_id ...,
member_nr ... -- who guessed the best for that event
)
SELECT p.event_id, p.member_nr
FROM
( SELECT w.event_id, w.winner, MIN(ABS(mp.score-w.score)) AS closest
FROM Multiple_Picks AS mp
JOIN Win AS w ON mp.event_id = w.event_id
AND mp.pick = w.winner
GROUP BY w.event_id, w.winner
) AS c
JOIN Multiple_Picks p
ON p.event_id = c.event_id
AND p.pick = c.pick
AND p.score = c.closest
Now, from that, you can pick the best guesser(s).
SELECT y.member_nr
FROM
( SELECT COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
ORDER BY COUNT(*) DESC
LIMIT 1
) AS x -- the max number of correct guesses
STRAIGHT_JOIN
( SELECT member_nr, COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
) AS y -- the users who guessed correctly that many times
USING (ct);
All this is pretty complex; I may have some typos, even logic errors. But maybe I came close.
It seems an additional table to store the actual results would help here.
E.g let's say this is in a table called results with sample values as follows:
event_id winner result
1 Crusaders 16
2 Waratahs 15
3 Chiefs 4
4 Crusaders 17
5 Reds 12
0 Rebels 14
7 Cheetahs 15
8 Crusaders 14
This can then be JOINed on each row and results compared as follows:
SELECT p.*
, CASE WHEN ABS(p.score - r.result)
- CASE WHEN p.pick = r.winner THEN 999999 ELSE 0 END
= (SELECT MIN(ABS(p2.score - r2.result)
- CASE WHEN p2.pick = r2.winner THEN 999999 ELSE 0 END)
FROM picks p2
JOIN results r2
ON p2.event_id = r2.event_id
WHERE p2.event_id = p.event_id)
THEN 1
ELSE 0
END AS win
FROM picks p
JOIN results r
ON p.event_id = r.event_id;
Explanation
The rightmost win column is 1 if the member is calculated to have won or drawn the event, otherwise it is 0. The method used is similar to the one in your post, with the main difference being the team and score are combined. The main thing to be explained here is the 999999, which is subtracted when a correct team is picked - so this can be sure to eclipse the score difference. (Of course, an even bigger value could be picked if needed).
Demo
SQL Fiddle Demo

Query based on three database tables always returns zero results

I have a database with three tables in it:
places:
id | name | latitude | longitude |
------|--------|------------|------------|
1 | place1 | 11.123456 | 76.123456 |
------|--------|------------|------------|
2 | place2 | 23.123456 | 65.123456 |
etc ...
categorized_places:
id | place_id | cat_id |
------|----------|--------|
1 | 1 | 2 |
------|----------|--------|
2 | 2 | 1 |
etc ...
places_visited:
id | user_name | user_email | place_id |
------|-----------|------------|----------|
1 | user_1 | x#mail.com | 2 |
------|-----------|------------|----------|
2 | user_2 | y#mail.com | 2 |
There's also a fourth named categories, but it's not important in this.
I'm trying to filter the places from the places-table to show the user the nearest place, that he/she has not yet visited.
$cur_cat is set on the previous page, where the user selects which kind of place he/she would like to visit.
$cur_user and $cur_user_email are based on $_SESSION variables
$max_lat, $max_lon, $min_lat and $min_lon are based on the users current position
I'm using this code in php (with PDO), but it always returns zero results:
$get_places = $db->prepare("
SELECT
places.id,
places.name,
places.latitude,
places.longitude
FROM
places,
categorized_places,
places_visited
WHERE
places.id = categorized_places.place_id
AND categorized_places.cat_id = '$cur_cat'
AND places.latitude <= '$max_lat'
AND places.latitude >= '$min_lat'
AND places.longitude <= '$max_lon'
AND places.longitude >= '$min_lon'
AND places_visited.user_name = '$cur_user'
AND places_visited.user_email = '$cur_user_email'
AND places.id != places_visited.place_id
");
$get_places->execute();
The code always shows 0 results and throws no error. I've also made sure, that the places are not already in the places_visited table.
I've stared at this for so very long now, and I just can't figure out the error.
Any help would be very appreciated!
Your query is doing inner joins. So, it can only return places that the user has visited. No way that it can return places that a user hasn't visited. Before proceeding further, here is a simple rule: Never use commas in the from clause. ANSI standard explicit JOIN syntax has been around for over two decades, and you should use it. In fact, in this case, you need it, because you need an outer join:
SELECT p.id, p.name, p.latitude, p.longitude
FROM places p INNER JOIN
categorized_places cp
ON p.id = cp.place_id LEFT JOIN
places_visited pv
ON pv.place_id = p.id AND
pv.user_name = '$cur_user' AND
pv.user_email = '$cur_user_email'
WHERE cp.cat_id = '$cur_cat' AND
p.latitude <= '$max_lat' AND
p.latitude >= '$min_lat' AND
p.longitude <= '$max_lon' AND
p.longitude >= '$min_lon' AND
pv.place_id IS NULL;
What this does is it matches the conditions to all the places visited, using an outer join. Then the condition pv.place_id IS NULL chooses the ones that have not been visited. Note that the conditions on the places_visited table go in the ON clause. The conditions on the other two tables remain in the WHERE clause. In general, when using LEFT OUTER JOIN, the filters on the first table stay in the WHERE clause. The filters on the second table go in the ON clause.
I also introduced table aliases. These help make queries easier to write and to read.

Select the earliest record in a MySQL table

Please consider the following table structure (this is sample data, so please ignore the timestamps being identical):
+---------+---------+------------+-----------+
| list_id | item_id | date_added | is_active |
+---------+---------+------------+-----------+
| 1 | 1 | 1352073600 | 1 |
| 1 | 2 | 1372073600 | 1 |
| 1 | 3 | 1332073600 | 1 |
| 1 | 4 | 1302073600 | 1 |
| 2 | 1 | 1302073600 | 1 |
| 3 | 1 | 1302073600 | 1 |
+---------+---------+------------+-----------+
Our client wishes to show how many lists were created on a certain day. The list is created when the first item is added, but the date is not explicitly stored, only the date the items have been added.
My question is this:
Using MySQL (and PHP for computing the timestamp), how can I return the number of lists that were created on a certain day. Essentially, the logic should be (pseudo-code):
select total records from tbl_list_items where date_added >= min_age and date_added <= max_age, and record is oldest for this list
It is difficult to explain what I'm looking for, so consider the following actions:
No items added to lists yet (i.e. all lists have 0 items)
User added `item_id = 1` to `list_id = 1` yesterday.
User added `item_id = 2` to `list_id = 1` today.
User added `item_id = 1` to `list_id = 2` yesterday.
User added `item_id = 1` to `list_id = 3` yesterday.
User added `item_id = 2` to `list_id = 2` today.
If we wanted to look how many lists were created yesterday (or rather, how many lists had the first item added yesterday), I would like to return the total number where the item first added = yesterday. Given the action set above, this would return a total of 3 (i.e. list_id = 1, list_id = 2 and list_id = 3).
This returns the list of lists created on a specific day:
SELECT list_id, min(date_added) AS date_creation
FROM tbl_list_items
GROUP BY list_id
HAVING min(date_added) >= #min_age AND min(date_added) <= #max_age
To count them you can count number of results of the query, or use sub-queries like:
SELECT COUNT(*) AS total FROM
(
SELECT list_id, min(date_added) AS date_creation
FROM tbl_list_items
GROUP BY list_id
HAVING min(date_added) >= #min_age AND min(date_added) <= #max_age
)
However, note that this is totally not-optimized, because you have to GROUP BY the whole table for each query; maybe will be better to add a flag ( creation = 0 | 1 ) when the first item of a list is added.
Try this:
SELECT count(*) FROM tbl_list_items WHERE date_added >= :min_age and date_added <= :max_age GROUP BY date_added
You should have multiple tables to normalise your data. One for list, and another for list item.
Table List
ListID
ListCreatedDate
...any other list information...
Table ListItem
ListID
ListItemID
ListItemCreatedDate
...any other list item information...
This way you'll have access to the list creation date as well as individual item dates. Then you can simply join on List.ListID = ListItem.ListID to get all of your information.
Those look like unix timestamps, in which case you could do something like
SELECT ..
FROM ...
WHERE DATE(FROM_UNIXTIME(date_added)) = '2012-11-03'
Note that this will be inefficient, since you're comparing derived values - indexes won't be used.
If you want to select records from one particular day, why would you specify min_age and max_age? You just need to compare to a single date (say, of the format YYYY-MM-DD):
select count(*) from table_name where from_unixtime(date_added, %Y-%m-%d) = $date group by list_id
or something like that.

Sort by YES OR NO

I have search for Articles with dates. In MySQL is:
Article:
id | title
1 | first
2 | second
3 | third
4 | fourth
DatesArticle:
id | article_id | from | to
1 | 1 | 10-10-2010 | 11-11-2010
2 | 2 | 11-10-2010 | 12-12-2010
3 | 1 | 13-12-2010 | 12-01-2012
4 | 3 | 11-11-2012 | 12-12-2012
5 | 4 | 02-02-2013 | 02-02-2014
i would like get all Article with dates and sort this by availability.
for example i would like get all Articles and SORT this by dates FROM 12-10-2011 TO 12-01-2012
this should return me:
first (is in range FROM TO - DatesArticle.id = 3)
third (is in range FROM TO - DatesArticle.id = 4)
second (is NOT in range FROM TO)
fourth (is NOT in range FROM TO)
Is this possible with SQL or SQL and PHP? If yes, how?
Use the clause CASE, something like:
SELECT * FROM DatesArticle
ORDER BY CASE
WHEN id=3 AND CURRENT_DATE()<=from and to < CURRENT_DATE()>=to THEN 1
WHEN <condition_2> THEN 2
etc...
ELSE <any other condition>
END
Not saying the above is going to work as it is but it gives you and idea. If you add an example of the query you have tried or how are you building your where clause it would help for better answer.
My idea was to when it's id= 3 assign 1, if id = 4 assign 2, any other value assign 3, after that make the where condition and order by the number that you assign.
Try this:
select *,
case when t1.id=3 then 1 when t1.id=4 then 2 else 3 end as t
from article as t1
join DatesArticle as t2
on t1.id=t2.id
where CURRENT_DATE()<=from
and to < CURRENT_DATE()
order by t
You must first JOIN the tables in order to access article's data.
Then you ORDER on a logical condition (if there's a date within the given range).
SELECT title
FROM Article JOIN DatesArticle
ON (Article.id = DatesArticle.id)
ORDER BY DatesArticle.from > LastDate AND DatesArticle.to < FirstDate DESC;
(I never get ASC and DESC right in logical sorts -- try and see what happens)

Categories