Welcome to Foxite.COM Community Weblog Sign in | Join | Help

Adding Columns in SQL SELECT statements

A common requirement when working with data, irrespective of its source is to be able to add additional columns to the result set ‘on the fly’. This is pretty straightforward if you simply want a new, blank column, just define it directly in the query using the SPACE() function like this:

SELECT SPACE(30) AS newcol FROM nametable

(Note: The word “AS” is not actually required in VFP (or in SQL Server) syntax, but some SQL dialects do require it, and, in any case, I think it improves the readability of the query). Now. let’s suppose we have some data in a table like this:

 

cfirst             

clast              

iintcol   

nnum1

nnum2

Andy               

Kramek             

0

123.45

3456.78

Vladimir           

Andropovitch       

3

1111.65

654.32

 

If we needed to concatenate the name columns to create a ‘full name’, then we can do so like this:

SELECT (ALLTRIM( cfirst ) + “ ” + ALLTRIM( clast )) AS fullname FROM sample

Although, in practice even this is not quite as straightforward as it may seem at first glance. The issue here is that when creating a ‘computed column’ (which is what we are doing here) VFP creates the definition for that column based on the length of the existing columns in concatenation. So if the “cfirst” and “clast” fields are each defined as C(20), the result set is defined with a field that is C(41). In other words, the 20 characters for the first field, one for the space and 20 characters for the last field.

This is fine, although it may be a little wasteful of space, but it won’t cause us to lose any data and if we really wanted to trim it down we could simply use the PADR() function to force the width to a specific value:

SELECT PADR( ALLTRIM( cfirst ) + “ ” + ALLTRIM( clast ), 30) AS fullname FROM sample

However, if we are also converting between data types (numeric to character for example) then we run into a potential problem because in this case VFP doesn’t necessarily know how long each value might be. All it can do is to base the definition on the length of the first values it finds. So a query like this:

SELECT TRANSFORM( nnum1 ) AS cvalue FROM sample

would return a result set with the ‘cvalue’ column defined as C(6) – in other words, the number of characters in the first value in the table. This, of course, means that the second value gets truncated because it actually contains seven characters. So now it becomes really important to ensure that we specify the format of the field to be large enough to handle any possible value and we can use the PADR() function to handle the formatting once we have transformed the data, like this:

SELECT PADR( TRANSFORM( nnum1 ), 10) AS cvalue FROM sample

But the introduction of the CAST() function in VFP 9.0 provides an alternative because it allows us to tell VFP directly how we want to output the result. So, in VFP 9.0 we can write the first query like this:

SELECT CAST( ALLTRIM( cFirst ) + “ ” + ALLTRIM( cLast ), AS CHAR(30)) AS fullName FROM sample

and the second like this:

SELECT CAST( nnum1 AS CHAR(10)) AS cvalue FROM sample

This is all fine when we are dealing with existing columns, but what happens if we need to create a new column with a specific data type? Well it’s easy enough to create character columns, just use the SPACE() function (or even PADL()) to create the required width:

SELECT *, SPACE(30) AS newstring FROM sample

Similarly if you needed a new currency column, you would define it as “$0” and a new date by using an empty date string “{}”, a new decimal column by using a string of zeroes, logical fields using .F. and so on:

SELECT $0 AS yamount, {} AS dpaid, 00000.00 AS newbal, .F. AS lCleared FROM sample

Given this, you might be tempted to think that to create an integer column you can simply use the following:

SELECT 0 AS newint FROM sample

after all, when you create an integer column in a table it is initialized to “0”, so it seems reasonable that telling VFP to create a column for the value “0” would result in an integer. Unfortunately that is not the case! What actually happens is that VFP creates a Numeric column with width = 1 and decimals = 0. The result is that you can only store the values 0 – 9 in the result. Not quite what we wanted!

So how do we get an integer? Well before VFP 9.0 this was a little tricky and there were two possible ways of doing it.

First (and simplest) you could define the computed column with sufficient width to hold an integer.

SELECT *, 0000000000 AS newint FROM sample

The result is not really an integer column however, merely a larger numeric column (N(10,0)) and I always found counting those zeroes problematic anyway. To create a true integer column in a result set we have to resort to a little trick that involves creating a Cartesian product.

A Cartesian product occurs when a query joins two tables without specifying a valid condition. The result is that every record in the first table is joined with every record in the second. The result set contains, therefore the number of records that is obtained by multiplying the record count of the first table by the record count of the second. This can rapidly generate VERY large result sets. A query like this

