Asked by:
write a IIF function with DLookup in TSQL using Case When

Question
-
Hello
am trying to write the following query in TSQL replacing the Case Statement and the Dlookup function as Microsoft SQL doesnt have DLookup function . we have recently migrated from MS access to SQL Server
SELECT Projid,
VW_Capability Now with_RAG_Crosstab.Analysis
VW_Capability Now with_RAG_Crosstab.Technical
IIF(Vcapability Now with RAG_Crosstab].[Other1] is Null,Null,Dlookup("OtherCapability",VW_Capability Now with_RAG","QTRID = " & [VW_Capability Now with_RAG_Crosstab] & "AND ProjReturnID= " & Vwcapability Now with RAG_Crosstab].[ProjReturnID] &" Capability = 'Other1'")) AS [Other1-Name],Vwcapability Now with RAG_Crosstab].Other1,Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future]
FROM
table 1 INNER JOIN Table 2 ON Table1.ProjID = Table2 .ProjReturnID
- Edited by Mittle Wednesday, November 25, 2020 8:43 PM
Wednesday, November 25, 2020 8:41 PM
All replies
-
Ok, that is a bit hard to read.
If I cust + paste into SSMS, and just do a quick "air code" edit, then something close to this will work:
SELECT Projid, VW_Capability Now with_RAG_Crosstab.Analysis, VW_Capability Now with_RAG_Crosstab.Technical, CASE WHEN [Vcapability Now with RAG_Crosstab].[Other1] is Null THEN null ELSE ( SELECT OtherCapability FROM [VW_Capability Now with_RAG] AS T WHERE T.QTRID = [VW_Capability Now with_RAG_Crosstab].[SOME FIELD NAME]
AND T.ProjReturnID = [Vwcapability Now with RAG_Crosstab].[ProjReturnID] AND T.Capability = 'Other1' ) END AS [Other1-Name], [Vwcapability Now with RAG_Crosstab].Other1, Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future] FROM table 1 INNER JOIN Table 2 ON Table1.ProjID = Table2 .ProjReturnID
With above (or say Access???). I tend to NOT use a dlooup(), but left join the needed value. However, you have several conditions for the dlookup().
Since there is no dlookup() in t-sql, what you do is write a FULL query (in this context, that is called a sub-query).
The only other issue is that the sub query can ONLY return one row/value. If there are possible more then one match, then add a TOP 1 and order the results say by ID DESC (say a autonumber descending ID to pluck the most recent row).
Given I don't really have the tables? The above is just a quick edit, but it should be quite close to what you need.
And the approach of how we "replace" a dlookup() is also in above. So, for a "lot" of dlookup() that are JUST a value say from another table based on some type of "id", then a left join of that table can be used - since you can build that 100% in the query builder.
However, for complex and "several" conditions in a dlookup(), then you have to bite the bullet, and create a sub query much like above. And added to this, is you have a iif() on top. I convered the iif() to a CASE, since iff() tends to be really quite hard to read and work with.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
- Edited by Albert D. Kallal Thursday, November 26, 2020 12:56 AM
Thursday, November 26, 2020 12:50 AM -
Thank you so much Albert
1)Your SELECT seems to only have one table even though you have used one alias AS T.
There are 2 tables involved and both have QTRID and ProjReturnID column
[VW_Capability Now with_RAG_Crosstab]
[VW_Capability Now with_RAG]
2)am confused about this part. what does it mean when you have more than one statement after the END AS Other1-Name]. what does the other part with AS [Other1-Future] mean . I know the first END AS means thats the decision take after the CASE statement.
) END AS [Other1-Name], [Vwcapability Now with RAG_Crosstab].Other1, Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future]
- Edited by Mittle Thursday, November 26, 2020 2:47 AM
Thursday, November 26, 2020 2:34 AM -
Well, the "CASE" always has a start, and a end.
So, say this;
SELECT ID, HotelName, FirstName, CASE WHEN City is null then 'No City Value' ELSE City END AS MyCity FROM tblHotels
So, in above, if the City is null, then we have 'No City Value', ELSE the City is given. However the column name output is now MyCity.
So whatever pops out of the CASE --->END must be given a value. In this case the results of that CASE/END is called MyCity.
So the "as" part in this context is the column name we give for the results of that CASE statement.
it not a lot different then going
SELECT ID,ThePersonsFirstName AS FirstName from tblCustomers
So the "as" gives us a nicer column name in above (The column ThePersonsFirstName say was too ulgy for our taste).
It called a "alias". Quite sure you need this for a CASE. If you leave it out, then you get "Expr1" or "no column name" for the column name.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
- Edited by Albert D. Kallal Thursday, November 26, 2020 2:46 AM
Thursday, November 26, 2020 2:45 AM -
thanks a lot yes I understand your explanation but remember my code had another 2 lines are the first END AS
their is another AS
does that mean another column [Other1-Future] should be created
[Vwcapability Now with RAG_Crosstab].Other1, Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future]
Thursday, November 26, 2020 8:30 AM -
Hi Albert
thank you so much . looks like am getting there but not quiet yet. the query result is getting more rows(13,468) in SQL Server than when I run the IIF's and Dlookup query in Access.(2974rows)
there appears to still be something wrong with the CASE Statement . ive made the query more clear than before
any further help is much appreciated.
--IIf([Capability Now with RAG_Crosstab].[Other2] Is Null,Null,DLookUp("OtherCapabilityDesc","Capability With RAG","QTRID =
--" & [Capability Now with RAG_Crosstab].[QTRID] & "AND ProjReturnID
--= " & [Capability Now with RAG_Crosstab].[ProjReturnID] & "And Capability = 'Other2'")) AS [Other2 - Name],
----[Capability Now with RAG_Crosstab].Other2, [Capability Future with RAG_Crosstab].Other2 AS [Other2 - Future],
CASE
WHEN [vwCapability Now with RAG_Crosstab].[Other1] Is Null THEN NULL ELSE
(SELECT
--TOP 1
r.OtherCapabilityDesc
FROM
[vwCapability With RAG] as r
WHERE
r.QTRID = [vwCapability Now with RAG_Crosstab].QTRID
AND
r.ProjReturnID = [vwCapability Now with RAG_Crosstab].ProjReturnID
AND
r.Capability = 'Other1'
--ORDER BY r.ProjReturnID desc
)
END
AS [Other1 - Name],
[vwCapability Now with RAG_Crosstab].Other1,
[vwCapability Future with RAG_Crosstab].Other1 AS [Other1 - Future]Thursday, November 26, 2020 11:51 AM -
Hi Albert
your query is given an error re: multipart identifier could not be bound .
I think you need to specify JOIN since you are joining 2 tables and haven't aliases the 2nd table Please correct me if am wrong .
so query
I have since used INNER JOIN which seems to run ok but am not getting the correct rows
INNER JOIN CODE and TOP 1 because I was also getting subquery returned more than 1 value not permitted . and you have stated to USE TOP 1 since am expecting 2000plus rows
here is the query getting over 12,000 rows instead of just about 200
ASE
WHEN [vwCapability Now with RAG_Crosstab].[Other4] Is Null THEN NULL ELSE
(SELECT
r.OtherCapabilityDesc
FROM
[vwCapability With RAG] as r
INNER JOIN
[vwCapability Now with RAG_Crosstab] as rc
ON rc.ProjReturnID =r.ProjReturnID
WHERE
r.QTRID = rc.[QTRID]
AND
r.ProjReturnID = rc.[ProjReturnID]
r.Capability = 'Other4'
ORDER BY r.ProjReturnID desc
)
END
AS [Other4 - Name],
[vwCapability Now with RAG_Crosstab].Other4,
[vwCapability Future with RAG_Crosstab].Other4 AS [Other4 - Future],
[vwCapability Now with RAG_Crosstab].Other5, [vwCapability Future with RAG_Crosstab].Other5 AS [Other5 - Future]
Thursday, November 26, 2020 3:30 PM -
Of course we need the join(s) that you origional had. My first query most certainly includes that join.
We breaking this down into bite sized parts. So we first learned/used/adopted/read about how to replace the IIF(). SQL server does have a iif() now, but the CASE is oh so much easer to read and maintain anyway.
Next, we then dealt with the dlookup.
dlookup("ColumnName","Table name", SQL WHERE) So, if we have: dlookup("HotelName","tblHotels","ID = 5") Then above becomes (SELECT HotelName from tblHotels WHERE ID = 5) as some column name
So adopting/using the CASE is a "one to one" conversion process for the iif().
And converting the dlookup() to the sub query select? Again, a one to one conversion process. nothing hints, suggests or implies that we joing to remove or dump or change the sql query in regards to joins etc. They have to remain - my first example (while air code) did and does include the joins. So yes, you certainly want to keep and need the joins and they should remain and work as before. The converting of the iif() and the converting of the dlookup() are discrete and isolated changes you make. If we adopt and do this correctly, then we ONLY change the iif() to a CASE.
And if we do this correctly, we ONLY change the dlookup() to a sub - query. Everything else thus should remain as before. And in fact, if we adopt the conversion process, then we don't even had to grasp or understand or even change the rest of the query.
So my first example - (which likely needs some work) including this at the end:
END AS [Other1-Name], [Vwcapability Now with RAG_Crosstab].Other1, Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future] FROM table 1 INNER JOIN Table 2 ON Table1.ProjID = Table2 .ProjReturnID
So there is no hint, suggestion, implication that we going to change the basic layout and logic and intent of the query. We ONLY going to un-plug the iif() and plug in our CASE. And we ONLY going to un-pluge the dlookup and insert the sub query. But the rest of the sql will remain as before, and hopefully un-touched without the need to change. So we not really changing the basic query and the joins - only un-plugging the iff() and unplugging the dlookup(). Everything else should remain. In fact, one might even remove the ii(), remove the dlookup and get the base query working. Once it seems to work, then we introduce the iif() - get that working.
Once that works, then we introduce the dlookup() replacement.
So the old saying?
How do you eat a elephant?
Answer: One bite at a time!
So consider building the base query (with the joins), but the iif() and dlookup() removed. Then introduce the CASE, and if that works, then introduce the sub-query. So we are swapping out a few parts of the basic query - but the joins and the rest of the query should be rather similar to the original query - including the joins.
And the original dlookup() did not have a join in it a all - so the replacement for the dlookup will be just like the above hotels example, or similar to what I posted. What and how the dlookup() does, and our sub query will BOTH do the exact same thing. That dookup() to my knowledge was not based on some "join" but had a single query (or table) specified with criteria - and you do the same with the sub query.
So we really don't re-write the basic logic and layout of the original query. As noted, I would consider getting that query working (including the joins) without the iff(), and the dlookup(). Then we introduce these two new parts to that original query with the joins and everything else it had before.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
- Edited by Albert D. Kallal Friday, November 27, 2020 1:50 AM
Friday, November 27, 2020 1:40 AM -
Hi Albert
thanks very much for your step by step explanation. ive tried again step by step so I run the subquery and I get rows but when I run the whole query including the CASE When part with it , I get error subquery returned more than 1 value. this is not permitted when the subquery follows =! etc .
how do I get round this please .
I also forgot to tell you I the 12,000 rows I got was as a result of including top 1 that you suggested which I dont understand why am doing it . the column Other1 was only receiving top 1 rows from the OtherCapability desc rows which is not what I want
thanks
- Edited by Mittle1 Saturday, November 28, 2020 10:47 PM
Saturday, November 28, 2020 10:46 PM -
Hi Albert
Also the Dlookup is based on some join
this is the original table specified :Capability With RAG
and this is the view its joining to Capability Now with RAG_Crosstab].please check the Look up again and see what am saying
I thought a lookup means if you are in a table then go look up data from another table .
- Edited by Mittle1 Saturday, November 28, 2020 11:00 PM
Saturday, November 28, 2020 10:59 PM -
As noted, before we do the open heart surgery, we really want to get the “base” query working.
Your query looks like this with the iff() removed.
SELECT Projid, [VW_Capability Now with_RAG_Crosstab.Analysis], [VW_Capability Now with_RAG_Crosstab.Technical], 'my if' AS [Other1-Name], [Vwcapability Now with RAG_Crosstab].Other1, [Vwcapability Future with RAG_Crosstab].Other1 AS [Other1-Future] FROM table1 INNER JOIN Table2 ON Table1.ProjID = Table2.ProjReturnID
I don't see a table called Capability With RAG in the above???
Your dlookup() most certainly uses that table, but that problem, issue does not matter. When we introduce the dlookup() replacement, we don't care - it only "one value" and "one thing" that we will introduce, and that dlookup() pulls one value from a "different" table that not really part of this query. So once the "base" query works, then we can introuce the dlookup() repacement - and it will work as before. So that job and task (to pull/get/grab) one value from table VW_Capability Now with_RAG will not damge or even effect the "base" query we have that we assume is working.
So, what I suggest is getting the above query working as written. I only see table1, and table2. So, get the above query working.
Then and ONLY then do we start the process of replacing the iif() part. But the base query as posted needs to work, and get that working first.
Our introduction of the sub-query will ONLY return one simple value. As noted, the only real difference here is that dlookup() grabbed the first value - just about any value to boot. So, we WILL have to introduce a top 1 into that sub-query, but it is of little use and effort to attempt that sub-query until such time we get the "base" query working.
Once you have a working base query, then we introduce the sub-query. And once that works, then we can write the CASE for when that value is null and introduce that CASE test.
So, break this down in to smaller bite sized parts. Our first goal is to obtain a working query, and one that returns the same number of rows as it did in the past.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
Saturday, November 28, 2020 11:15 PM -
Thanks so much sorry to bother you. am a DBA just getting into Coding so not an expert in Coding and yes ur correct in saying don't see a table called Capability With RAG in the above???-----thats the main query
the CAPABILITY with RAG is introduced in the subquery .
I have followed your steps
Run the main query without the IIF's and Case statement --------------that works OK
Run the SELECT subquery --------------------runs ok with 132 rows with format like that
SELECT OtherCapabilitydesc FROM [VW_Capability with_RAG] AS T WHERE T.QTRID = [VW_Capability Now with_RAG_Crosstab].[SOME FIELD NAME]
AND T.ProjReturnID = [Vwcapability Now with RAG_Crosstab].[ProjReturnID] AND T.Capability = 'Other1'
otherCapabilityDesc Column
----------------------------------
Administration
3.when I now run the CASE Statement
I get error
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
Thank you
Sunday, November 29, 2020 12:55 PM -
Ok, I don’t see your basic working main query with the iif and case removed.
Should not that be the base starting query that we START from and START working on?
And note how I stated the sub-query can ONLY return one row – so we use the top 1 for that sub query. But right now, I don’t see our nice, easy, and well laid out main query with the join, and the iif() and case removed.
We need and want that nice laid out main query.
Once we have that query, then it is a simple matter to THEN introduce the sub query. And as noted, that sub query can only return one row – easy achieved with a top 1.
Once we have that nice working query (don’t see it anywhere), then and ONLY then can we start the next step in this process of introducing the sub query.
In fact, I might even suggest that with the working main query, we introduce a step BEFORE we attempt the sub query.
So our goal, a quest for the Rossetta stone here is to get a main query working.
Why try to deal with 5 or 10 or 2 things at one time?
How do you eat an elephant?
Answer: one bite at a time.
I’m sharing the above process with you because be it making a cup of coffee, going to the grocery store, or planning a camping trip? This about how we attack a given problem, be it software or planning a camping trip.
So, what does our working main query with the joins look like without the iif() and sub-query look like?
That working query is the starting point here.
And VERY good that you built and worked and played with a SEPARATE query that will become our sub query.
So yes, very good to work on the sub-query as a separate query to test, try, play with etc. (again, that work on one thing concept is the key here).
And since you are playing and working on that sub query (as a 100% separate query?).
Then hard code in a fake/known condition that will eventually come from the main query. Shove in a hard coded value in that sub query (that you slicing, dicing, playing with in a separate query window).
You get multiple rows. But now we can introduce that top 1 and the order by to pull ONE row out of that sub query. And the order by will determine what row we pull. You can’t say order invoices by date, since we might have TWO invoices based on that critera.
So, to force ONLY one row in a typical TOP 1 query?
In the order by, add a 2nd value to the order by. Say there is the possible of 10 invoices from a customer for today’s date. But I only care or want ONE of them (just like what a dlookup() does or did).
So
SELECT TOP 1 InvoiceTotal from tblInvoices
WHERE invoiceDate = today and CustomerID = MyCustomerID
ORDER BY InvoiceDate
The above might produce 2 or more results, since Invoice date is our order by.
So, add this:
SELECT TOP 1 InvoiceTotal from tblInvoices
WHERE invoiceDate = today and CustomerID = MyCustomerID
ORDER BY InvoiceDate, ID DESC
So, the “order by” clause is the trick and Rosetta stone to force top 1 to ONLY produce one row. In above, since “ID” is our PK autonumber ID, then I take the LAST (most high ID) by introduction of a order by, but by invoice date and then by DESCending autonumber “ID”, and thus top 1 will ONLY EVER return one value since ID is unique, and top 1 takes its “que” from the order by clause as to which row will be top 1.
However, we are putting the cart before the horse. We not yet seen a working main query without the ii() and case. That main working query is our goal and ONLY goal here.
Once we achieved the goal of a nice laid out main query, then step 2 is to introduce our sub query. And as noted, (multiple times) that sub query can only return one row.
So, the whole concept here is to break this down into smaller and smaller steps.
And that first step is building a working query. Once we have that working query, then we can then start to work on the problem and task of building a sub query. However, without that working known main query, then little use to start step 2 to build that sub query until such time that we have a main working query.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
Monday, November 30, 2020 7:23 PM -
Hi Albert
am to sure what you mean but we have been on this for over a week
I have run the main query on my PC and it works fine. my issue is with translating the lookup and IIF part but you are going on about the main query
all am asking is how can I write the subquery to give me my desired result . its to working and giving errors
the subquery you helped with is not working and giving error
I get error
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
ONLY one expression can be specified in the SELECT List when subquery is not introduced with Exists ---what does this mean
Monday, November 30, 2020 8:45 PM -
Not at all sure where “exists” was introduced here?
As noted, build this sub query, and build it as 100% separate in a new query window in SSMS.
Hard code the condition to a known value that dlookup() say used.
Build that query, and you VERY likely will get multiple rows.
Now, once that query works, you THEN introduce the top 1 to result in that query ONLY returning one row.
Once you have that working, then you can risk/try introducing that query as a sub-query into that main working query.
>been on this for over a week
No kidding. You have shown the most amazing ability to refuse advice, follow advice and shown no ability to follow instructions and suggests. So, no not surprised.
The old saying about leading a horse to water comes to mind here.
We still don’t even have a posted working main query. And I don't recall anything about "exists" being introduced here. (we might go down that road, but we not that far down the road just yet).
Once you have that working main query? (Without the sub query)?
Then then suggest that you 100% get the other query (that will eventually become our sub query) ALSO working with a hard coded value).
In other words, get the sub query working, but work, test, play and build that 2nd query as 100% separate.
With the two working queries then it will be a rather simple process to introduce and drop the working 2nd query as a sub query into the main query.
Now, does the query + sub query work?
Now, we can remove the hard coded condition in that sub query, and change it to the given value that comes from the main query.
Follow the above steps – this will become a walk in the park.
And once that sub query is working in that main query, we then and ONLY then can introduce the CASE statement.
Regards,
Albert D. Kallal (Access MVP 2003-2017)
Edmonton, Alberta Canada
- Edited by Albert D. Kallal Monday, November 30, 2020 9:10 PM
Monday, November 30, 2020 9:09 PM