How to lookup range value from database - php

How to find a value based on a range stored in the database.
My friend_levels table:
and on my profile table:
When I have friend_points = 1, I will get Normal Rate, then if I had friend_points = 180 I will get Great Friend, so it's basically like this on programming
if($profile_points >= 0 && $profile_points < 50) {
return 'Normal Rate';
} else if($profile_points >= 50 && $profile_points < 100) {
return 'Friend';
} else if($profile_points >= 100 && $profile_points < 150) {
return 'Good Friend';
}....
my question too is does it possible on QUERY? or I just make it on the PHP?
EDIT1: Is there a way to get the next target value?
For ex. If I'm on the Friend rate with 68 points how to get the 100 = Good Friend ? nevermind the substraction, I just want to get the next row.

If I understood you correctly, you can use CASE EXPRESSION like this:
SELECT id,user_id,
case when friend_points between 0 and 49 then 'Normal rate'
when friend_points between 50 and 99 then 'Friend'
when friend_points between 100 and 149 then 'Good friend'
.......
end as 'Friend_Status'
FROM profile
EDIT:
Or, if this names can change dynamicly then with a join:
SELECT t.id,t.user_id,s.name
FROM profile t
INNER JOIN friend_levels s ON(t.friend_points >= s.points_needed)
WHERE s.points_needed = (select min(f.points_needed)
from friend_levels f
where t.friend_points >= f.points_needed)

next smallest point
SELECT MAX (DISTINCT points_needed)
FROM friend_levels
WHERE points_needed < =$profile_points;
next highest point
SELECT MIN (DISTINCT points_needed)
FROM friend_levels
WHERE points_needed > $profile_points;

The CASE method proposed by Sagi works, but means that you need to update your code (SQL in this case rather than PHP) if you want to change your ranges.
Safin's method is only part of a solution - in order to find the corresponding description, Safin's query needs to be embedded in a select with joins/subselects as per Sagi's update, but this is somewhat inefficient.
Personally I wouldn't use aggregation for this:
SELECT user_id,
(SELECT fl.name
FROM friends_level fl
WHERE fl.points_needed<profiles.friend_points
ORDER BY fl.point_needed DESC
LIMIT 0,1) as level
FROM profiles

Related

Mysql UNION ALL to count multiple columns BUT also insert date column

Hi Guys I have a question. I am still learning and am trying to get some date out. Beneath is the table. It has hundreds of lines, but for example:
FormNR
Datum
XX1
XX2
XX3
0001
2022-09-08
4
23
7
0002
2022-09-10
8
5
0
The table name is 'forms'. Now what I need to do is to count XX1+XX2+XX3 (for a year rapport). Then I have a 'date from and to' selection box on my page. So the question would be:
What instanties have been used between a certain date in total but so that you can see a a total per Instantie (each number is a different instantie).
So for example...Between the 1st of January and the 1st of June a list of all XX numbers ( there are 36 ) with there total behind it
What I have is the following. Is works great and shows all XX's in a nice table but for the entire table, not per date. As soon as i want to add the 'between $date_from AND $date_to' it fails.
<?php
$sql_rg_total="SELECT forms.Datum, x.f1,Count(x.f1)
FROM
(SELECT XX1 As F1 FROM forms
UNION ALL
SELECT XX2 As F1 FROM forms
UNION ALL
SELECT XX3 As F1 FROM forms) x
WHERE x.f1 = '$subcat_id'
GROUP BY x.f1";
$resultvv=mysqli_query($conn, $sql_rg_total);
if (mysqli_num_rows($resultvv) > 0) {
while ($rowvv = mysqli_fetch_assoc($resultvv)) {
$subnr = $rowvv['Count(x.f1)'];
echo $subnr;
}
}
?>
By the way $subcat_id is from another table which connects the number to a name.
I have tried to write it as clear as I could. I know it's a bit thought haha. Thanks anyway for any input. Really stuck.
This query should do it:
SELECT SUM(x.c) AS c
FROM (
SELECT ((XX1 = '$subcat_id') + (XX2 = '$subcat_id') + (XX3 = '$subcat_id')) AS c
FROM forms
WHERE Datum BETWEEN '$date_from' AND '$date_to'
) x
The value of a boolean condition is 1 when it's true, 0 when it's false. So XX1 = '$subcat_id' + XX2 = '$subcat_id' + XX3 = '$subcat_id' adds up the number of columns that match in a row, then SUM(c) totals them in the entire table.
You don't need GROUP BY, since it's the same column that you're filtering in the WHERE condition (and now in the SELECT expression). And this moves the date condition into the subquery.

SQLite Find missing numbers between range (3000-4000)

