views:

52

answers:

1

I have two tables: locations and listings.

locations
id title address latitude longitude

listings
id location info status

SELECT locations.title, 
       locations.address, 
       ( 3959 * acos( cos( radians('".$center_lat."') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('".$center_lng."') ) + sin( radians('".$center_lat."') ) * sin( radians( latitude ) ) ) ) AS distance      
  FROM locations 
ORDER BY distance

This will list the locations in order by location with the users provided latitude and longitude. Works perfect, but what I really want to do is..

  1. List one "listings" per location, and have the locations remain in order.
  2. If a location has more then one "listings" have it be completely random.

Would it be better to do this all in one SQL query? Or populate all the locations that have atleast one "listings", then use another query to select a random "listings" for that location?

UPDATE

Provided create table:

CREATE TABLE `listings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `token` varchar(4) DEFAULT NULL,
  `location` varchar(45) DEFAULT NULL,
  `info` varchar(45) DEFAULT NULL,
  `status` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;

CREATE TABLE `locations` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(45) DEFAULT NULL,
  `address_street` varchar(45) DEFAULT NULL,
  `addrees_city` varchar(45) DEFAULT NULL,
  `address_state` varchar(45) DEFAULT NULL,
  `address_zip` varchar(45) DEFAULT NULL,
  `latitude` decimal(10,6) DEFAULT NULL,
  `longitude` decimal(10,6) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+1  A: 

To output the locations.title if the location has at least 1 row associated with it in the "listings" table, use:

SELECT loc.title
  FROM LOCATIONS loc
 WHERE EXISTS(SELECT NULL
                FROM LISTING li
               WHERE li.location = loc.id)

Use:

  SELECT x.title, 
         x.address,
         x.distance,
         x.info,
         x.status
    FROM (SELECT loc.title, 
                 loc.address, 
                 ( 3959 * acos( cos( radians('".$center_lat."') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('".$center_lng."') ) + sin( radians('".$center_lat."') ) * sin( radians( latitude ) ) ) ) AS distance,
                 li.*,
                 CASE 
                   WHEN @location = loc.id THEN @rownum := @rownum + 1
                   ELSE @rownum := 1
                 END AS rank,
                 @location := loc.id
            FROM LOCATIONS loc
       LEFT JOIN LISTINGS li ON li.location = loc.id
            JOIN (SELECT @rownum := 0, @location := -1) r
        ORDER BY loc.id, RAND()) x
   WHERE x.rank = 1
ORDER BY x.distance

Using MySQL 5.1.49-community, I've successfully rendered the desired results with the query above.

I'm unable to reproduce the OP's duplicated row using:

CREATE TABLES

CREATE TABLE `locations` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(45) DEFAULT NULL,
  `address_street` varchar(45) DEFAULT NULL,
  `address_city` varchar(45) DEFAULT NULL,
  `address_state` varchar(45) DEFAULT NULL,
  `address_zip` varchar(45) DEFAULT NULL,
  `latitude` decimal(10,6) DEFAULT NULL,
  `longitude` decimal(10,6) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1$$

CREATE TABLE `listings` (
  `id` int(11) NOT NULL,
  `token` varchar(4) DEFAULT NULL,
  `location` varchar(45) DEFAULT NULL,
  `info` varchar(45) DEFAULT NULL,
  `status` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$

INSERT statements:

INSERT INTO `locations` 
VALUES (1,'John\'s Ice Cream','1701 S Martin Luther King Jr Blvd','Lansing','MI','48910','42.714672','-84.567139'),
       (2,'7 Eleven','3500 Okemos Rd','Okemos','MI','48864','42.683331','-84.431709'),
       (3,'Kurt\'s Pizza','213 Ann St.','East Lansing','MI','48823','42.736053','-84.481636'),
       (4,'Walmart','16275 National Pkwy','Lansing','MI','48906','42.780350','-84.637238'),
       (5,'Alex\'s Hot dog Shop','8505 Delta Market Dr','Lansing','MI','48917','42.739830','-84.677330');

INSERT INTO `listings` 
VALUES (19,'39c4','1','5 gallons for $8','active'),
       (21,'89dF','4','2 mens shirts for $2','active'),
       (22,'67oP','1','Ice cream cones for $1','active'),
       (23,'5tG8','2','Large soft drinks only $0.99!','active');
OMG Ponies
Look great, but I get a syntax error near '@location := loc.id FROM LOCATIONS loc LEFT JOIN LISTINGS li' Cant seem it figure it.
Chad Whitaker
@Chad Whitaker: I was missing a comma after "AS rank ", corrected.
OMG Ponies
It works now, but.. 1. It displays "locations" that have no "listings" 2. The item doesn't seem to be at random if there is more then one for a location.
Chad Whitaker
@Chad Whitaker: Your original ordering didn't including if there was a `listing` associated, so the `LEFT JOIN` would produce identical results. Change "LEFT JOIN LISTINGS " to "JOIN LISTINGS " if you don't want to see `LOCATION` records without at least one `LISTING` record.
OMG Ponies
@Chad Whitaker: The `ORDER BY loc.id, RAND()` ensures that beyond the `LOCATION.id` ordering, the rows are as random as MySQL will allow. Keep in mind that the random ordering of two `LISTING` records isn't going to appear all that random ;)
OMG Ponies
@Chad Whitaker: Yes, the inner query will have to process when there's more than one `LISTING` record associated to a `LOCATION`, but the outer query--the one that actually returns data--will only return rows based on the `rank` value (which there'll only be one per `LOCATION` record. Thanks for the PayPal offer, but an upvote for my answer would be fine.
OMG Ponies
Sorry! but when I remove LEFT from LEFT JOIN LISTINGS, it will display the location multiple times for each listing it has, I only want to display the location once with its (locations.title) Title and a random listing (listings.info) next to it. Thank you for your help.
Chad Whitaker
@Chad Whitaker: Your updated comment makes a more sense, but my response explains why there's no concern for sake of the filteration on the outer query. I'm not in the habit of accepting $ for answering questions on SO. I'm flattered, but you have enough reputation (over 15) to upvote now.
OMG Ponies
With LEFT JOIN: http://cl.ly/2YQv (displays locations that have no listings ie. "Kurt's Pizza", "Alex's Hot dogs"; With just JOIN: http://cl.ly/2YbK Displays John's Icecream twice, instead of just once and showing listings.info at random between the two ("5 Gallons for $8" and "Ice cream cones for $1")
Chad Whitaker
@Chad Whitaker: Thx, I see what you are saying but I'd require your data to see if I can reproduce the issue. It doesn't makes sense how a rank value per location could be duplicated, unless you have duplicate `LOCATION.title` with different id values.
OMG Ponies
locations table: http://cl.ly/2YeB ; Listings table: http://cl.ly/2YC5 Let me know if you need any clarifications.
Chad Whitaker
OMG Ponies
I re did my database and I get the same results as before. Can you maybe repost your sql select? Even without the `distance` calculation?
Chad Whitaker
@OMG Ponies - If I made a simple map of what I need done, and the output, could I hire you? I know it something that would take you less then 1-2 hours to create. You appear to be very experienced. Thank you for all your help!
Chad Whitaker
@Chad Whitaker: I'd rather teach you to do it yourself. You need to provide the CREATE TABLE statements for both tables involved.
OMG Ponies
@OMG Ponies - I have updated the post with the CREATE TABLE statements. Once again, thank you very much for your help.
Chad Whitaker
@Chad Whitaker: I used your table statements, and reproduced your output. The only difference I can see is the use of MyISAM when I tested using InnoDB. Is there a particular need for you to use MyISAM?
OMG Ponies
No, I dont understand the use of engine. How could I change this?
Chad Whitaker
@Chad Whitaker: MySQL has different engines for different functionality. MyISAM doesn't support transactions or referential integrity, but is the only one to support Full Text indexing `
OMG Ponies
That may have been the issue. But back to the issue where the listing isn't random. For example "John's Ice Cream" has 2 listings, but only the first listing (5 gallons for $8) will display. I ran the query over 50 times with the same result.
Chad Whitaker
@Chad Whitaker: Try a larger sample. I was seeing the random work in the inner query, but wonder about query cache being responsible for the final resultset not changing much. But I think I've done enough to at least merit an upvote.
OMG Ponies
Yes, I will definitely upvote you now. I hope you can still help with this issue. This project is overdue and I'm stuck at this point..... I added several rows from location "1" (John's Ice cream) and nothing is random. I actually found it is grabbing the listings withe the lowest id. Maybe you can send me your query that you are using so I can see the results. Thank you!
Chad Whitaker
@Chad Whitaker: I was only running the inner query--the stuff inside the FROM clause. I was starting to think that you'd be better off using PostgreSQL--8.4+ has analytical functions, which is what this query would use if MySQL had the functionality.
OMG Ponies
There isn't a way to make this work using Mysql then?
Chad Whitaker
@Chad Whitaker: There might be, but it'd be an uglier hack than what I've provided.
OMG Ponies
What about this? Can you provided a query that will search and list just the locations with the locations title where the location has at least 1 listing?
Chad Whitaker
@Chad Whitaker: Yes--do you need to see the info from the listings table as well in the resultset? I could get one column from `LISTING`; two or more columns from LISTING couldn't be guaranteed to be from the same LISTING record if there are more than one associated to a LOCATION record.
OMG Ponies
No, just to be able to output the locations.title if the location has at least 1 row associated with it in the "listings" table
Chad Whitaker
@Chad Whitaker: See update to my answer--it's at the top
OMG Ponies
Thanks. This is what I did. I called the locations with one query, then I pulled the listings at random with a inner query using php. http://pastebin.com/FcS3YbWs Do you think this is okay, or would this be very hard in the database? What is your input?
Chad Whitaker
@Chad Whitaker: My query makes one database trip for all the info; yours will make one for the list of locations, and a trip for each location returned. The more locations that are returned, the more load on the database.
OMG Ponies
I see. So why can't I mix SELECT loc.id, loc.title, ( 3959 * acos( cos( radians('42.7159912') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('-84.561563') ) + sin( radians('42.7159912') ) * sin( radians( latitude ) ) ) ) AS distance FROM locations locWHERE EXISTS(SELECT NULL FROM listings li WHERE li.location = loc.id) `AND` SELECT listings.token, listings.info FROM listings WHERE listings.location = '".$locations['id']."' ORDER BY RAND() into one query?
Chad Whitaker
@Chad Whitaker: 1) you'd use a JOIN, not EXISTS for that 2) the join doesn't return a single LISTING record per LOCATION record. You could select from LISTING in a subselect, but you can only get one column back from a subselect. Realistically, what you want is beyond MySQL's capability to provide in a means that scales to handel any number of LOCATION records.
OMG Ponies
I see. Well I use what I showed you in pastebin for now. And if we grow much larger I will hopefully hire a programmer. Thank you very much for your help OMG Ponies. Fantastic help!
Chad Whitaker
@Chad Whitaker: Glad to help, this pushed the bounds of what I've used the pseudo ranking.
OMG Ponies