SELECT * FROM table1, table2

Assuming 100 records in the first table and 1000 records in the second, would generate a result containing 100,000 records. Sounds like an error eh? So how can this help us to create a true integer column?

Well, if we create a cursor named “dummy” which has one column (defined as an integer). Then add one (empty) record to the cursor and include “dummy” in the FROM list of the query without specifying a join condition we force every column from the dummy cursor to be added unconditionally to every record in the result set. The result is that every record in the result set ends up with an extra, integer, column named whatever we called it in the dummy cursor. Like this:

CREATE CURSOR dummy ( newint I )

INSERT INTO dummy VALUES (0)

SELECT * FROM sample, dummy

The same trick can be used to add MEMO, GENERAL or indeed any columns, of any data type, to result sets.

However, using VFP 9.0 we no longer need to resort to this trick thanks, once again, to the CAST() function. By using this function we can simply tell VFP to create whatever type of column we need:

SELECT *, CAST( 0 AS INT) AS newint FROM sample

In fact we can also use CAST() to force specific data types to be returned. One the perpetual irritants for users is when we developers use datetime fields to hold data that is really only a date (often, we have no choice because if we are seeking compatibility with SQL Server does we cannot use DATE data types directly because it does not support them). Once again we have ways of handling this in all versions of VFP but the new CAST() function is the cleanest and simplest to handle it:

SELECT CAST( datetime AS date ) AS sqldate FROM sample

The VFP 9.0 help file includes a table which defines which conversions can be handled by CAST() and which cannot. For example you can cast date values as a character, varchar, datetime or memo, but not as a numeric data type.

Of course, the usual reason for adding a new column to a result set is so that you can update it with some value later. One common requirement is to add a logical column to a cursor that will be used as the source for printing a report. The idea is that as each record is processed, the flag gets updated and so we have some idea which records have actually been sent to the printer in case something goes wrong. The problem with this scenario is that cursors created by SQL Select statements are created as read-only by VFP.

Prior to Version 7.0 we had to use another trick to create a read-only version of a VFP cursor. This relies on the fact that a cursor is actually implemented as a temporary table and that if you try to use an existing cursor twice, VFP is forced to create a second table for it, and that table will be created read-write. So the following code will create a read-write cursor in any version of VFP (and even in FoxPro 2.x!!!)

SELECT * FROM sample INTO CURSOR temp

USE DBF( “temp” ) AGAIN IN 0 ALIAS cur_readwrite

SELECT cur_readwrite

USE IN temp

VFP Version 7.0 introduced the READWRITE clause to the language which tells VFP to create a read-write cursor directly:

SELECT * FROM sample INTO CURSOR temp READWRITE

And whenever possible I now use this.

However, as I was reminded the other day, the old tricks are useful to know because I had to do some work on a FoxPro 2.6 application for a client and the simplest way to handle the problem was to use a read-write cursor which, of course, in Fox 2.6 you couldn’t do…except that by using the above trick I was able to do it.

 

Published Sunday, September 18, 2005 5:04 PM by andykr
Filed Under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Adding Columns in SQL SELECT statements

Monday, December 05, 2005 9:19 PM by anabisbe@amby.net
Thanks Andy !!

You can find the Spanish version of this article at (Puede encontrar la versión en Español de este artículo en:)

http://www.portalfox.com/modules.php?op=modload&name=Sections&file=index&req=viewarticle&artid=93

Regards / Saludos,

Ana
www.amby.net
www.PortalFox.com

# re: Adding Columns in SQL SELECT statements

Tuesday, October 14, 2008 3:06 AM by Bao

great post.  very helpful.  thanks!

You are most welcome - glad it was useful.

# re: Adding Columns in SQL SELECT statements

Wednesday, March 25, 2009 7:27 PM by Mark

'Was looking for a SQLS way to create a temp table on the fly and create an empty numeric column with decimals.  SELECT $0, ... INTO #TempNew FROM #TempOld works fine.  Thanks.  A regular 0 just creates an integer column.

An alternative (in T-SQL or even VFP V9.0) would be simply to use a CAST():
SELECT CAST( 0 AS N(10,2)) AS NewCol,..FROM Source
-- Andy

 

# re: Adding Columns in SQL SELECT statements

Thursday, May 28, 2009 6:08 PM by John

Fantastic, this post helped me first, solve the need for a null col but also was presented in a clear logical way that I was able to create another table based on an example. Keep this sort of thing going.

Thank you. I am glad it was useful for you -- Andy

What do you think?

(required) 
required 
(required)