Showing posts with label sql. Show all posts
Showing posts with label sql. Show all posts

Monday, August 4, 2008

Hacking SQL to Save CFGRID: LEN, CHARINDEX, Filename parsing

OK, so then I ran into the same problem I had that I blogged about previously, which is this: getting a full link, image, with unique record ID, into the single column value from SQL so I can have it in my CFGRID.

With one hitch: this time, I needed to use logic to evaluate the image filename. The filename that is in the database is the one for the actual photo, but the filename I needed is a mini-thumbnail that is based on that photo. Now, the thumbnail naming is consistent, at least in regards to matching the database's photo filename. But there are two big caveats, that kept causing SQL errors:

1 - some columns didn't have any value at all there. Their original upload was a PDF for example so there are no files and no value in the database.

2 - the filename entries in that column varied in length. So I couldn't just say, give me the first 5 characters only, then add a new extension (as all the mini thumbnails are JPG, but the filetypes in the database may vary).

This means I had to do four things:

1 - Get the real length of the filename entry

2 - Get what the length would be MINUS 3 digits (the filename extension)

3 - Get only that quantity (#2 above) of characters from the original filename

4 - Then add strings to it, to add the 'mini_' and 'jpg'. And while we're at it, add the strings that will give it the IMG REF and A HREF because CFGRID is not going to be doing that for me either.

5 - And during all this, we have to make sure we are NOT trying to do database math on column values that are incapable of having it done to them or it will probably trigger an error for the user. So if there is no filename or it's too short we just have to do something else.

The first thing I had to figure out was how to stop the logic if the value wasn't up to it. So my first "condition" in my CASE WHEN statement related to that:

CASE WHEN LEN(dsfbfile) > 5


If the filename isn't 5 digits long this is all gonna fail and/or it isn't a real file anyway, so we've taken care of that.

Then I tried to combine the LEN (char length in SQL Server) function and the math subtraction function, to put it only one statement, both 'get the length' and 'remove the length of the extension'. That worked fine.

Unfortunately when I tried to use that it failed, because this is not like CFML, that column name you create is not creating a 'variable' you can just go using everywhere else from then on. It would give me the error INVALID COLUMN every time I tried to use it "as if" it were a number. This is what I tried to do, which failed:

SELECT myrecordID,

LEN(dsfbfile) -4 as THENEWLENGTH,

LEFT(dsfbfile, THENEWLENGTH) as FILETRIM,

'mini_' + filetrim + 'jpg' AS minipic


You see in line 3 I was trying to make that new 'thenewlength' column that I had created, stand in place for an actual number, which SQL Server was not happy about. I actually don't think it would have been happy about me using the calculated column in the last line either but I didn't get that far before I changed it.

Then I found reference to this cool function called PARSENAME:


PARSENAME retrieves parts of string delimited by dots.
It is used to split DataBaseServer, DataBaseName, ObjectOwner and ObjectName
but you can use it to split IP addresses, names etc.

DECLARE @ParseString VARCHAR(100)
SELECT @ParseString = 'DataBaseServer.DataBaseName.ObjectOwner.ObjectName'
SELECT PARSENAME(@ParseString,4)

got that from this address


I wasn't entirely sure how it worked but I got to something else before then so I didn't use it, but thought I'd mention it as it'd be a cool thing I bet for dealing with "filename" manipulation (getting full name or extension easily).

Then I happened to see another example on that same page I linked above, that did something neat I hadn't seen before. First, it used a function called CHARINDEX. Here's a neat article on CHARINDEX and PATINDEX (pattern-index). Basically this function works for 'searching strings' in a database. You can look for a pattern (PATINDEX does wilcards) or something specific like a single character or string (CHARINDEX does that).

Well I didn't know about the use of it in search (I'm still using LIKE % which is probably why my search forms suck), but I saw this example of it used LIKE A NUMBER in a LEFT function -- exactly what I'd been trying to do, but SQL-Server was balking at my using my 'generated value' (or even the logic for the generated value) "as" a number.

