Showing Featured Item From the Database - php

I have database table as below.
id, bungalow_name, type, address, featured
A bungalow can be featured in the home page. If a bungalow is featured, featured column has the value 1. I have 50 bungalows in the tables and 5-7 bungalows are featured at a given time.
Let's assume featured bungalow names are as below.
bungalow 1, bungalow 2, bungalow 3, .........., bungalow 6
What I'm trying to do is show a featured bungalow in the home page for each day. And I want to loop like below as below for each month. Given that I don't want to show a bungalow randomly for each page load. I want to show per day one bungalow basis.
today -> bungalow 1
tomorrow -> bungalow 2
day after tomorrow -> bungalow 3
...
After bungalow 6, bungalow 1 is shown on the next day.
How can I do it? Is it even possible with SQL/PHP?

You could use this MySQL query:
SELECT *
FROM Bungalows
WHERE id = (
SELECT b1.id
FROM
Bungalows b1 LEFT JOIN Bungalows b2
ON b1.id>b2.id AND b2.featured=1
WHERE
b1.featured=1
GROUP BY
b1.id
HAVING
COUNT(b2.id) = (SELECT
DATEDIFF(CURDATE(), '2013-05-06') MOD
(SELECT COUNT(*) FROM Bungalows WHERE Featured=1))
)
Please see fiddle here. '2013-05-06' is the day when you want to start to show the first featured bungalow. They will be shown ordered by ID, strarting from '2013-05-06'.
EDIT
The following query will return the number of elapsed days since 2013-05-06:
SELECT DATEDIFF(CURDATE(), '2013-05-06')
the MOD function will return the integer remainder of the division of the number of elapsed day by the number of featured rows:
SELECT DATEDIFF(CURDATE(), '2013-05-06') MOD
(SELECT COUNT(*) FROM Bungalows WHERE Featured=1)
If there are 6 featured bungalows, it will return 0 the first day,1 the second,2,3,4,5, and then 0,1,2...again.
MySQL does not have a function to return a RANK (number of row), so you have to simulate it somehow. I simulated it this way:
SELECT b1.id, COUNT(b2.id)
FROM
Bungalows b1 LEFT JOIN Bungalows b2
ON b1.id>b2.id AND b2.featured=1
WHERE
b1.featured=1
GROUP BY
b1.id
I'm joining the Bungalows table with itself. The rank of bungalow ID is the count of bungalows that have an ID less than that (hence the join b1.id>b2.id).
I'm then selecting only the row that have the RANK returned by the function above:
HAVING
COUNT(b2.id) = (SELECT
DATEDIFF(CURDATE(), '2013-05-06') MOD
(SELECT COUNT(*) FROM Bungalows WHERE Featured=1))
If you use MySQL, my initial query could be simplified as this:
SELECT b1.*
FROM
Bungalows b1 LEFT JOIN Bungalows b2
ON b1.id>b2.id AND b2.featured=1
WHERE
b1.featured=1
GROUP BY
b1.id
HAVING
COUNT(b2.id) = (SELECT
DATEDIFF(CURDATE(), '2013-05-06') MOD
(SELECT COUNT(*) FROM Bungalows WHERE Featured=1))

$dbh = new PDO(....); // use your connection data
$statement = $dbh->query("SELECT count(*) as size FROM bungalows where features = 1");
$data = $statement->fetchALL(PDO::FETCH_CLASS,"stdClass");
$i = date('z') % $data[0]->size;
$statement = $dbh->query("SELECT * FROM bungalows where features = 1 order by id LIMIT $i,1");
$bungalow = reset($statement->fetchALL(PDO::FETCH_CLASS,"stdClass"));
EDIT
Removed mysql_ calls
added an order clause as fthiella suggested (thank you :) )

