Selecting latest entries for distinct entry - php

Im having a brain fart as to how I would do this.
I need to select only the latest entry in a group of same id entries
I have records in an appointment table.
lead_id app_id
4 42
3 43
1 44
2 45
2 46 (want this one)
1 48
3 49 (this one)
4 50 (this one)
1 51 (this one)
The results I require are app_id 46,49,50,51
Only the latest entries in the appointment table, based on duplicate lead_id identifiers.

Here is the query you're looking for:
SELECT A.lead_id
,MAX(A.app_id) AS [last_app_id]
FROM appointment A
GROUP BY A.lead_id
If you want to have every columns corresponding to these expected rows:
SELECT A.*
FROM appointment A
INNER JOIN (SELECT A2.lead_id
,MAX(A2.app_id) AS [last_app_id]
FROM appointment A2
GROUP BY A2.lead_id) M ON M.lead_id = A.lead_id
AND M.last_app_id = A.app_id
ORDER BY A.lead_id
Here i simply use the previous query for a jointure in order to get only the desired rows.
Hope this will help you.

The accepted answer by George Garchagudashvili is not a good answer, because it has group by with unaggregated columns in the select. Select * with group by is simply something that should not be allowed in SQL -- and it isn't in almost all databases. Happily, the default version of the more recent versions of MySQL also rejects this syntax.
An efficient solution is:
select a.*
from appointment a
where a.app_id = (select max(a2.app_id)
from appointment a2
where a2.lead_id = a.lead_id
);
With an index on appointment(lead_id, app_id), this should be as fast or faster than George's query.

I think this is much more optimal and efficient way of doing it (sorting next grouping):
SELECT * FROM (
SELECT * FROM appointment
ORDER BY lead_id, app_id DESC
) AS ord
GROUP BY lead_id
this will be useful when you need all other fields too from the table without complicated queries
Result:
lead_id app_id
1 51
2 46
3 49
4 50

Related

How do I get count of a row in SQL

I am a beginner regarding SQL queries so I hope that someone can help me with this.
I have a table that has 2 columns that are called MID and jachtID.
What I need is the count of how many of the same jachtIDS there are with different MIDS attached to them like this:
MID jachtID
89 10
95 10
83 11
The result should look something like this:
MID jachtID count
89 10 2
95 10 2
83 11 1
And I need this for all of the rows
I have tried using
SELECT count(DISTINCT jachtID) FROM table
But this just gives me 1 big number and not the result that I need.
Any help would be appreciated.
You can try the following query:
SELECT
T.MID,
T.jachtID,
jT.total
FROM table T INNER JOIN
(
SELECT
jachtID,
COUNT(*) total
FROM table
GROUP BY jachtID
)AS jT
ON T.jachtID = jT.jachtID
First get count of each jachtID by the following query:
SELECT
jachtID,
COUNT(*) total
FROM table
GROUP BY jachtID
Then make an INNER JOIN between the main table and the above query and get the corresponding jatchtID count thereby.
You might be able to do this with some GROUP BY magic, but I'm not sure, since you want all the rows. Using a sub query will work, though.
SELECT
a.MID,
a.jachtID,
(SELECT count(b.jachtID) FROM table AS b WHERE b.jachtID= a.jachtID) AS `count`
FROM table AS a

Get 5 random rows from MySQL DB [duplicate]

