16

I have a table of postcodes and I want to update each postcode with its 3 nearest neighbours. Ie to fill in the blanks in this table:

postcode  nearestPostcode1  nearestPostcode2  nearestPostcode3
_______________________________________________________________

KY6 1DA      -                -                  -
KY6 1DG      -                -                  -
KY6 2DT      -                -                  -
KY6 1RG      -                -                  -
....

I've figured out a SELECT query to find the nearest postcodes and here is one clumsy way the first row could be updated:

update table1 set 
nearestPostcode1 = (select query for returning the first nearest postcode),
nearestPostcode2 = (select query for returning the second nearest postcode),
nearestPostcode3 = (select query for returning the third nearest postcode)
where postcode = 'KY6 1DA';

However this will result in 3 select queries being run for each row update. It would be more efficient if there was some way to do what is expressed by this pseudo code:

update table1 set 
(nearestPostcode1, nearestPostcode2, nearestPostcode3) = 
(select query to return the 3 nearest postcodes)
where postcode = 'KY6 1DA';

The 'select query' in the above looks like this:

select postcode from postcodeTable 
order by <equation to calculate distance> ASC 
limit 3

Is there anyway for the rows returned from the select to be put into a form that they can be used to update multiple fields? Thanks.

spiderplant0
  • 3,872
  • 12
  • 52
  • 91

4 Answers4

19
Update Table1
    Cross Join  (
                Select Min( Case When Z1.Num = 1 Then Z1.postcode End ) As PostCode1
                    , Min( Case When Z1.Num = 2 Then Z1.postcode End ) As PostCode2
                    , Min( Case When Z1.Num = 3 Then Z1.postcode End ) As PostCode3
                From    (
                        Select postcode 
                            , @num := @num + 1 As Num
                        From postcodeTable 
                        Where postcode = 'KY6 IDA'
                        Order By <equation to calculate distance> ASC 
                        Limit 3
                        ) As Z1
                ) As Z
Set nearestPostCode1 = Z.PostCode1
    , nearestPostCode2 = Z.PostCode2
    , nearestPostCode3 = Z.PostCode3
Where Table1.postcode =  'KY6 IDA'
Thomas
  • 63,911
  • 12
  • 95
  • 141
  • Thanks @Thomas. This worked. However I need to expand on the code and I cant figure out the meaning of "Min( Case When Z1.Num = 1 Then Z1.postcode End ) As PostCode1". I thought "Min" was for use with "group by". I tried to replace them with "IF(@rownum=1,postcode,'') ) AS PostCode1" etc but I cant get this to work. Can you explain how the "Min(Case..." works please. – spiderplant0 May 13 '11 at 13:18
  • @spiderplant0 - Typically, Min is used with Group By however, if every column is wrapped in an aggregate function (like the "Z" derived table) , then just one row is returned. – Thomas May 13 '11 at 16:45
  • @spiderplant0 - You should use `Case` instead of `If` as `Case` is ANSI compliant. Notice that I have to calculate the order in its own derived table (Z1) and then evaluate my expressions like Z1.Num = 1. – Thomas May 13 '11 at 16:46
1

You can do something similar to this:

UPDATE table1
SET
nearestPostcode1 = pc1,
nearestPostcode2 = pc2,
nearestPostcode3 = pc3
FROM 
(SELECT pc1, pc2, pc3 FROM ....) t
WHERE 
postcode = 'KY6 1DA';

I found this related question on Stackoverflow on how to transform columns to rows:

MySQL - Rows to Columns

In your case, you can do something like

SELECT 
IF(@rownum=1,postcode,'') ) AS pc1, 
IF(@rownum=2,postcode,'') ) AS pc2, 
IF(@rownum=3,postcode,'') ) AS pc2, 
FROM
(SELECT postcode 
FROM postcodeTable 
ORDER BY <equation to calculate distance> ASC 
LIMIT 3)

Here is a hack to simulate the ROW_NUMBER() functionality in MySQL [1]:

SELECT @rownum:=@rownum+1 rownum, t.*
FROM (SELECT @rownum:=0) r, mytable t;
Community
  • 1
  • 1
rkg
  • 5,559
  • 8
  • 37
  • 50
  • Thanks @peter However are pc1, pc2, and pc3 columns? My select query return 3 rows not 3 columns. I.e. it returns 3 postcodes that are the closest. I've added a bit more to my original question to explain this a bit more. – spiderplant0 May 12 '11 at 22:43
  • @spiderplant0: Modified the answer. I haven't actaully run the query so just consider it as a psuedo-code. – rkg May 12 '11 at 23:08
  • 7
    I got error saying "FROM" cannot be placed at that location – Dinesh Apr 11 '17 at 16:13
0

I think you could do this with the pseudo-code:

REPLACE INTO table1 (postcode, nearestPostcode1, nearestPostcode2, nearestPostcode3)
    SELECT "KY6 1DA", col1, col2, col3 FROM myTable WHERE ...;

it'd be easier to specify it seeing the real SQL.

Note the first column is specified as a constant in quotes. For this to work postcode must be a UNIQUE or PRIMARY index.

James C
  • 14,047
  • 1
  • 34
  • 43
  • Thanks @james-c I should have explained that my select query return 3 rows not 3 columns. I.e. it returns 3 postcodes that are the closest. I've added a bit more to my original question to explain this a bit more. – spiderplant0 May 12 '11 at 22:48
  • the actual sql for the select query to find the 3 closest postcodes is SELECT postcode FROM postcode_table where lat > " . ($lat - $deltaLat) . " and lat < " . ($lat + $deltaLat) . " and lng > " . ($lng - $deltaLng) . " and lng < " . ($lng + $deltaLng) . " and active = '1' ORDER BY POW(lat - $lat, 2) + POW((lng - $lng) * $lat2LngAdjust, 2) ASC limit 3 – spiderplant0 May 12 '11 at 22:50
  • I suspect you could do this with stored procedures but I'm afraid I don't have the know how. I would probably handle this with some simple wrapping logic in (e.g. PHP) that runs the first query, extracts the values and then creates the update statement. In theory you could run the `SELECT` queries for multiple postcodes, batch up the results and then create a big multi-row `REPLACE` statement. – James C May 12 '11 at 22:52
  • For the closest postcodes couldn't you just use pythag's to work out the closest ones? – James C May 12 '11 at 22:53
  • @James-C thanks. The code in the 'where' statement narrows the search down quickly and the 'order by' fine tunes it. I tried it with just the Pythagerous in the 'order by' and it was very slow (1.8 million postcodes). – spiderplant0 May 12 '11 at 23:01
-1

Anytime I see a table with columns that have 1-up counters after their names, I get concerned.

In general, it is a Bad Idea (TM) to store data that can be calculated from data that is already stored. What happens if your application all of a sudden needs the 4 closest postal codes? What if the postal code boundaries change?

Assuming the distance calculation isn't very complex, you'll be better off in the long run not explicitly storing this data.

Chris Morgan
  • 2,080
  • 15
  • 19
  • 1
    @chris-morgan Calculating the 3 closest postcodes will take a very long time to run. But this OK as it only needs to be done once, and the subsequent database queries that need this information need it fast. The decision on whether or not something is a bad idea virtually always depends on the context. – spiderplant0 May 12 '11 at 22:56
  • @spiderplant0 I definitely appreciate that context matters, I just always like to err on the side of flexibility, since change is generally guaranteed. – Chris Morgan May 12 '11 at 23:10