Skip to main content

Blog Post

Rescuing (really bad) Drupal Installs with SQL

A strategy for building queries with Inline Views

It’s a new client. You’re checking off the list - the one you use for estimating when you’re moving clients to a new version of Drupal, or rebuilding a site. Things look good on quick inspection, no errors all over the place, but then you start seeing a ton of custom blocks and features. No worries, we’ll check out their custom modules.

Here’s where it gets interesting: there’s a ton of raw SQL queries. What for? Oh man, the modules not only read but write to core tables without any sort of structure or hooks, so now you check the theme. Same thing. You race up the file structure and run a compare against Drupal core to confirm your sneaking suspicions… the install is hacked to the moon and back, and the last wrench in the bucket: the client is requiring you save and migrate all of the data.

Usually at this point, you start thinking of how to manage the rescue, how much time it will take to organize the data, understand it, and execute the plan so you can get the client a good estimate on what it will take. You start querying the tables, trying to get a clear picture of the data you need to save that can’t be moved without some custom work and un-entanglement. This is where SQL Inline Views come in (combined with Temporary Tables, they are like MSSQL/Oracle/DB2 Common Table Expressions, executed by the WITH statement).

What are SQL Inline Views ?

The inline view is a method for writing queries that allows you to build upon a series of queries to get your final result. Most of us may be familiar with writing subqueries or including inline views in our SQL, but many times we don’t tend to think of exactly how to build a query from the ground up to work with a complex and often times poorly structured source data.

 
/*
 * Example: get the latest owner.
 */
SELECT clientdata.nid,
      clientdata.customvalue as uid
 
 FROM clientdata,
(
 SELECT clientdata.nid,
        MAX(clientdata.date) as max_date
   FROM clientdata
  WHERE clientdata.customtype = “owner”
) latest_owner
 
WHERE clientdata.nid  = latest_owner.nid
  AND clientdata.uid  = latest_owner.uid
  AND clientdata.date = latest_owner.max_date
  AND clientdata.customtype = “owner”
 

Example SQL Inline Views for converting bad Drupal 6 to Drupal 7 fields

The Workflow

In most cases when writing SQL queries, you start at the result and work your way backwards, building the select statement and collecting the tables you need. When rescuing a Drupal install, the most prominent issue I’ve seen is where data you need to collect in a single new content type or field lies disparate between multiple tables that have complex relationships or do not have direct keys linking them.

tables node_custom, users, and clientdata

Example Bad Table Structure - Yes, I’ve seen even worse than this!

In this example, we’re going to migrate a custom module and node to a new custom node with fields for each of the above rows (owner, featured writer, body etc.) We start by separating each item you need to pull in your query out into its own query. You want to think of each thing you need to retrieve as its own object. A good example is content that is created by one user but belongs to another via the owner field as with the example bad table above. You would want a query for the users, a query for the latest owner, and a query for the content. Once you have a series of queries, the next step is to start integrating them for the final result.

 
/*
 * latest_owner
 * We need to find the latest owner to insert into the field data table.
 */
SELECT clientdata.nid,
      MAX(clientdata.date) as max_date
 FROM clientdata
WHERE clientdata.customtype = “owner”
 
/*
* owners
* We need to find who the owner is once we have the latest owner.
*/
SELECT clientdata.nid,
      clientdata.customvalue as uid
 FROM clientdata,
      latest_owner
WHERE clientdata.nid  = latest_owner.nid
  AND clientdata.uid  = latest_owner.uid
  AND clientdata.date = latest_owner.max_date
  AND clientdata.customtype = “owner”
 
/*
* Our final result that should feed into code to put each input field
* into a D7 field.
* /
SELECT node.nid,
      users.uid  as author,
      owners.uid as owner,
      node.body
 FROM node_custom node,
      users,
      owners
WHERE node.author = users.username
  AND node.nid    = owners.nid
 

Example Queries

The easiest way to accomplish rewriting your separate queries into a single query with inline views is to work in reverse order. Take your result, and integrate the next query that it needs to pull information from. In some cases, you may end up with multiple subqueries to create your inline view - and in other cases, you may be able to add the previous query on without creating an inline view at all.

 
/*
* Get all custom nodes with an owner and pull only the latest owner to put
* into our new D7 field body table. This assumes we have another query
* elsewhere that populates the field revision tables with the remaining
* owner data and that we have another content type in D7 for variations of
* this custom content without an owner.
*/
SELECT node.nid,
      users.uid  as author,
      owners.uid as owner,
      node.body
 
 FROM node_custom node,
      users,
(
 SELECT clientdata.nid,
        clientdata.customvalue as uid
   FROM clientdata,
 (
   SELECT clientdata.nid,
          MAX(clientdata.date) as max_date
     FROM clientdata
    WHERE clientdata.customtype = “owner”
 ) latest_owner
  WHERE clientdata.nid  = latest_owner.nid
    AND clientdata.uid  = latest_owner.uid
    AND clientdata.date = latest_owner.max_date
    AND clientdata.customtype = “owner”
) owners
 
WHERE node.author = users.username
  AND node.nid    = owners.nid
 

Example Combined Queries to create an Inline View

The final thing to remember is that you don’t always need to break your queries down like this - most of the time Drupal installs don’t get so incredibly complex. In the rare case where you are faced with the near impossible, Inline Views are the precision tools you need to help pull manageable data out of near chaos.

Author’s Note: MSSQL/DB2/Oracle Common Table Expressions accomplish everything we’re doing here in a neater and more accessible format, but until they are made available to MySQL, we can use Inline Views to accomplish much of the same, albeit without the added benefit of recursion.

Additional Resources
Inline WYSIWYG Editing in Drupal
The Power of Maps and Drupal (Webinar)
How To Quickly Change Your Drupal Homepage