This question already has answers here:
MySQL select 10 random rows from 600K rows fast
(28 answers)
Closed 8 years ago.
I have searched all over for an answer and although people say not to use the ORDER BY RAND() clause, I think for my purposes it is ok as this is for a competition which barely has more than a few hundred records at a time PER competition.
So basically i need to retrieve 5 random records from a competition entries table. However any loyalty customers will received an additional EXTRA entry so example:
compEntryid | firstName | lastName | compID |
1 | bob | smith | 100
2 | bob | smith | 100
3 | jane | doe | 100
4 | sam | citizen | 100
etc
So we are giving the loyalty members a better chance at winning a prize. However im a little worried that the returned result from a usual ORDER BY RAND() can include 2 entries of the SAME person ? What is an optimised method to ensure that we truly have 5 random records but at the same time giving those extra entrants a better or (weighted) chance ? Happy to use multiple queries, sub-queries or even a mix of MySQL and PHP ? Any advice is deeply appreciated thank you !
Bass
EDIT:
These 2 queries both work!
query1
SELECT concat(firstName, " ", lastName) name,id, email
FROM t WHERE
RAND()<(SELECT ((5/COUNT(id))*10) FROM t)
group by email ORDER BY RAND() limit 5;
query2
select distinct
email, id, firstName, lastName from
(
select id ,
email, firstName , lastName , compID, rand()/(select count(*) from t where
email=t1.email
) as rank
from t t1
where compID = 100
order by rank) t2 limit 5;
http://sqlfiddle.com/#!2/73470c/2
If you have a few hundred record, I think that order by rand() solution should be fine:
subquery will order weighting number of entries, but duplicates remains. Parent SELECT will take the first 5 distinct rows.
SELECT DISTINCT firstName ,
lastName ,
compID
FROM
( SELECT compEntryid ,firstName , lastName , compID, rand()/(select count(*)
FROM t
WHERE firstName=t1.firstName AND
lastName = t1.lastName) AS rank
FROM t t1
WHERE compID = 100
ORDER BY rank) t2
LIMIT 5
Fiddle
I think you will need to use a sub query if you want to return a compEntryid.
SELECT t.firstName, t.lastName, t.compID, MIN(compEntryid)
FROM t
INNER JOIN
(
SELECT DISTINCT firstName, lastName, compID
FROM t
ORDER by rand()
LIMIT 5
) t2
ON t.firstName = t2.firstName
AND t.lastName = t2.lastName
AND t.compID = t2.compID
GROUP BY t.firstName, t.lastName, t.compID;
This uses a sub query to get 5 random firstName / lastName / compID. Then joins against the table to get the MIN compEntryId.
However not certain about this. Think it will eliminate the duplicates in the sub query before performing the order / limit, which would prevent someone with more entries having more chances.
EDIT
More of a play and I think I have found a solution. Although efficiency is not one of its strong points.
SELECT MIN(compEntryid), firstName, lastName, compID
FROM
(
SELECT firstName, lastName, compID, compEntryid, #seq:=#seq+1 AS seq
FROM
(
SELECT firstName, lastName, compID, compEntryid
FROM t
ORDER by rand()
) sub0
CROSS JOIN (SELECT #seq:=0) sub1
) sub2
GROUP BY sub2.firstName, sub2.lastName, sub2.compID
ORDER BY MIN(seq)
LIMIT 5
This has an inner sub query that gets all the records in a random order. Around that another sub query adds a sequence number to the records. The outer query groups by the name, etc, and orders by the min sequence number for that name. The compEntryId is just grabbed as the MIN for the name / competition (I am assuming you don't care too much about this).
This way if someone had 5 entries the inner sub query would mix them up in the list. the next sub query would add a sequence number. At this stage those 5 entries could be sequence numbers 1 to 5. The outer one would order by the lowest sequence number for the name and ignore the others, so of those 5 only sequence number 1 would be used and 2 to 5 ignored, with the next selected person being the one with sequence number 6.
This way the more entries they have the more likely they are to be a winner, but can't be 2 of the 5 winners.
With thanks to kiks73 for setting up some sqlfiddle data:-
http://sqlfiddle.com/#!2/cd777/1
EDIT
A solution based on that above by #kiks73. Tweaked to use a non correlated sub query for the counts, and eliminates a few uncertainties. For example with his solution I am not quite sure whether MySQL will chose to do the DISTINCT by implicitly doing a GROUP BY, which would also implicitly do an orderering of the results prior to doing the limit (it doesn't seem to, but I am not sure this behaviour is defined).
SELECT t.firstName ,
t.lastName ,
t.compID,
MIN(rand() / t1.entry_count) AS rank
FROM
(
SELECT firstName, lastName, compID, COUNT(*) AS entry_count
FROM t
GROUP BY firstName, lastName, compID
) t1
INNER JOIN t
ON t.firstName=t1.firstName
AND t.lastName = t1.lastName
AND t.compID = t1.compID
GROUP BY t.firstName, t.lastName, t.compID
ORDER BY rank
LIMIT 5

Matching interests(Nearest neighbour) search in SQL

I'm trying to find users with similar set of interests, with the following schema..
USERS - ID name etc
Interests - ID UID PID
where ID is unique ID for Interests, UIS is user ID and PID is a product ID. I have looked at other similar questions at SO, but none of them had an exact answer.
Example- Let's say I'm interested in getting users with similar interest to John, and this is how to two tables look like ...
ID Name
11 John
12 Mary
13 Scott
14 Tim
ID UID PID
3 12 123
4 12 231
5 12 612
6 13 123
7 13 612
8 14 931
9 14 214
10 11 123
11 11 231
12 11 781
13 11 612
I would like a result with in that order.
I was thinking of doing a set intersection of the user I'm interested in with all other users. It doesn't sound like a very good solution, because it will have to be done everytime a user adds interest or another user is added. Its a small project, and as of now I'll be limiting users to 100. I still think that the above approach will not be efficient at all as it will take 1002 time.
Can someone guide me in the right direction? What are the possible solutions, and which one will be the best with above given constraints. I'm looking at ANN to see if I can use that.
This starts by counting the number of interests that each user has in common with John. The approach is to take all of John's interests, join back to the interests table and aggregate to the the count of common interests. Here is the SQL for that:
select i.uid, COUNT(*) as cnt
from (select i.*
from interests i join
users u
on i.uid = i.id
where u.name = 'John'
) ilist join
interests i
on ilist.pid = i.pid and
ilist.uid <> i.uid -- forget about John
group by i.uid
But, you actually want the list of products, rather than just the count. So, you have to join back to the interests table:
select i.*
from (select i.uid, COUNT(*) as cnt
from (select i.*
from interests i join
users u
on i.uid = i.id
where u.name = 'John'
) ilist join
interests i
on ilist.pid = i.pid and
ilist.uid <> i.uid -- forget about John
group by i.uid
) t join
interests i
on t.uid = i.uid
group by t.cnt, i.uid
The following query finds others users with atleast 2 or more similar interests according to the interests of user 11.
SELECT in2.UID FROM users u
INNER JOIN interest in1 ON (in1.UID = u.ID)
INNER JOIN interest in2 ON (in2.PID = in1.PID AND in2.UID <> u.ID)
WHERE u.ID = 11
GROUP BY in2.UID
HAVING COUNT(in2.UID) >= 2
ORDER BY COUNT(in2.UID) DESC
The ORDER BY ensures that users with the most similar interests ends up first. The HAVING COUNT(in2.UID) >= 2) makes sure the users which are found have atleast 2 or more similar interest.

MySQL Review System for Website

I need help with a query involving a review system set up with the following two tables.
reviews
-------
id date user_id item_id rating review
1 02-2012 40 456 3 'I like it'
2 03-2012 22 342 1 'I don't like it'
3 04-2012 45 548 0 'I hate it'
reviews_thumbs
--------------
review_id user_id like
1 22 1
1 45 -1
2 40 -1
3 22 1
The "reviews_thumbs" table exists to keep track of upvotes and downvotes for the reviews, so that reviews can be rated by quality. In the 'like' column, a 1 is an upvote and a -1 is a downvote. (The rating column in the reviews table is a star system, unrelated.)
When loading reviews, I need to join the reviews_thumbs table in such a way that I know the following details (for each individual review as they are returned):
1. The total number of upvotes
2. The total number of downvotes
3. Whether the current active user has upvoted or downvoted the review
I have accomplished this using the following query, which isn't sitting right with me:
SELECT `reviews`.*,
COUNT(upVoteTable.`user_id`) AS upVotes,
COUNT(downVoteTable.`user_id`) AS downVotes,
COUNT(userUpTable.`user_id`) AS userUp,
COUNT(userDownTable.`user_id`) as userDown
FROM `reviews`
LEFT JOIN `reviews_thumbs` AS upVoteTable
ON upVoteTable.`review_id` = `reviews`.`id`
AND upVoteTable.`like` = 1
LEFT JOIN `reviews_thumbs` AS downVoteTable
ON downVoteTable.`review_id` = `reviews`.`id`
AND downVoteTable.`like` = -1
LEFT JOIN `reviews_thumbs` AS userUpTable
ON userUpTable.`review_id` = `reviews`.`id`
AND userUpTable.`like` = 1
AND userUpTable.`user_id` = :userid
LEFT JOIN `reviews_thumbs` AS userDownTable
ON userDownTable.`review_id` = `reviews`.`id`
AND userDownTable.`like` = -1
AND userDownTable.`user_id` = :userid
WHERE `item_id`=:itemid
GROUP BY `reviews`.`id`
ORDER BY `date` DESC
(And binding the appropriate :userid and :itemid.)
So this query works perfectly and accomplishes what I need it to. But that is a lot of joining, and I'm almost positive there must be a better way to do this, but I can't seem to figure anything out.
Could someone please point me in the right direction on how to accomplish this in a cleaner way?
What I've Tried:
I've tried doing a GROUP_CONCAT, to list a string that contains all the user ids and likes, and to then run a regex to find the user's id to see if they've voted on the review, but this also feels really unclean.
Thank you in advance for any help you may provide.
You could modify reviews_thumbs to look more like this:
reviews_thumbs
--------------
review_id user_id upvote downvote
1 22 1 0
1 45 0 1
2 40 0 1
3 22 1 0
This would effectively store duplicate information, but that's okay when you have a good purpose. You really have 2 things you want to know, and this gives you a quick sum on 2 columns (and a quick subtraction on those results) to get exactly what you are looking for. This cuts you down to querying the reviews_thumbs table to 2x, once for the totals, and once for the users specific action.
Not sure if that improves your query performance, but you could try to do the counting in a sub-select
SELECT `reviews`.*,
(SELECT count(*) FROM reviews_thumbs t WHERE t.review_id =reviews.id AND t.like = 1) AS upVotes
...
FROM reviews
...

Get multiple GROUP BY results per group, or use separate concatenated table

I am working on an auction web application. Now i have a table with bids, and from this table i want to select the last 10 bids per auction.
Now I know I can get the last bid by using something like:
SELECT bids.id FROM bids WHERE * GROUP BY bids.id ORDER BY bids.created
Now I have read that setting an amount for the GROUP BY results is not an easy thing to do, actually I have found no easy solution, if there is i would like to hear that.
But i have come up with some solutions to tackle this problem, but I am not sure if i am doing this well.
Alternative
The first thing is creating a new table, calling this bids_history. In this table i store a string of the last items.
example:
bids_history
================================================================
auction_id bid_id bidders times
1 20,25,40 user1,user2,user1 time1,time2,time3
I have to store the names and the times too, because I have found no easy way of taking the string used in bid_id(20,25,40), and just using this in a join.
This way i can just just join on auction id, and i have the latest result.
Now when there is placed a new bid, these are the steps:
insert bid into bids get the lastinserteid
get the bids_history string for this
auction product
explode the string
insert new values
check if there are more than 3
implode the array, and insert the string again
This all seems to me not a very well solution.
I really don't know which way to go. Please keep in mind this is a website with a lot of bidding's, they can g up to 15.000 bidding's per auction item. Maybe because of this amount is GROUPING and ORDERING not a good way to go. Please correct me if I am wrong.
After the auction is over i do clean up the bids table, removing all the bids, and store them in a separate table.
Can someone please help me tackle this problem!
And if you have been, thanks for reading..
EDIT
The tables i use are:
bids
======================
id (prim_key)
aid (auction id)
uid (user id)
cbid (current bid)
created (time created)
======================
auction_products
====================
id (prim_key)
pid (product id)
closetime (time the auction closses)
What i want as the result of the query:
result
===============================================
auction_products.id bids.uid bids.created
2 6 time1
2 8 time2
2 10 time3
5 3 time1
5 4 time2
5 9 time3
7 3 time1
7 2 time2
7 1 time3
So that is per auction the latest bids, to choose by number, 3 or 10
Using user variable, and control flow, i end up with that (just replace the <=3 with <=10 if you want the ten auctions) :
SELECT a.*
FROM
(SELECT aid, uid, created FROM bids ORDER BY aid, created DESC) a,
(SELECT #prev:=-1, #count:=1) b
WHERE
CASE WHEN #prev<>a.aid THEN
CASE WHEN #prev:=a.aid THEN
#count:=1
END
ELSE
#count:=#count+1
END <= 3
Why do this in one query?
$sql = "SELECT id FROM auctions ORDER BY created DESC LIMIT 10";
$auctions = array();
while($row = mysql_fetch_assoc(mysql_query($sql)))
$auctions[] = $row['id'];
$auctions = implode(', ', $auctions);
$sql = "SELECT id FROM bids WHERE auction_id IN ($auctions) ORDER BY created LIMIT 10";
// ...
You should obviously handle the case where, e.g. $auctions is empty, but I think this should work.
EDIT: This is wrong :-)
You will need to use a subquery:
SELECT bids1.id
FROM ( SELECT *
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
WHERE bid2.id IS NULL)
ORDER BY bids.created DESC
LIMIT 10
So the subquery performs a left join from bids to itself, pairing each record with all records that have the same auctionId and and a created date that is after its own created date. For the most recent record, there will be no other record with a greater created date, and so that record would not be included in the join, but since we use a Left join, it will be included, with all the bids2 fields being null, hence the WHERE bid2.id IS NULL statement.
So the sub query has one row per auction, contianing the data from the most recent bid. Then simply select off the top ten using orderby and limit.
If your database engine doesn't support subqueries, you can use a view just as well.
Ok, this one should work:
SELECT bids1.id
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
GROUP BY bids1.auctionId, bids1.created
HAVING COUNT(bids2.created) < 9
So, like before, left join bids with itself so we can compare each bid with all the others. Then, group it first by auction (we want the last ten bids per auction) and then by created. Because the left join pairs each bid with all previous bids, we can then count the number of bids2.created per group, which will give us the number of bids occurring before that bid. If this count is < 9 (because the first will have count == 0, it is zero indexed) it is one of the ten most recent bids, and we want to select it.
To select last 10 bids for a given auction, just create a normalized bids table (1 record per bid) and issue this query:
SELECT bids.id
FROM bids
WHERE auction = ?
ORDER BY
bids.created DESC
LIMIT 10
To select last 10 bids per multiple auctions, use this:
SELECT bo.*
FROM (
SELECT a.id,
COALESCE(
(
SELECT bi.created
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), '01.01.1900'
) AS mcreated
COALESCE(
(
SELECT bi.id
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), 0)
AS mid
FROM auctions a
) q
JOIN bids bo
ON bo.auction >= q.auction
AND bo.auction <= q.auction
AND (bo.created, bo.id) >= (q.mcreated, q.mid)
Create a composite index on bids (auction, created, id) for this to work fast.

Categories