I do have a table that has multiples visitors in it. In my result list only the last visit of each visitor should be shown.
the table looks like:
id | created_at | descr | website | source
------------------------------------------
2 | 2017_12_22 | john | john1.com | a
3 | 2017_12_23 | marc | ssdff.com | b
4 | 2017_12_24 | john | john1.com | c
5 | 2017_12_24 | tina | def.com | b
6 | 2017_12_25 | stef | abc.com | a
7 | 2017_12_26 | john | john2.com | c
If I do an
->orderBy('visitors.created_at', 'desc')
->groupBy('visitors.descr')
the result list is correct as only each visitor is shown once. But I need the latest occurrence of a visitor and the result list shows the first occurrence. So instead of ID 2 I would like to see ID 7.
The following query solves that issue:
select *
from visitors as v
INNER JOIN ( select descr , MAX(created_at) as max_created
from visitors as v
group by descr ) AS M
where v.descr = M.descr AND v.created_at = M.max_created
ORDER BY created_at DESC
Could someone help get this into eloquent or get me into another direction?
If you can use the AUTO_INCREMENT id column instead of created_at, you can use another way to get the (same) result:
select *
from visitors
where id in (
select MAX(id)
from visitors
group by descr
)
ORDER BY id DESC
This query is probably less efficient, but it's easier to use with eloquent, since whereIn() supports subqueries.
$subQuery = Visitor::groupBy('descr')->select(DB::raw('max(id)'));
$latestVisitors = Visitor::whereIn('id', $subQuery)->orderBy('id', 'desc')->get();
Note that your original query can return multiple rows per descr, if the value in created_at is the same. This can't happen using id since it is unique.
Why dont you just query the sql directly like this:
DB::table('visitors')->select("
select *
from visitors as v
INNER JOIN ( select descr , MAX(created_at) as max_created
from visitors as v
group by descr ) AS M
where v.descr = M.descr AND v.created_at = M.max_created
ORDER BY created_at DESC
");
In eloquent the query would be something like this:
DB::table('visitors as v')
->join(DB::raw("( select descr , MAX(created_at) as max_created
from visitors as v
group by descr ) AS M "),'v.id','=','M.id')
->select('*')
->where('v.descr','M.descr')->where('v.created_at','M.max_created')
->orderBy('created_at','desc')
->get()
You can write a helper relationship in your Visitor model
public function lastVisit()
{
$this->hasOne('VisitModel')->latest();
}
and then just fetch all visitors with their last visit
$visitors = Visitor::with('lastVisit')->get();
That's really all there is to it.
Related
I have a table where it stores the types of discounts that a user can have.
Some users will get the standard discount, but some will get a bigger and better discount. For users who have the biggest and best discount, there will be two records in the database, one for the default discount and the other for the biggest and best discount. The biggest and best discount will be preferred in the search.
I would like to do a SELECT that would return the record with the highest discount and if you don't find it, return it with the standard discount for me to avoid making two queries in the database or having to filter in the source code.
Ex:
| id | user_id | country | discount | cashback | free_trial |
|-----------------------------------------------------------------------|
| 1 | 1 | EUA | DEFAULT | 10 | false |
| 2 | 1 | EUA | CHRISTMAS | 20 | true |
| 3 | 3 | EUA | DEFAULT | 10 | false |
SELECT *
FROM users
WHERE country = 'EUA'
AND (discount = 'CHRISTMAS' OR discount = 'DEFAULT');
In this example above for user 1 it would return the record with the discount equal to "CHRISTMAS" and for user 3 it would return "DEFAULT" because it is the only one that has. Can you help me please?
You can use the row_number() window function to do this. This function includes a PARTITION BY that lets you start the numbering over with each user, as well as it's own ORDER BY that lets you determine which rows will sort first within each user/partition.
Then you nest this inside another SELECT to limit to rows where the row_number() result is 1 (the discount that sorted best):
SELECT *
FROM (
SELECT *, row_number() OVER (PARTITION BY id, ORDER BY cashback desc) rn
FROM users
WHERE country = 'EUA'
) u
WHERE rn = 1
You could also use a LATERAL JOIN, which is usually better than the correlated join in the other answer, but not as good as the window function.
You can using GROUP BY to do it
SELECT u1.*
FROM users u1
JOIN
(
SELECT COUNT(id) AS cnt,user_id
FROM users WHERE country = 'EUA'
GROUP BY user_id
) u2 ON u1.user_id=u2.user_id
WHERE IF(u2.cnt=1,u1.discount='DEFAULT',u1.discount='CHRISTMAS')
DB Fiddle Demo
I have an table like that:
id | name | v (lvl)
11 | Jane | 6
12 | John | 5
13 | Jane | 6
14 | John | 5
15 | Jane | 7
16 | Jane | 5
In my autocomplete form now id like to group the names but get the last value (value with biggest id). In the example above would be
Jane | 5
I tried with combinations like distinct, group by, order by. But im always get
Jane | 6
or grouped like this and reversed:
Jane | 6
Jane | 7
Jane | 5
I would need something like this:
SELECT name,lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY lvl DESC
)
GROUP BY name
EDIT: I won't get the highest lvl, i want get the lvl of the highest id, grouped by name. Thats all. My example above would be the best explanation what i like to get.
In the inner query i change the order to DESC for all and in the outer i group it by names. But i get an error for this.
EDIT 2 I finally did at my own. The correct solution (i was already close):
SELECT a.name, a.lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY id DESC
)as a
GROUP BY name
LIKE without % is just =
SELECT *
FROM yourTable
WHERE name = 'Jane'
ORDER BY id DESC
LIMIT 1
But because you mention autocomplete functionality you should use:
WHERE name LIKE 'Jane%'
To have the latest, you need to have a field dateAdded which stores the date you ran the insert command.
Following which, you use MAX(dateAdded) to get the latest ID (since, as you mentioned, it may decrease as well)
UPDATE:
if ID doesn't decrease, you can always use MAX(ID)
SELECT MAX(id), v from tablename where name = 'Jane'
UPDATE:
This has been tested:
SELECT ID, v from tableName where ID = (SELECT MAX(ID) as ID from tableName where name like '%Jane%')
Try the following query (h/t #lamak)
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY name
ORDER BY [id] DESC)
FROM poro
)
SELECT *
FROM CTE
WHERE RN = 1
I want to get MIN price from the below tables using RIGHT JOIN and WHERE price not equal to zero and not empty based on user id. How I can get single record with MIN price based on user id in single MYSQL query.
Here is my query with just right join.
SELECT *
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1'
table name: search
search | search_id | user_id | datetime
1 | 1 | 1 | -
table name: offer
offer_id | search_id | price
1 | 1 |
2 | 1 | 0
3 | 1 | 506.1
4 | 1 | 285.3
Query will be :
SELECT *
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1' AND search.price > 0
ORDER BY search.price ASC LIMIT 1
An alternative using a different join condition:
SELECT MIN(o.price) min_price
FROM search s
JOIN offers o ON (
s.search_id = o.search_id
AND o.price IS NOT NULL
AND o.price > 0
)
WHERE s.user_id = '1'
When you want to select the minimal price, you can use the MYSQL "MIN" function. For this function you need a GROUP BY in your query.
Something like this, just edit it to your requirements.
SELECT *, MIN(price) as `minPrice`
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1' AND search.price > 0
GROUP BY search.search_id
I'm trying to do a query that selects mike if it isn't in the three highest bids for a keyword. Rows 4 and 7 should be selected.
So in final, if mike isn't in the three highest bids for a keyword, then select.
How do I solve this? With a sub query?
$construct = "SELECT child.* FROM `temp-advertise` child
LEFT JOIN `temp-advertise` parent on child.keyword=parent.keyword
WHERE child.name='mike'
ORDER BY child.id DESC";
id | name| keyword | bid |
1 | mike| one | 7 |
2 | tom | one | 4 |
3 | ced | one | 6 |
4 | mike| two | 1 |
5 | tom | two | 5 |
6 | har | two | 5 |
7 | mike| one | 3 |
8 | har | two | 3 |
SELECT *
FROM `temp-advertise` ta
WHERE ta.keyword = 'one'
AND ta.name = 'mike'
AND ta.bid <
(
SELECT bid
FROM `temp-advertise` tai
WHERE tai.keyword = 'one'
ORDER BY
bid DESC
LIMIT 2, 1
)
Your structure doesn't look too promising, nor your sample data. However, that said, you want to know if "Mike" was in the top 3 per keyword... and that he has 3 bids.... 2 for "one", 1 for "two". From the raw data, it looks like Mike is in 1st place and 4th place for the "one" keyword, and 4th place for "two" keyword.
This should get you what you need with SOME respect to not doing a full query of all keywords. The first innermost query is to just get keywords bid on by "mike" (hence alias "JustMike"). Then join that to the temp-advertise on ONLY THOSE keywords.
Next, by using MySQL variables, we can keep track of the rank PER KEYWORD. The trick is the ORDER BY clause needs to return them in the order that represents proper ranking. In this case, each keyword first, then within each keyword, ordered by highest bid first.
By querying the records, then using the #variables, we increase the counter, start at 1 every time the keyword changes, then preserve the keyword into the #grpKeyword variable for comparison of the next record. Once ALL bids are processed for the respective keywords, it then queries THAT result but ONLY for those bid on by "mike". These records will have whatever his rank position was.
select RankPerKeyword.*
from
( SELECT ta.*,
#grpCnt := if( #grpKeyword = ta.Keyword, #grpCnt +1, 1 ) as KWRank,
#grpKeyword := ta.Keyword as carryForward
FROM
( select distinct ta1.keyword
from `temp-advertise` ta1
where ta1.name = "mike" ) as JustMike
JOIN `temp-advertise` ta
on JustMike.Keyword = ta.Keyword,
( select #grpCnt := 0,
#grpKeyword := '' ) SqlVars
ORDER BY
ta.Keyword,
ta.Bid DESC" ) RankPerKeyword
where
RankPerKeyword.name = "mike"
(Run above to just preview the results... should show 3 records)
So, if you want to know if it was WITHIN the top 3 for a keyword you could just change to
select RankPerKeyword.keyword, MIN( RankPerKeyword.KWRank ) as BestRank
from (rest of query)
group by RankPerKeyword.Keyword
Try this:
Select ID, name, keyword from temp-advertise e
where 3 <= (select count(name) from temp-advertise
where e.keyword = keyword and bid > e.bid)
Try
SELECT .. ORDER BY bid LIMIT 3,999
I read many topics about this problem but I can't find the solution.
I have a table (called users) with the users of my website. They have points. For example:
+-----------+------------+
| User_id | Points |
+-----------+------------+
| 1 | 12258 |
| 2 | 112 |
| 3 | 9678 |
| 4 | 689206 |
| 5 | 1868 |
+-----------+------------+
On the top of the page the variable $user_id is set. For example the user_id is 4. Now I would like to get the rank of the user by points (output should be 1 if the user_id is 4).
Thank you very much!
SELECT
COUNT(*) AS rank
FROM users
WHERE Points>=(SELECT Points FROM users WHERE User_id=4)
Updated with some more useful stuff:
SELECT
user_id,
points,
(SELECT COUNT(*)+1 FROM users WHERE Points>x.points) AS rank_upper,
(SELECT COUNT(*) FROM users WHERE Points>=x.points) AS rank_lower
FROM
`users` x
WHERE x.user_id = 4
which includes the range of ranks user is at. So for example if the scores for first five places are 5 4 3 3 3, the result would be:
id points rank_upper rank_lower
id 5 1 1
id 4 2 2
id 3 3 5
id 3 3 5
id 3 3 5
This query should do what you want :
SELECT rank FROM (
SELECT User_id, Points,
FIND_IN_SET(
Points,
(SELECT GROUP_CONCAT(
DISTINCT Points
ORDER BY Points DESC
)
FROM users)
) as rank
FROM users )
WHERE User_id = 4;
If you don't want to do it outside mysql you'll need to use variables to compute the rank.
Here's a solution that describes exactly what you want :
http://www.fromdual.ch/ranking-mysql-results
You still need, it you want to have it directly for each record, to store it in the record and to update it yourself. There is no reasonable query that will give you directly the rank without storage on a real table (I mean not just a few hundreds records).
There's already a simple solution, just suited for your purpose.
This may help
SELECT #rank:=#rank+1 AS rank,`User_id`,`Points` FROM `users` u JOIN (SELECT #rank:=0) r ORDER BY u.Points DESC