This logic, and I'm not even sure why but it does work, looks at my column value, goes to the period in it, goes back one character (-1, to BEFORE the period), and then gets me the string of text that is left of that location, all from the 'imagefile' column exampled below:

LEFT(imagefile,CHARINDEX('.',dsfbfile)-1)


I was able to drop that into a string manipulation, and from an input of an 18 character filename (WHEN it exists), output a link, and image (a different image), with the unique record ID on the link, and if the column wasn't a real picture or any value it would write in a filename value, so I can put a 'placeholder' image file there to keep displays from being funky.

So the good news is, here's the code that worked:

SELECT recordid,
CASE WHEN LEN(imagefile) > 5
THEN '<a href="rvg_showpractice.cfm?id=' + CAST(recordid AS varchar(12)) + '"><img ref="http://www.mysite.com/folder/' + 'mini_' + LEFT(imagefile,CHARINDEX('.',imagefile)-1) + '.jpg" /></a>'
ELSE 'nofilehere.gif' END
AS 'See Session',
anothercolumn here
FROM table


Now the bad news: it took >59000ms seconds to run on 1000 records. I kept taking the quantity of records down, down, down, until I only had 150 records, and still it was taking like >41000ms.

RATS!!

So I ditched that whole phrase about the mini-thumbnail with link. It is still a long query, even on fairly few records, but it's not so long it's at risk of timeout at least. I will have to put the images somewhere else I guess, where the query is not on a view table made of two other view tables, but something more straightforward so assumed fast.

Still. I was damn proud of getting it to work at all.

Hacking SQL to Save CFGRID: CASE functions

OK, now that I'm using CFGRID that means I can't use all the simple logic that CFML is so famous for making easy.

The irony of this is that it's a big deal how 'easy and lovely' CFGRID is. But it's mostly so freakin easy because it forces you do all the hard work you used to do writing CFML code, instead writing SQL code.

It's rather like a thing at work recently. We used to take content from editorial and build a product. Then we got this automated script tool that builds all the XML almost instantly. Unfortunately it requires the editorial people spend eons making this absolutely perfect, must be precisely formatted, multipage excel workbook which in some cases amounts to 55 sheets of hundreds of lines each... screw up a field and it's a problem, screw up something important and it won't run at all. These people are far less technical than we are, so it puts a larger burden on them. Now production is happy because we can say, "Hey, now we only charge a fraction what we used to, and it's much faster, to build that product." But editorial is doing at least as much work as our PM's used to, except on the front-end, rather than the back end, and frankly they didn't have enough time to begin with (probably why their turnover % is ridiculous). In the bigger picture, the equation and the answer did not improve.

Where was I.

Oh yeah. In my primary coding project there is a gallery of sorts, and the artists (we call "viewers") have the choice as to whether they use their name, their alias, or 'anon' on their contributions (which we call "sessions"). The problem is, this means if you are making a list of the last entered sessions, you don't know what you can put in the space that has the artist's name. Not only that, you don't even know which column it should come from, until you look at the value of a totally different column called REVEAL, that is for their settings.

So you look at REVEAL and say, if it's zero, anon, it's one, alias, if it's two, get both first and last name and combine them. That's all very well in ColdFusion. You have to loop the query out anyway in the page, and you do a CFIF statement with it, so what displays just depends.

<cfif query.reveal is 0>'anon'<cfelseif query.reveal is 1>#trim(query.alias)#<cfelseif query.reveal is 2>#trim(query.fname)# #trim(query.lname)#<cfelse>?</cfif>


But now to CFGRID, ah, yes... it only displays what the database spits out, which means if you want something like a combined field value, a variety of formatting values (with some exceptions), or god-forbid a CONDITIONAL value, you're doomed. The only way to get it is to go back to SQL which feeds the grid and figure out how to get your final answer out of that to begin with.

So this evening I got to learn about CASE functions.
This is SQL Server's version of IF THEN ELSE in most code languages.