I'm looking to find the lowest missing number between 2 numbers.
there may be jumps between 3080 and 3085, etc and all I would need is 3081.
This is for assigning an id to an entry and these entries can be deleted thus the id is too and because we have a limited range we want to make sure we use all the numbers. So if there aren't any missing numbers it would have to grab the next available number. if it's full till 3850 then it would need to grab 3851.
They are 0 if they have been deleted rather than null if that helps.
SELECT number, user_id
FROM entries
WHERE user_id = '18'
AND number BETWEEN '3000' AND '4000'
I'm not sure how to get the lowest available number in this sequence, please help. Thank you.
Try using a self join:
SELECT MIN(e1.number) + 1 num_missing
FROM entries e1
LEFT JOIN entries e2
ON e2.number = e1.number + 1
WHERE e1.id = 18 AND e1.number BETWEEN 3000 AND 4000 AND
e2.number IS NULL;
The critical condition in the WHERE clause is e2.number IS NULL, which means that this particular number had no next immediate value in the sequence, i.e. it is the start of a gap.
If you want next missing number for an id, then:
select e.id, e.number + 1
from entries e
where not exists (select 1
from entries e2
where e2.id = e.id and e2.number = e.number + 1
);
If you want this for a particular id, then add id = 18 to the outer where clause.

Group values and get percentage from database

I have a database table where I save players bets it the following logic:
This table will of course have several user_id's for every match_id. I want to display how the users have placed their bets. If they think that the home team will win lose or draw. AND I want it to be stated in percentage.
Germany - France 35% - 20% - 45%.
So far I have managed to calculate the quantity guesses, but not the percentage, with the following query:
SELECT games.home_team,games.away_team,COUNT(*) as 'qty',user_result_bet.match_id,(
CASE
WHEN `home_score` > `away_score` THEN 'home'
WHEN `home_score` < `away_score` THEN 'away'
ELSE 'draw'
END) as 'tipped_result'
FROM user_result_bet,GAMES
WHERE games.match_id = user_result_bet.match_id
GROUP BY tipped_result, match_id
ORDER BY match_id
Where do I go from here? I want to turn this in to percentage somehow? Im using PHP for the website
you need to use count if
SELECT user_result_bet.match_id,COUNT(*) as 'qty',
count(if(`home_score` > `away_score`,1,null))/count(*)*100 as home_percent,
count(if(`home_score` < `away_score`,1,null))/count(*)*100 as away_percent,
count(if(`home_score` = `away_score`,1,null))/count(*)*100 as draw_percent
from wherever
group by 1
SUM(CASE WHEN `home_score` > `away_score` THEN 1 ELSE 0 END)/COUNT(*) AS PercentageHome

MySQL/PHP Sort with natural sort

I writing a code to get mysql field values sorted.
my filed values are as below
**downloads**
N/A
10
50
30
unlimited
N/A
70
unlimited
those are on mysql table field.
i need to sort those assenting and descending like below
Assending
N/A
10
30
50
70
unlimited
unlimited
Desending
unlimited
unlimited
70
50
30
10
N/A
The space is some rows don't have data. i wrote mysql query like below
SELECT * FROM fltable ORDER BY LENGTH(downloads), downloads DESC
But this not returns correct sort, can anyone help me with this using my sql or php solution.
Thank You
For assending value use:
SELECT downloads, (CASE WHEN downloads = 'N/A' THEN 0
WHEN downloads = '' THEN 1
WHEN downloads='unlimited' THEN 4
ELSE 3 END) as rank
FROM fltable
ORDER BY rank ASC;
For desending value use:
SELECT downloads, (CASE WHEN downloads = 'N/A' THEN 0
WHEN downloads = '' THEN 1
WHEN downloads='unlimited' THEN 4
ELSE 3 END) as rank
FROM fltable
ORDER BY rank, downloads DESC;
SELECT * FROM fltable
ORDER BY case when downloads = 'N/A' then 1
when downloads is null then 2
when downloads = 'unlimited' then 4
else 3
end DESC,
downloads * 1 DESC
I know, this solution is very similar to another in SQL.
You can use usort to implement any logic you need.
define('DV_NA', 1);
define('DV_EMPTY', 2);
define('DV_NUM', 3);
define('DV_UNLIMITED', 4);
define('DV_OTHER', 5);
function customDloadValue($n) {
switch($n) {
case "N/A": return DV_NA;
case null: case "": return DV_EMPTY;
case "unlimited": return DV_UNLIMITED;
default: return is_numeric($n) ? DV_NUM : DV_OTHER;
}
}
usort($strings, function ($a, $b) {
$av = customDloadValue($a);
$bv = customDloadValue($b);
if ($av != DV_NUM or $bv != DV_NUM) return $av - $bv;
return intval($a) - intval($b)
});
Another similar way :
SELECT download, (download= 'N/A') boolNA, (download= '') boolBlank,
(download+0 > 0) boolNum, (download= '0') boolZero
FROM table
ORDER BY boolNA DESC, boolBlank DESC, boolZero DESC, boolNum DESC,
(download+0), download
That way, you can create groups to sort.
Which could result in something like :
N/A
10
30
50
70
unlimited
unlimited
Same result as above, different way. If you only have a few datatype (less than 3) that you need to group, might be easier.

Count number of consecutive visits

