In a query I create lots of fields using the CASE expression.
I need to reference these fields later on in the query but it seems I can't access the field using its alias - I have to repeat the CASE expression every time I want to reference its value.
Is there a simple way to access these fields?
You can use CTEs (assuming SQL Server 2005+), like this very basic example:
DECLARE #Val INT
SET #Val = 1
;WITH CTEExample AS
(
SELECT CASE #Val WHEN 1 THEN 'A' ELSE 'B' END AS MyCaseField1
)
SELECT * FROM CTEExample WHERE MyCaseField1 = 'A'
why not simply make a subquery
eg
SELECT foo.column
FROM (
SELECT
CASE WHEN yourcase THEN 'a'
ELSE 'b'
END AS 'column'
FROM yourtable) AS foo
but this can be done with CTEs either (look at this answer)
You can also use CROSS APPLY for this as in this tip by Itzik Ben Gan.
SELECT SalesOrderID, OrderDate, Week_Day
FROM Sales.SalesOrderHeader
CROSS APPLY
(SELECT DATEPART(weekday, DATEADD(day, ##DATEFIRST - 7, OrderDate)) AS Week_Day) AS A
WHERE Week_Day NOT IN (1, 7);
You should be aware that reusing aliases in a where clause will render the predicate unsargable however so should be used with caution (this applies regardless of whether you use APPLY or a CTE)
Related
Here is the query I am using to generate last date of each week. However the filter does not seem to take affect. and the starting date I get is "2016-01-03".
SELECT TO_DATE(last_day(DATEADD(DAY, SEQ4()*7, '2016-01-01 00:00:00'), 'week')) AS WEEK_LAST_DATE
FROM TABLE(GENERATOR(ROWCOUNT=>1000))
WHERE WEEK_LAST_DATE > '2016-09-07';
The following works though:
SELECT * from (
SELECT TO_DATE(last_day(DATEADD(DAY, SEQ4()*7, '2016-01-01 00:00:00'), 'week')) AS WEEK_LAST_DATE
FROM TABLE(GENERATOR(ROWCOUNT=>1000))
) as A
WHERE A.WEEK_LAST_DATE > '2016-09-07';
Why does the first query not work?
In general in SQL, you can't refer to an expression defined in the select clause in the same scope - left apart the order by clause. Typically, this raises a syntax error:
select t.*, 'abc' as newcol
from mytable t
where newcol = 'abc'
newcol is defined in the select clause, hence not available in the where clause. This is typically worked around by computing the value in an inner scope, then using it for filtering in an outer scope: subqueries, lateral joins, and common table expressions are the most common approaches. So:
select *
from (
select t.*, 'abc' as newcol
from mytable t
) t
where newcol = 'abc'
I am surprised that your first query even compiles; it may be some Snowflake-specific thing. But the second query is indeed the right way to do what you want:
I am trying to do a "with" to loop through some data (which its doing fine). But after that with, I want to return data dependent on a bit parameter. Its important that this is inside a function. Below is basically what my code is doing.
WITH StuffChain
AS (
//initial
union all
//more
)
After this, I am trying to do something like
CASE WHEN #MyParamVal = 1 THEN
SELECT TOP (1) * FROM StuffChain
ELSE
SELECT * FROM StuffChain
END
RETURN
SQL is not my strength and I am still learning sorry. I am also unsure whether or not to use inline or multi statement function
EDIT: When I am giving the case, I am using it to explain what I am after to return, not necessarily what I will use. I use it to just describe what I need using what little I know if that makes sense.
First of all, using TOP without ORDER BY is somewhat meaningless, because it provides no order against which to select the first few rows. In this case, we can try using ROW_NUMBER to control the ordering:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY some_col) rn
FROM StuffChain
)
SELECT *
FROM cte
WHERE
(rn = #MyParamVal) OR (#MyParamVal <> 1);
You can do as follows. This is just one of the solution. you can do with many other ways also.
WITH StuffChain
AS (
//initial
union all
//more
)
After creation of CTE, try with following
SELECT TOP (CASE WHEN #MyParamVal = 1 THEN 1 ELSE
(SELECT COUNT(1) FROM StuffChain) END *
FROM StuffChain
order by <column> <ASC/DESC>;
We can use a Table Variable e.g. Declare #TEMP Table (intcol int,...) inside a function.
Declare #TEMP Table (intcol int,...)
WITH StuffChain
AS (
//initial
union all
//more
)
SELECT * INTO #TEMP FROM StuffChain;
--do what ever you want with temp table
I'm trying to find a tidier way of doing the below query so that I'm not duplicating my code.
SELECT CASE WHEN <COMPLICATED CODE THAT RETURNS A SINGLE INT> = 0
THEN 1
ELSE <COMPLICATED CODE THAT RETURNS A SINGLE INT> END
Ideally, I would like something like this using an existing function rather than creating my own:
SELECT ISVALUE(COMPLICATED CODE THAT RETURNS A SINGLE INT,0,1)
You can use apply:
SELECT (CASE WHEN v.val = 0 THEN 1 ELSE v.val END)
FROM . . . CROSS APPLY
(VALUES (<COMPLICATED CODE THAT RETURNS A SINGLE INT>)) v(val);
You could also do a series of functions:
select coalesce(nullif(<COMPLICATED CODE THAT RETURNS A SINGLE INT>, 0), 1)
However, I think apply is clearer. In addition the above will turn NULL values into 1 as well as 0.
You can use a CTE (or a subquery) as
WITH CTE AS
(
SELECT <COMPLICATED CODE THAT RETURNS A SINGLE INT> AS Value
FROM ...
)
SELECT CASE WHEN Value = 0 THEN 1 ELSE Value END
FROM CTE
This way you write the complicated code just once, and then use just the Value column.
You can use IIF:
SELECT IIF(1 = 1, 'true', 'false')
https://learn.microsoft.com/en-us/sql/t-sql/functions/logical-functions-iif-transact-sql
use a sub query, then you can figure out a mathematical formula that acts to give the values you desire, that way you can eliminate actual boolean logic and replace with mathematical functions
an example is
SELECT *,(1 - ceiling(cos(atan(abs(cast(x as float)))) -
floor(cos(atan(abs(cast(x as float))))))) +
x * ceiling(cos(atan(abs(cast(x as float)))) -
floor(cos(atan(abs(cast(x as float)))))) as Computed
FROM
( select 0 as x union SELECT 1 X UNION SELECT -.123 UNION SELECT 9.1 UNION SELECT 67000 union select -1) OQ
I was able to craft this and it works, though I'm not sure why or if there is a cleaner way.
Basically I have an ASP.NET C# server side that's going to be passing a parameter. If the parameter = 0, I want the select to return everything
If the select is anything but zero, I want to just return the specific row.
DECLARE #OrgId INT = 0
;WITH cte AS
(
SELECT
o.ID_PK OrgId, ISNULL(o.Parent_ID_FK, 1) ParentId,
o.Organization_Name
FROM
DYN_Organization o
WHERE
o.Is_Active = 1
AND #OrgId LIKE CASE #OrgId
WHEN '0' THEN '0'
ELSE o.ID_PK
END
)
SELECT *
FROM cte
Again this works like I want it to, I just don't know why. I thought I would have to put % but it didn't like that. I was just testing my query when I realized it worked.
You could emulate this behavior with the logical or operator, or better yet, with in which is a shorthand over a series of ored equal checks:
;WITH cte AS
(
SELECCT o.ID_PK OrgId, ISNULL(o.Parent_ID_FK, 1) ParentId, o.Organization_Name
FROM DYN_Organization o
WHERE o.Is_Active = 1 AND
#OrgId IN ('0', o.ID_PK)
)
SELECT * FROM cte
I've found basic answer for replacing the Oracle's "CONNECT BY LEVEL" in this question but my case is little bit more complicated:
Basically things that I want to replace looks like this:
...
UNION ALL
Select
adate, 'ROAD' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum
from dual
connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
Select
adate, 'PORTPACK' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum from dual connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
....
Now, the single dual connect is easy, even if this is apparently not very efficient method
WITH CTE AS (
SELECT dateadd(day,-720,CONVERT (date, GETDATE())) as Datelist
UNION ALL
SELECT dateadd(day,1,Datelist)
FROM CTE
WHERE datelist < getdate() )
SELECT *,'ROAD' as Tserv_ID , 0 as EQ_NBR FROM CTE
option (maxrecursion 0)
repeating the union is hard because I get an error:
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
There are more parts of this union that I've provided here; I've tried to use the "WITH" only at start but no luck. Am I missing something obvious here?
EDIT: There is of course big question WHY I am even trying to do such thing: Personally, I wouldn't, but at the other end of the query there is a huge Crystal Report that runs once every month and which accepts data in this particular format. End of the FULL query's output is something like
3 columns of data
3 Columns of data
...
Currentdate-732,"ROAD",0
Currentdate -731,"ROAD",0
...
Currentdate, "ROAD,"0"
Currentdate -732, "PORTPAK", 0
Currentdate -731, "PORTPAK", 0
etc.
Are you trying to do:
WITH CTE1 AS (...),
CTE2 AS (...)
SELECT stuff FROM CTE1
UNION ALL
SELECT stuff FROM CTE2;
? This is a common challenge, I guess it is not very discoverable that in order to use more than one CTE, you just separate them with a comma.
That all said, it seems like you are just trying to generate a series of dates. A recursive CTE (never mind a series of many of them) is not the most efficient way to do this. Instead of telling us you want to replace CONNECT BY LEVEL and showing us the syntax you've tried, why don't you just show or describe the output you want? We've already got an appreciation that you've tried something on your own (thanks!) but we'd rather give you an efficient solution than bridging the gap to an inefficient one.
As an example, here is something that requires a lot less redundant code, and ( think gives you what you're after:
DECLARE #n INT = 722, #d DATE = CURRENT_TIMESTAMP;
;WITH v AS (SELECT v FROM (VALUES('ROAD'),('PORTPACK')) AS v(v)),
n AS (SELECT TOP (#n) n = ROW_NUMBER() OVER (ORDER BY number)
FROM master.dbo.spt_values ORDER BY n)
SELECT Datelist = DATEADD(DAY, 2-n.n, #d), Tserv_ID = v.v, EQ_NBR = 0
FROM n CROSS JOIN v
ORDER BY Tserv_ID, Datelist;