It turns out, it's not that hard, once you get the syntax down. At first I thought I could only do two options (like some spreadsheet formulas) but I'm not sure there is even a limit. In the stuff I found online, they never used in their example real data, like the value of a column in that table -- they always used some string, like CASE WHEN saleprice > 500 THEN 'bigsale' or whatever. I wasn't sure using a column value rather than string would work, but it does.

Also, while I'm at it, I go ahead and rename all my columns to whatever I want the title to be in the grid, since then I don't even have to put that into the cfml file, as CFGRID will automatically use the column names if the header isn't defined. And some of my column names are pretty damn obscure (because they may have begun life for a different usage) so this kind of makes the queries clearer in retrospect, since I have the memory of a gnat.

If you want to use a column name that has spaces, dashes etc. just put the name in single quotes.

Here is the logic I used to get the identity column working right:


CASE WHEN reveal = 1 THEN alias
WHEN reveal = 2 THEN fname + lname
ELSE 'anon' END
AS Viewer


So it uses the actual value of columns depending on the value of another, and then uses a string in the last case. And it names the final value "Viewer".

Sunday, August 3, 2008

Hacking SQL to Save CFGRID: Image as column value, HREF link with RECORD ID

This is only part of solving a larger problem. The problem is using the HREF on CFGRID underlines every damn thing and I couldn't figure out how to get rid of it. I did find extensive notes on CSS styling for the grid, down to every single cell, but nobody crying about it on the internet had gotten any answer on how to get rid of the underline.

So I took the HREF parameter off my CFGRID because the underline looked so horrible and made it so unreadable I wouldn't even use the grid if I had to suffer that. Which left me with the following dilemma: since the grid no longer links to something semi-automatically (posts to a form, click chooses a whole row, you can pass the ID value you need), I would have to link to it manually.

As part of doing this I decided that much like the simple HTML table I've been using prior to trying to implement CFGRID, I would use a little icon that would intuitively tell people (a) to click that for a link, and (b) that it would open in a new window. So I had three things to figure out:

1. How to get an image as a column value, because CFGRID only lets you insert a query column name, not html, and

2. How to get an html HREF link into a column value, for the same reason, and

3. How to get the unique record ID into that link.

To get a single (just one, same image on every record) into a column value, I did the below. Note that if you need a different image for each record, and you can build that filename/loc from whatever values are in the query, you could do that too. Use the logic of the Record ID link below, and modify that.

SQL Server uses + to concatenate strings and columns. Other DBs may use things like CONCAT function. You use single quotes around every string, and no quotes around actual column names.

You have to render, aka CAST or CONVERT, integer fields to strings before you can concatenate them.


SELECT
'<a target="_blank" href="myactionfile.cfm?id=' + CAST(p.myid AS varchar(12)) + '">' + '<img src="../shared/icons/external2.gif" border="0" /></a>' as 'See',

then in CFGRID, I just did

<cfgridcolumn name="See" width="35" display="yes" >


and that's it! That changed my ID to a string, and it tied it into a link and an image, so now in my column it looks like this:



You click on the little image and it opens that record in a new page.

Hacking SQL to Save CFGRID: Date Format

In the HTML format CFGRID, my date "mask" formatting, using the CF documentation, didn't seem to work at all. I don't know why but my impression from cfdocs was that it wasn't going to work in the HTML format grid I was using. I hacked this by changing my SQL to begin with, rather than doing it in CF.

Formerly it was:


SELECT mydatefield as DateEntered

then

<cfgridcolumn
name="mydatefield"
display="yes"
header="Date"
mask="EEE DD-MMM-YY H:NN A">


Which sucked and continued to show me whatever it wanted, rather than the "mask" attributes the cfdocs had. So I changed it to this:


SELECT CONVERT(VARCHAR(11), mydatefield, 106) AS DateEntered

then

<cfgridcolumn
name="mydatefield"
display="yes"
header="Date">



And the resulting output looks like this:

22 Jun 2008

You can google for SQL DATE FORMAT or something like that and see the whole huge list of numbers (mine 106, above) that will format dates differently.