Would it be possible to alias an expression returned by a SELECT statement in order to refer to it in other parts of this same SELECT as if it would be a column among others ?
A kind of "temporary variable" whose scope would be limited to the SELECT statement, a little bit like the WITH clause before a SELECT to use a temporary named recorset.
A naive sample of what I'd like to achieve :
SELECT
FIRSTNAME + ' ' + NAME AS FULLNAME,
CASE WHEN LEN(FULLNAME)>3 THEN 1 ELSE 0 END AS ISCORRECT
FROM USERS
where FULLNAME could be used to determine the subsequent output field ISCORRECT, though not being a real column of the table USERS... instead of this laboured error-prone (but working) copy/paste :
SELECT
FIRSTNAME + ' ' + NAME AS FULLNAME,
CASE WHEN LEN(FIRSTNAME + ' ' + NAME)>3 THEN 1 ELSE 0 END AS ISCORRECT
FROM USERS
This sample well describes what I want, but I can easily imagine similar needs where FULLNAME might also be used in other parts of the SELECT statement : in a JOIN, in the WHERE, in a GROUP BY, ORDER BY, etc.
PS : I use SQL Server 2005 but would be also interested in any 2008-specific answer.
Thanks a lot ! :-)
Edit :
In spite of my high respect towards those of you proposing to use a side- or inner-query, I don't feel at ease with such possibilities. My sample really is a naive one. The true queries are rather with 30 output fields including complex expressions (including calls to CLR functions), 15 inner/left outer joins, and 20 additionnal where criteria. I suspect I had rather not multiplying to many indirections towards co-queries if I can avoid it.
I believe you would have to put it in an inner query, and then be able to refer to it outside of the query.
Simplest example based on yours:
select a.fullname, case when len(a.fullname) > 3 then 1
else 0 end as incorrect
from (select firstname + ' ' + name as fullname
from users) a
Example with a CTE
;with names (FULLNAME) as (
SELECT FIRSTNAME + ' ' + NAME
FROM USERS
) select
FULLNAME,
CASE WHEN LEN(FULLNAME) > 3 THEN 1 ELSE 0 END AS ISCORRECT
FROM names
You can use cross apply to concatenate strings or do calculations etc.. that involves just the current row.
select T.fullname,
case when len(T.fullname) > 3
then 1
else 0
end iscorrect
from users as U
cross apply
(select U.firstname+' '+U.name) as T(fullname)
order by T.fullname
Though not very satisfied with it, I choose (temporarily ?) a third option : I avoid co-queries and copy/pasting my complex hard-to-read expression (here symbolized by the simple one aliased as FULLNAME) by embeddind it in a scalar function... which is therefore called several times in different parts of my SELECT.
SELECT
dbo.GetFULLNAME(FIRSTNAME,NAME) AS FULLNAME,
CASE WHEN LEN(dbo.GetFULLNAME(FIRSTNAME,NAME))>3 THEN 1 ELSE 0 END AS ISCORRECT
FROM USERS
What do you think of it ?
(I precise that though more complex and unreadable than in my OP, the real expression remains a "simple" matter of string manipulation using several input fields, and doesn't involve any sub-querying or anything like that).
Related
I found this question on SO elsewhere, but the answer included a part that doesn't pertain to me, so I must ask this question with my specifics.
I need to simply add an option in my dropdown menu. Here's my SQL query to be used for the salesperson only (debug mode):
declare #user varchar(30)
set #user = 'DOMAIN\ppelzel'
select SalesPerson_Name
from Salesperson
where salesperson_id = case
when #user in ('DOMAIN\Brandyj',
'DOMAIN\jwilson','DOMAIN\KRoberts',
'DOMAIN\ppelzel','DOMAIN\bmurray')then salesperson_id
else SUBSTRING(#user,14,20)
end
order by 1
Per my previous mention of another question like this asked, it said to not use a WHERE clause. I, however, must use a WHERE clause because I need it to determine if the person logged in matches what's in the dataset, then that is the only name they'll see, outside of a handful of 'Admin' users who need to see everyone.
For these same Admin users, I need to add an option to select all salespeople. I tried simply using the "allow multiple values" but it doesn't like that and gives me an error: Incorrect syntax near ','. even when I take out the WHERE clause in my query of sp.salesperson_name = #salesperson. Anyway, what's my best course of action for adding an All option for this report?
EDIT: I just realized I might need to add the main dataset query for context:
SELECT sp.SalesPerson_Name
,c.Calendar_Month_Name
,sum(isnull(sales_qty, 0)) AS 'total gallons'
,sum(isnull(Ext_Retail_Base, 0) + isnull(Ext_Retail_Freight, 0)) - sum(isnull(Ext_Cost_Base, 0) + isnull(Ext_Cost_Freight, 0)) 'Sales GM'
,(sum(isnull(Ext_Retail_Base, 0) + isnull(Ext_Retail_Freight, 0)) - sum(isnull(Ext_Cost_Base, 0) + isnull(Ext_Cost_Freight, 0))) / sum(isnull(sales_qty, 0)) 'cpg'
FROM Fuel_Wholesale_Sales_Fact fwsf
JOIN calendar c ON fwsf.Calendar_key = c.calendar_key
JOIN Salesperson sp ON sp.SalesPerson_Key = fwsf.Salesperson_Key
JOIN Customer cu ON fwsf.Customer_Key = cu.Customer_Key
WHERE sp.SalesPerson_Name = #SalesPerson
AND c.Day_Date BETWEEN #Start
AND #End
and isnull(fwsf.sales_qty,0) != 0
GROUP BY sp.SalesPerson_Name, c.Calendar_Month_Name
UPDATE 1: I attempted to use the STRING_SPLIT function, but even using the simple example from Microsoft's website (https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2016) resulted in me getting an error: Invalid object name 'STRING_SPLIT'. I am running SQL 2016. Thoughts?
Figured it out. Compatibility level issue. My DB is set on 110. I may need to ask some questions if there's a reason it's set on that as opposed to the default 130.
UPDATE 2: I finally sorted out what I needed. I just used the "Allow multiple values" option in the Parameter Properties. It wasn't working before because I foolishly did not use an IN operator nor surround the parameter with parentheses.
So I had the following: select salesperson where id = #salesperson
When what I needed was: select salesperson where id in (#salesperson) because Report Builder will pass a string of parameter values as 'Bob','Mary','John' which require that they be put inside the parentheses. This is for others who come looking for answers.
I have a very peculiar case. My ASP.NET page calls a stored procedure of ours that performs a Full-Text Search query on our database. Some of the commonly searched strings include an ampersand because a few brands of our products (well-known brands, too) have an & in their name.
It turns out that in a certain case I get no results unless I escape the ampersand (\&), and in a certain other case I get no results only if I escape the ampersand.
I don't know if this is relevant, but (without giving out the brand names) one ends in &b and the other one in &c.
Is it possible that these strings (&b or &c) have some special meaning of their own? And that by escaping them I'm actually passing a special string to T-SQL?
EDIT
Additional info: after further testing, I proved that the error is in the stored procedure itself. Calling it with & or \& yields different results.
I'll try to post selected parts of the stored procedures. I won't post it all, because most of it isn't really relevant.
The vParamBuca parameter is the one that causes the troubles. Values could be 'word&letter' or word\&letter.
SET #ricercaA = '''FORMSOF(INFLECTIONAL,"' +
REPLACE(LTRIM(RTRIM(#vParamBuca)),' ', '") AND FORMSOF(INFLECTIONAL,"') + '")'''
The variable #ricercaA is then used to create the query string:
[...]
FROM Products AS FT_TBL
LEFT OUTER JOIN CONTAINSTABLE (Products, Sign1, '+ #ricercaA + ') AS ColSign1_0 ON FT_TBL.ID = ColSign1_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, ManufacturerAdditionalText, '+ #ricercaA + ') AS ColManufacturerAdditionalText_0 ON FT_TBL.ID = ColManufacturerAdditionalText_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, ManufacturerForSearch, '+ #ricercaA + ') AS ColManufacturer_0 ON FT_TBL.ID = ColManufacturer_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, TuttaLaRiga, '+ #ricercaA + ') AS ColTuttaLaRiga_0 ON FT_TBL.ID = ColTuttaLaRiga_0.[KEY]
[...]
EDIT 2
Many thanks to #srutzky for pointing me in the right direction! In the meanwhile, I also found a data inconsistency where one of the brands with the & in its name was modified not to have the &, and the other one wasn't modified (bottom line, my current problem is caused by that: a partial fix that was made by someone in the past).
Anyway, back on track. Now I understand that the & character in the CONTAINSTABLE function is treated as a logical AND (non bitwise).
I still need a solution for that. This answer gives a solution that doesn't work for me (the conditions are not the same as mine). How could I perform a CONTAINSTABLE search for a string with an ampersand in it? Preferably without having to transform the ampersand to another safe character?
The odd behavior you are seeing is most likely due to the CONTAINS and CONTAINSTABLE functions (both used with SQL Server's Full Text Search feature) using the ampersand ( & ) character as equivalent to the AND operator. The following statement is taken from the documentation for CONTAINS:
The ampersand symbol (&) may be used instead of the AND keyword to represent the AND operator.
There is no mention of there being any escape character for it (and a back-slash isn't typically an escape character in SQL anyway).
UPDATE
Based on the information now provided in "Edit 2" of the Question, and additional research, I would say that you do not need to escape anything. It seems that putting the search phrases in double-quotes (as a result of using FORMSOF) treats the & as either a literal or a word-breaker, depending on the values on both sides of the &. Try the following examples:
DECLARE #Term NVARCHAR(100);
SET #Term = N'bob&sally'; -- 48 rows
--SET #Term = N'bob\&sally'; -- 48 rows
--SET #Term = N'r&f'; -- 4 rows
--SET #Term = N'r\&f'; -- 24 rows
SET #Term = N'FORMSOF(INFLECTIONAL,"' + #Term + '")';
SELECT * FROM sys.dm_fts_parser(#Term, 1033, 0, 0);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, 0, 1);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, NULL, 0);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, NULL, 1);
The results for bob&sally and bob\&sally are the same, and in both cases bob and sally are separated and never combined into a single exact-match string.
The results between r&f and r\&f, however, are not the same. r&f is only ever treated as a single, exact-match string because r and f alone are not known words. On the other hand, adding in the back-slash separates the two letter since \ is a word-breaker, in which case you get both r and f.
Given that you stated in the Update that you have "data inconsistency, where one of the brands with the "&" in its name was modified not to have the "&", and the other one wasn't", I suspect that when you do not add in the \ character you get the brand that was not modified (since it is an exact match for the full term). But when you do add in the \ character, then you get the brand that was modified to have the & removed, since you are now searching on both pieces, each one matching part of that brand name.
I would fix the data to be consistent: update the brand names that had the & removed to put the ampersands back in. Then when people search using & without the extra \ added, it will be an exact match. This behavior will be consisted across the data, and will not require you adding code to circumvent the natural operation of FTS, which seems to be an error-prone approach.
The Data
Let us say I have a field in SQL that consists of multi-line Information, each of which consists of i topics, each topic consisting of m points of information. Topics are prefaced with 'i.' and information with a dash. It looks something like:
________________________________________________
|Number | Information
|===============================================
|1 | 1. Topic 1.1
| | -Info 1.1.1
| | - ... [more info]
| | 2. Topic 1.2
| | -Info 1.2.1
| | - ...[more info]
| | ... [more topics]
|_______|_____________________________
|2 | 1. Topic 2.1
|....and so on
The Current System
What I am doing with this information is to parse out each topic into it's own column, then unpivoting those columns and searching for Topics that contain a given keyword #keyword.
Currently the code reads something like:
Select
Number
,Case When Information LIKE '%1. %2. %'
Then substring (Information, charindex('1.',Information),
charindex('2.', Information) -(charindex('1.',Information)+2) )
Else Information
End as [Topic1]
,Case When Information LIKE '%2. %3. %'
Then substring (Information, charindex('2.',Information),
charindex('3.', Information) -(charindex('2.',Information)+2) )
Else 'N/A'
End as [Topic2]
...repeat 2nd case for each set of numbers up to '%20. %21. %'
The only reason the first one is different is because if it doesn't match the pattern then I want to grab the whole field so that I don't miss anything. I then unpivot the Topic fields that I just created into a general [Topic] field, and then utilize a WHERE [Topic] LIKE '%' +#keyword+'%' to pull out any particular topics and their associated case number to output as my final table. The cases can have anywhere from 1 to 40+ topics attached, with 1-7 attached info fields per topic.
The Desired Modification
Notice: To make the code easier to read, I will not be writing my substring code in proper syntax, instead opting to write substring(Information,ci(#Iter), ci(#Iter+1)-ci(#Iter)) to denote the substring running from the position given by '(iter).' to the position given by '(iter+1).'
What I would like to do is to perform the following:
Declare #Iter smallint
Declare #Result varchar(max)
Select
Number
, Set #Iter=1
Set #Result = ' '
Case When Information LIKE '%'+#keyword+'%' --keyword chosen at front end
Then While #Iter < #n --#n set by the user from front end
Begin
Case When Information LIKE '%' + cast(#Iter as varchar(5))
+ '. %'+cast((#Iter+1) as varchar(5))+'. %'
and substring(Information,ci(#Iter), ci(#Iter+1)-ci(#Iter) )
LIKE '%'+#keyword+'%'
Then Set #Result = #Result +substring(Information,ci(#Iter),
ci(#Iter+1)-ci(#Iter) )
Else Set #Result = #Result end
Set #Iter = #Iter +1
End
Else ' ' end [Result]
The Explanation
In case what I want isn't clear, I'll run through what I'm trying to accomplish
I want to output a list of case numbers that include Topics that include the keyword.
For each case in the list I want to output only those topics that include the keyword.
I want to allow the end user of the report to choose how many Topics in each case they'll search.
I don't want to have to create a table with a column for each Topic when I can't know how many the user will want to create.
Due to these considerations it feels like a loop would be the best option, but there are problems in trying to accomplish that.
The Problem
SQL server won't allow me to utilize a loop in my Select statement--Incorrect syntax near 'While'.
The place where the information comes from prohibits normalization of the information in the table I'm searching
Even if it didn't I am barred from creating my own permanent tables at work, so I can't normalize the data for all incoming data
I am also not allowed to write my own stored procedures.
If there is any way (for example through a cte) to implement these changes, I'm open to hearing them! I'm mostly looking at ways to make the code less daunting looking (20 cases to produce 20 fields in my current cte looks scary, which then needs 3 ctes just to unpack properly [unpivot, removal of certain cases meeting certain conditions, combination into a workable output table])
Thanks in advance for reading this and helping!
I think you're working too hard.
If all you need are topic names and numbers, isn't it easier to split the Information column by newlines, and then collect all lines that start with a number and not a "dash" by then, you will have a list of strings that look like:
Topic 1.1
Topic 2.1
And then it's easy to just match the lines against the keyword?
Something like this untested SQL:
select SUBSTRING(s.value,1, PATINDEX('% %', s.Value) - 1) AS topicId
, SUBSTRING(s.Value, PATINDEX('% %', s.Value), LENGTH(s.Value)) AS topicText
from [table that would make Codd cry] t
cross apply STRING_SPLIT(t.Information, CHAR(13)) s
where s.Value LIKE '[0-9]%' -- Starts with a number
AND s.Value LIKE #keywords --matches keywords
Not sure if you can create functions or you have STRING_SPLIT available in your SQL Server version, but if you don't, there are some string splitting CTEs you can find on the net to do the job for you
I want to select a list of items and part numbers for for each item as a string:
SELECT top 100 *
FROM ii
OUTER APPLY
(SELECT def, ( ipr.part_number + ',') as prt
FROM ipr
WHERE ii.item_id = ipr.item_id
FOR XML PATH('') ) PN
The error is:
[Error Code: 8155, SQL State: S0002] No column name was specified for
column 1 of 'PN'.
How can I fix this?
I think that your whole OUTER APPLY statement generates one XML for both default_part_number and concatenated string, which(the whole XML) doesn't have a name.
What you could try to do would be adding alias like this AS PN(TestThis).
However, I don't think that you're expecting result you're going to get. It would better if you'd give us some example data and expected output. It will be easier for us to solve your problem in that case.
The combination of XML and STUFF is funny but perfectly fitting to your needs.
First you concat your strings with the ', ' in front, then you must return your XML with ", TPYE). You must read the result with ".value()" and use STUFF to replace the first ', '.
You'll find a lot of exampels in the net...
I am working in SQL Server 2008. I have been tasked with writing a stored procedure to do some data validations on external data before we move it into our star schema data warehouse environment. One type of test requested is domain integrity / reference lookup from our external fact data tables to our dimension tables. To do this, I use the following technique:
SELECT
some_column
FROM some_fact_table
LEFT JOIN some_dimension_table
ON
some_fact_table.some_column = some_dimension_table.lookup_column
WHERE
some_fact_table.some_column IS NOT NULL
AND
some_dimension_table.lookup_column IS NULL
The SELECT clause will match the column definition for an errors table that I will eventually move the output into via SSIS. So, the SELECT clause actually looks like:
SELECT
primary_key,
'some_column' AS Offending_Column,
'not found in lookup' AS Error_Message,
some_column AS Offending_Value
But, because the fact tables are very large, we want to minimize the number of times that we have to select from it. Hence, I have just 1 query for each fact table to check each column in question, which looks something like:
SELECT
primary_key,
'col1|col2|col3' AS Potentially_Offending_Columns,
'not found in lookup|not found in lookup|not found in lookup' AS Error_Messages,
col1 + '|' + col2 + '|' + col3 AS Potentially_Offending_Values
FROM fact_table
LEFT JOIN dim_table1
ON
fact_table.col1 = dim_table1.lookup_column
LEFT JOIN dim_table2
ON
fact_table.col2 = dim_table2.lookup_column
LEFT JOIN dim_table3
ON
fact_table.col2 = dim_table3.lookup_column
WHERE
dim_table1.lookup_column IS NULL
OR
dim_table2.lookup_column IS NULL
OR
dim_table3.lookup_column IS NULL
This has some problems with it. (1) If any of the source column rows is null, then the string concatenation in Offending_Values will result in NULL. If I wrap each column with ISNULL (and swap the nulls for something like an empty string), then I won't be able to tell if the test failed because of a true empty string in the source or if it was swapped for an empty string. (2) If just one of the columns fail in the lookup, then the error message will still read 'not found in lookup|not found in lookup|not found in lookup', i.e., I can't tell which of the columns actually failed. (3) The Potentially_offending_Columns column in the output will always be static, which means I can't tell if any of the columns failed just by looking at it.
So, in effect, I am having some design problems with my errors table. Is there a standard way of outputting to an errors table in this situation? Or, if not, what do I need to fix to make the output readable and useful?
I don't know what your data looks like, but instead of using an empty string with ISNULL, couldn't you return the word FAIL or something that's meaningful to you. You could do a CASE WHEN for your 'not found in lookup' column.
CASE WHEN Col1 IS NULL THEN 'not found in lookup' ELSE '' END + '|' +
CASE WHEN Col2 IS NULL THEN 'not found in lookup' ELSE '' END + '|' +
CASE WHEN Col3 IS NULL THEN 'not found in lookup' ELSE '' END AS Error_Messages,
ISNULL(col1,'FAIL') + '|' + ISNULL(col2,'FAIL') + '|' + ISNULL(col3,'FAIL') AS Potentially_Offending_Values