Try this query it will work in every case with increase in number of featured bungalows etc
and daily will give a different one.
Here in the query I am assigning numbers to each featured bungalow from 0 to n and receiving then by dividing total number of featured bungalow to date diff I find the bungalow to be displayed.
Query 1:
select
a.*
from
(select
#rn:=#rn+1 as rId,
b.cnt,
a.*
from
Bunglows a
join
(select #rn:=-1) tmp
join
(select
count(*) as cnt
from
Bunglows
where
featured=1)b
where
featured=1) a
where
datediff(CURDATE(), '2013-01-01')%a.cnt=a.rId
SQL FIDDLE:
| RID | CNT | ID | BUNGALOW_NAME | FEATURED |
---------------------------------------------
| 3 | 4 | 6 | bungalow 4 | 1 |
EDIT
select count(*) as cnt from Bunglows where featured=1
This query finds the total featured bungalows
select #rn:=#rn+1 as rId, b.cnt, a.* from Bunglows a join (select #rn:=-1) tmp join select count(*) as cnt from Bunglows where featured=1
This query adds the a rownumber to each featured bungalow starting from 0 to n
The main query first finds date diff from current date and a old date and find mod value by total featured bungalows which will give values from 0 to n-1 and I have added a where clause which checks for the divided value to be equal to the rowid which we have assigned..
Hope this helps...

The basic concept of my answer is to;
1/ Create a list of all featured bungalows. This is achieved in the sub-query, where each bungalow is given a unique sequence number. The code for the seq_num field is based on the answer here
2/ Pick a single bungalow from that listed based on where we are in the month. To do this I look at the day of the month for today, using the code day(curdate()) and I find the mod of that number to the total number of featured bungalows.
select sq.bungalow_name
from (
select bungalow_name
,#curRow := #curRow + 1 AS seq_num
from table1, (SELECT #curRow := 0) r
where featured = 1
order by
bungalow_name desc
) sq
where sq.seq_num = mod(day(curdate()),(select count(*) from table1 where featured = 1))
Example at this SQL Fiddle

You have to track the last date of display for each featured record (last_viewed).
For new records, set this date to a day in the past, eg. 2000-01-01.
If there is a record with the current date, use that.
In not, use the record with the earliest date.
SELECT *, IF(DATE(last_viewed)=CURDATE(), 1, 0) AS current
FROM #__bungalows
WHERE featured=1
ORDER BY current DESC, last_viewed ASC
LIMIT 0,1
Like the hit counter in com_content, you should add a hit method to your bungalow model, which sets the last_viewed column of the selected bungalow to now().

select ....
where featured = 1
limit DAYOFYEAR(NOW()) % (select count(*) from ... where featured = 1), 1
I'm not sure if subselect is allowed in limit. You may have to perform that query separately. This will rotate every day. Easy peasy lemon squeezy.
edit: to perform in 2 queries
$query = "SELECT COUNT(*) FROM ... WHERE FEATURED = 1";
$count = intval(array_pop(mysql_fetch_assoc(mysql_query($query))));
$query = "
select ....
where featured = 1
limit DAYOFYEAR(NOW()) % {$count}, 1
";
DONE!

The modulo-operator % does the trick:
First, add a column "counter int".
Next, number the featured columns like 1,2,3... if you are lazy, you can use this:
set #cc=0;
update bungalows set counter=(select #cc:=#cc+1) where featured=1;
Now, everything is prepared and you simply can do a first select:
select * from bungalows where featured=1 and counter%(select count(*) from bungalows where featured=1)=0;
And everytime before you need the next featured bungalows, do a:
update bungalows set counter=counter+1 where featured=1;
Then again:
select * from bungalows where featured=1 and counter%(select count(*) from bungalows where featured=1)=0;
...
update bungalows set counter=counter+1 where featured=1;
and so on...

Check here with SQLFiddle
SELECT *
FROM bungalows b
JOIN (SELECT
( DAYOFMONTH(CURDATE() ) % COUNT(b2.id) ) AS slab,
COUNT(b2.id) AS total_count
FROM bungalows b2
WHERE b2.featured = 1) AS b3
WHERE IF(b3.slab = 0, b3.total_count, b3.slab) = (SELECT
COUNT(id)
FROM bungalows b1
WHERE b.id >= b1.id
AND b1.featured = 1)
AND b.featured = 1

Related

SQL - Get AVG value from other table

My question sounds really easy, but I'm stuck.
Sample Data:
Listing:
id title State
1 Hotel with nice view Arizona
2 Hotel to stay Arizona
Review:
id listing_id rating mail_approved
1 1 4(stars) 1
2 1 4(stars) 0
3 1 3(stars) 1
4 2 5(stars) 1
So now I get the AVG value of the listings, but I want to get only the value of each listing when the review is mail_approved = 1. But when there is none review or no review with mail_approved = 1 it should give me the listing back just with 0.0 review points. So I would like to get all listing back if they have a review just calculate the AVG of those reviews with mail_approved = 1
How can I do this?
Do I have to rewrite the whole query?
Here is my query:
SELECT
ls.id,
title,
state,
ROUND(AVG(rating),2) avg_rating
FROM listing ls
JOIN review rv
ON ls.id = rv.listing_id
WHERE ls.state = '$get_state'
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC
You used join, which is short for inner join. This type of join only gives results if a matching record exists in both tables. Change it to left join (short for left outer join), to also include listings without reviews.
You will need to move the state check and any other check to the join condition too, otherwise those listings without review will be dropped from the result again.
Lastly, you can coalesce the average value to get 0 instead of null for those records.
SELECT
ls.id,
title,
state,
COALESCE(ROUND(AVG(rating),2), 0) avg_rating
FROM listing ls
LEFT JOIN review rv
ON ls.id = rv.listing_id
AND ls.state = '$get_state'
AND ls.mail_approved = 1
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC
As a side note, please check prepared statements (for PDO or MySQLi) for the proper way to pass input parameters to your query instead of concatenating with variables like $get_state. Concatting is error prone, and makes you more vulnerable for SQL injection.
Outer join the avarage ratings to the hotels:
select
l.id,
l.title,
l.state,
coalesce(r.avg_rating, 0)
from listing l
left join
(
select
listing_id,
round(avg(rating), 2) as avg_rating
from review
where mail_approved = 1
group by listing_id
) r on r.listing_id = l.id
where l.state = '$get_state'
order by avg_rating desc;

Get data if insert are in the same second

I have this table :
id idm date_play
1 5 2017-08-23 12:12:12
2 5 2017-08-23 12:12:12
3 6 2017-08-23 12:14:13
I want to identify if user has more then one insert in the same second. In the case describe I want to get the user id that is 5.
I tried like this :
SELECT `idm`, MAX(`s`) `conseq` FROM
(
SELECT
#s := IF(#u = `idm` AND (UNIX_TIMESTAMP(`date_play`) - #pt) BETWEEN 1 AND 100000, #s + 1, 0) s,
#u := `idm` `idm`,
#pt := UNIX_TIMESTAMP(`date_play`) pt
FROM table
WHERE date_play >= '2017-08-23 00:00:00'
AND date_play <= '2017-08-23 23:59:59'
ORDER BY `date_play`
) AS t
GROUP BY `idm`
Can you help me please ? Thx in advance and sorry for my english.
Assuming your dates are accurate down to the second level, you can do this with a single aggregation:
select idm
from t
group by idm
having count(*) > count(distinct date_play);
If date_play has fractional seconds, then you would need to remove those (say by converting to a string).
If you want the play dates where there are duplicates:
select idm, date_play
from t
group by idm, date_play
having count(*) >= 2;
Or, for just the idms, you could use select distinct with group by:
select distinct idm
from t
group by idm, date_play
having count(*) >= 2;
(I only mention this because this is the only type of problem that I know of where using select distinct with group by makes sense.)
If you want all the rows that are duplicated, I would go for exists instead:
select t.*
from t
where exists (select 1
from t t2
where t2.idm = t.idm and t2.date_play = t.date_play and
t2.id <> t.id
);
This should have reasonable performance with an index on (idm, date_play, id).
If your table is called mytable, the following should work:
SELECT t.`idm`
FROM mytable t INNER JOIN mytable t2
ON t.`idm`=t2.`idm` AND t.`date_play`=t2.`date_play` AND t.`id`!=t2.`id`
GROUP BY t.`idm`
Basically we join the table with itself, pairing records that have the same idm and date_play, but not the same id. This will have the effect of matching up any two records with the same user and datetime. We then group results by user so you don't get the same user id listed multiple times.
Edit:
Gordon Linoff and tadman's suggestions led me to this probably much more efficient query (credit to them)
SELECT t.`idm`
FROM mytable t
GROUP BY t.`date_play`
HAVING COUNT(t.`id`)>1

Calculating score based on rank position

I have a table jackpot with columns uid for user ID and nright for number of right answers.
I manage to SELECT and rank users by right answers, but what next?
SELECT
a1.uid,
a1.nright,
COUNT(a2.nright) AS rank
FROM
jackpot a1,
jackpot a2
WHERE
a1.nright < a2.nright
OR (
a1.nright = a2.nright
AND a1.uid = a2.uid
)
GROUP BY
a1.uid,
a1.nright
ORDER BY
a1.nright DESC,
a1.uid DESC
I need to calculate the amount of points to give to each user depending on his position.
Only users with top 3 MAX nright receive points.
The total amount of points = the number of users*20.
First position gets 70% of the total, 2nd - 20%, 3rd - 10%.
In case of equal right answers between users, the points are split evenly (50/50, 33/33/33...).
SQL Fiddle
You need to decompose what you want.
1st step : You want the top 3 scores.
SELECT nright
FROM jackpot
ORDER BY nright DESC
LIMIT 3
2nd step : The user id who gets this 3 first scores
SELECT j.uid
FROM jackpot j
INNER JOIN (
SELECT nright
FROM jackpot
ORDER BY nright DESC
LIMIT 3 ) AS t ON t.nright = j.nright
3rd step: the total amount of point
SELECT COUNT(uid)*20 AS lot FROM jackpot
4th step: the rank and the number of person
Here we need to use a variable, as you are in php, you can't use set #var:= X; , so the trick is to do a Select #var:= X , this variable will not work because of the aggregate functions. So you need to do this :
SELECT #rank := #rank+1 as rank,T1.nright,T1.nb,T1.lot
FROM(
SELECT nright,
COUNT(uid) as nb,
(SELECT COUNT(uid)*20 FROM jackpot) as lot
FROM jackpot
GROUP BY nright
ORDER BY nright DESC
LIMIT 3
)T1, (SELECT #rank:= 0) r
5th step: The lots distribution
SELECT j.uid,
CASE
WHEN t.rank = 1 THEN (t.lot*0.7)/t.nb
WHEN t.rank = 2 THEN (t.lot*0.2)/t.nb
WHEN t.rank = 3 THEN (t.lot*0.1)/t.nb
END as lot
FROM jackpot j
INNER JOIN
(SELECT #rank := #rank+1 as rank,T1.nright,T1.nb,T1.lot
FROM(
SELECT nright,
COUNT(uid) as nb,
(SELECT COUNT(uid)*20 FROM jackpot) as lot
FROM jackpot
GROUP BY nright
ORDER BY nright DESC
LIMIT 3
)T1, (SELECT #rank:= 0) r) t ON t.nright = j.nright

Select account_id by max Date

Hello I have the following table design
ID account_id score date
------------------------------------------
1 500 4 x
2 764 4 x
3 500 6 x
4 500 7 x
5 764 5 x
I'm trying to get all rows with the latest account_id entry
so my code should return
ID account_id score date
------------------------------------------
4 500 7 x
5 764 5 x
I tried the following code but it seems to return the first entry but with the latest date
SELECT account_id,score, max(date) from table group by account_id
Case 1: If id is auto-increment column or max(id) means latest row.
select * from
(select * from table_name
order by id desc) temp
group by account_id
Case 2: If date column decides latest row then replace id by date in order clause and group clause.
try it-
SELECT distinct a.id,a.account_id,b.score,b.date
FROM mytable b
JOIN
(
SELECT account_id,MAX(id) AS id
FROM mytable
GROUP BY account_id
) a ON a.account_id=b.account_id
ORDER BY a.id;
This question is just a duplicate of SQL Select only rows with Max Value on a Column
There you'll find a good explanation.
SELECT a.*
FROM table a
INNER JOIN (
SELECT `account_id`,MAX(`date`) AS latest FROM table GROUP BY account_id
) b
ON a.`date` = b.`latest` AND a.`account_id` = b.`account_id`
ORDER BY `date` DESC
Reference :
Using ORDER BY and GROUP BY together
Try using this query it works fine
Select a.account_id,a.score,a.date
from authors as a
join
(Select account_id,max(date) as date
from authors
group by account_id) as d
on(d.date=a.date)

How to find required unique result

I have two table 'topic' and 'subcategory'
I am using this query--
Select * from `subcategory` as s
Inner join `topic` as f
WHERE s.`Subcategory_id` = f.`Subcategory_id
My result shows like
Category_id Subcategory_id Post_id time
2 2.3 4 2012-12-01
1 1.5 5 2013-01-20
1 1.3 6 2013-03-18
There's also other columns... but all I want is to select the latest Post_id and Subcategory_id of one Category_id ... that means here Category 1 has two Subcategory it will select only the latest(here 1.3) and same result all the time for all Category when database will grown larger. What will be the next query or how could I change the existing query to gain my desired result?
SELECT Post_Id, Subcategory_Id from subcategory as s, topic as t where
s.Subcategory_id = t.Subcategory_id and time = (
SELECT Max(time) from subcategory as s1, topic as t1 where
s1.Subcategory_id = t1.Subcategory_id and s1.Category_id = s.Category_id
);
Something like that, I think, will work.
SELECT TOP 1 ... ORDER BY whatever column determines "the latest"
e.g.
SELECT TOP 1 ... ORDER BY TIME DESCENDING
Or in case of mysql:
SELECT ... ORDER BY TIME DESCENDING LIMIT 1
Join your topic table with following query:
SELECT s.* FROM subcategory s
Inner JOIN (SELECT s1.Category_id,
MAX(s1.time1) AS max_time
FROM subcategory s1
GROUP BY s1.Category_id) y
ON y.Category_id = s.Category_id AND y.max_time = s.time1

Categories