title: How to select a range of rows from a multiple column primary key?
body: <p>I'm trying to chunk through rows in MySQL 5.5 and to do this I want to select a range between two primary keys (which I can get easily). This is trivial when the primary key is only one column. However, some of the tables I need to chunk through have multiple columns in the primary key, and I haven't figured out how to make this work in a single prepared statement.</p>
  
  <p>Here's an example table with some data:</p>
  
  <pre><code>CREATE TABLE test (
    a INT UNSIGNED NOT NULL,
    b INT UNSIGNED NOT NULL,
    c INT UNSIGNED NOT NULL,
    d VARCHAR(255) DEFAULT '', -- various data columns
    PRIMARY KEY (a, b, c)
  ) ENGINE=InnoDB;
  
  INSERT INTO test VALUES 
  (1, 1, 1),
  (1, 1, 2),
  (1, 1, 3),
  (1, 2, 1),
  (1, 2, 2),
  (1, 2, 3),
  (1, 3, 1),
  (1, 3, 3),
  (2, 1, 1),
  (2, 1, 2),
  (2, 2, 2),
  (2, 3, 1),
  (2, 3, 3),
  (3, 1, 2),
  (3, 1, 3),
  (3, 2, 1),
  (3, 2, 2),
  (3, 2, 3),
  (3, 3, 1),
  (3, 3, 3);
  </code></pre>
  
  <p>If I had two primary keys like (1, 1, 3) and (3, 2, 1), the following statement would work. a1, b1, and c1 are the values from the first primary key, and a2, b2, and c2 are the values from the second primary key:</p>
  
  <pre><code>SELECT * FROM test WHERE a = a1 AND b = b1 AND c &gt;= c1
  UNION
  SELECT * FROM test WHERE a = a1 AND b &gt; b1
  UNION
  SELECT * FROM test WHERE a &gt; a1 AND a &lt; a2
  UNION
  SELECT * FROM test WHERE a = a2 AND b &lt; b2
  UNION
  SELECT * FROM test WHERE a = a2 AND b = b2 AND c &lt;= c2
  </code></pre>
  
  <p>Or</p>
  
  <pre><code>SELECT * FROM test WHERE a = 1 AND b = 1 AND c &gt;= 3
  UNION
  SELECT * FROM test WHERE a = 1 AND b &gt; 1
  UNION
  SELECT * FROM test WHERE a &gt; 1 AND a &lt; 3
  UNION
  SELECT * FROM test WHERE a = 3 AND b &lt; 2
  UNION
  SELECT * FROM test WHERE a = 3 AND b = 2 AND c &lt;= 1
  </code></pre>
  
  <p>Which gives</p>
  
  <pre><code>(1, 1, 3),
  (1, 2, 1),
  (1, 2, 2),
  (1, 2, 3),
  (1, 3, 1),
  (1, 3, 3),
  (2, 1, 1),
  (2, 1, 2),
  (2, 2, 2),
  (2, 3, 1),
  (2, 3, 3),
  (3, 1, 2),
  (3, 1, 3),
  (3, 2, 1)
  </code></pre>
  
  <p>But the above fails when the first column is the same, e.g. (1, 2, 2) and (1, 3, 1). In this case, the 2nd and 4th SELECT select too much.</p>
  
  <pre><code>SELECT * FROM test WHERE a = 1 AND b = 2 AND c &gt;= 2
  UNION
  SELECT * FROM test WHERE a = 1 AND b &gt; 2
  UNION
  SELECT * FROM test WHERE a &gt; 1 AND a &lt; 1
  UNION
  SELECT * FROM test WHERE a = 1 AND b &lt; 3
  UNION
  SELECT * FROM test WHERE a = 1 AND b = 3 AND c &lt;= 1
  </code></pre>
  
  <p>Which gives</p>
  
  <pre><code>(1, 1, 1), -- erroneously selected from: SELECT * FROM test WHERE a = 1 AND b &lt; 3
  (1, 1, 2), -- erroneously selected from: SELECT * FROM test WHERE a = 1 AND b &lt; 3
  (1, 1, 3), -- erroneously selected from: SELECT * FROM test WHERE a = 1 AND b &lt; 3
  (1, 2, 1), -- erroneously selected from: SELECT * FROM test WHERE a = 1 AND b &lt; 3
  (1, 2, 2),
  (1, 2, 3),
  (1, 3, 1),
  (1, 3, 3)  -- erroneously selected from: SELECT * FROM test WHERE a = 1 AND b &gt; 2
  </code></pre>
  
  <p>The desired output is</p>
  
  <pre><code>(1, 2, 2),
  (1, 2, 3),
  (1, 3, 1)
  </code></pre>
  
  <p>I would like a single statement that works with all primary key ranges, including identical values for the first and second columns. I also have tables with 4 columns in the primary key, and I'll extend the pattern in that case.</p>
  
  <p>I would like a single statement per table instead of creating queries on the fly because the query will be executed up to a million times as I chunk through the tables. Some of the tables have over 100M rows.</p>
  
  <p>I would rather avoid constructing multiple statements as I have hundreds to write following this pattern, and writing more would be significantly more work. I will do this if it's the only option.</p>
  
  <p>I currently use parametrized queries, and generate the values programmatically from the two primary keys, taking care of required duplicate values (the a1 x3, b1 x2, a2 x3, b2 x2 in the above example) in the application layer. So passing duplicate values for parameters is simple for me to do.</p>
  
  <p>My best guess at this point is duplicate the SELECTs with an additional part of the WHERE clause comparing the values of the columns of the primary keys.</p>