Every time a logged in user visits the website their data is put into a table containing the userId and date (either one or zero row per user per day):
444631 2011-11-07
444631 2011-11-06
444631 2011-11-05
444631 2011-11-04
444631 2011-11-02
444631 2011-11-01
I need to have ready access to the number of consecutive visits when I pull the user data from the main user table.. In the case for this user, it would be 4.
Currently I'm doing this through a denormalized consecutivevisits counter in the main user table, however for unknown reasons it sometimes resets.. I want to try an approach that uses exclusively the data in the table above.
What's the best SQL query to get that number (4 in the example above)? There are users who have hundreds of visits, we have millions of registered users and hits per day.
EDIT: As per the comments below I'm posting the code I currently use to do this; it however has the problem that it sometimes resets for no reason and it also reset it for everyone during the weekend, most likely because of the DST change.
// Called every page load for logged in users
public static function OnVisit($user)
{
$lastVisit = $user->GetLastVisit(); /* Timestamp; db server is on the same timezone as www server */
if(!$lastVisit)
$delta = 2;
else
{
$today = date('Y/m/d');
if(date('Y/m/d', $lastVisit) == $today)
$delta = 0;
else if(date('Y/m/d', $lastVisit + (24 * 60 * 60)) == $today)
$delta = 1;
else
$delta = 2;
}
if(!$delta)
return;
$visits = $user->GetConsecutiveVisits();
$userId = $user->GetId();
/* NOTE: t_dailyvisit is the table I pasted above. The table is unused;
* I added it only to ensure that the counter sometimes really resets
* even if the user visits the website, and I could confirm that. */
q_Query("INSERT IGNORE INTO `t_dailyvisit` (`user`, `date`) VALUES ($userId, CURDATE())", DB_DATABASE_COMMON);
/* User skipped 1 or more days.. */
if($delta > 1)
$visits = 1;
else if($delta == 1)
$visits += 1;
q_Query("UPDATE `t_user` SET `consecutivevisits` = $visits, `lastvisit` = CURDATE(), `nvotesday` = 0 WHERE `id` = $userId", DB_DATABASE_COMMON);
$user->ForceCacheExpire();
}
I missed the mysql tag and wrote up this solution. Sadly, this does not work in MySQL as it does not support window functions.
I post it anyway, as I put some effort into it. Tested with PostgreSQL. Would work similarly with Oracle or SQL Server (or any other decent RDBMS that supports window functions).
Test setup
CREATE TEMP TABLE v(id int, visit date);
INSERT INTO v VALUES
(444631, '2011-11-07')
,(444631, '2011-11-06')
,(444631, '2011-11-05')
,(444631, '2011-11-04')
,(444631, '2011-11-02')
,(444631, '2011-11-01')
,(444632, '2011-12-02')
,(444632, '2011-12-03')
,(444632, '2011-12-05');
Simple version
-- add 1 to "difference" to get number of days of the longest period
SELECT id, max(dur) + 1 as max_consecutive_days
FROM (
-- calculate date difference of min and max in the group
SELECT id, grp, max(visit) - min(visit) as dur
FROM (
-- consecutive days end up in a group
SELECT *, sum(step) OVER (ORDER BY id, rn) AS grp
FROM (
-- step up at the start of a new group of days
SELECT id
,row_number() OVER w AS rn
,visit
,CASE WHEN COALESCE(visit - lag(visit) OVER w, 1) = 1
THEN 0 ELSE 1 END AS step
FROM v
WINDOW w AS (PARTITION BY id ORDER BY visit)
ORDER BY 1,2
) x
) y
GROUP BY 1,2
) z
GROUP BY 1
ORDER BY 1
LIMIT 1;
Output:
id | max_consecutive_days
--------+----------------------
444631 | 4
Faster / Shorter
I later found an even better way. grp numbers are not continuous (but continuously rising). Doesn't matter, since those are just a mean to an end:
SELECT id, max(dur) + 1 AS max_consecutive_days
FROM (
SELECT id, grp, max(visit) - min(visit) AS dur
FROM (
-- subtract an integer representing the number of day from the row_number()
-- creates a "group number" (grp) for consecutive days
SELECT id
,EXTRACT(epoch from visit)::int / 86400
- row_number() OVER (PARTITION BY id ORDER BY visit) AS grp
,visit
FROM v
ORDER BY 1,2
) x
GROUP BY 1,2
) y
GROUP BY 1
ORDER BY 1
LIMIT 1;
SQL Fiddle for both.
More
A procedural solution for a similar problem.
You might be able to implement something similar in MySQL.
Closely related answers on dba.SE with extensive explanation here and here.
And on SO:
GROUP BY and aggregate sequential numeric values
If it is not necessary to have a log of every day the user was logged on to the webiste and you only want to know the consecutive days he was logged on, I would prefer this way:
Chose 3 columns: LastVisit (Date), ConsecutiveDays (int) and User.
On log-in you check the entry for the user, determine if last visit was "Today - 1", then add 1 to the columns ConsecutiveDays and store "Today" in column LastVisit. If last vist is greater than "Today - 1" then store 1 in ConsecutiveDays.
HTH

Categories