SELECT with a Replace() - sql

I have a table of names and addresses, which includes a postcode column. I want to strip the spaces from the postcodes and select any that match a particular pattern. I'm trying this (simplified a bit) in T-SQL on SQL Server 2005:
SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts
WHERE P LIKE 'NW101%'
But I get the following error;
Msg 207, Level 16, State 1, Line 3
Invalid column name 'P'.
If I remove the WHERE clause I get a list of postcodes without spaces, which is what I want to search. How should I approach this? What am I doing wrong?

Don't use the alias (P) in your WHERE clause directly.
You can either use the same REPLACE logic again in the WHERE clause:
SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts
WHERE Replace(Postcode, ' ', '') LIKE 'NW101%'
Or use an aliased sub query as described in Nick's answers.

You can reference is that way if you wrap the query, like this:
SELECT P
FROM (SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts) innertable
WHERE P LIKE 'NW101%'
Be sure to give the wrapped select an alias, even unused (SQL Server doesn't allow it without one IIRC)

You are creating an alias P and later in the where clause you are using the same, that is what is creating the problem. Don't use P in where, try this instead:
SELECT Replace(Postcode, ' ', '') AS P FROM Contacts
WHERE Postcode LIKE 'NW101%'

You have to repeat your expression everywhere you want to use it:
SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts
WHERE Replace(Postcode, ' ', '') LIKE 'NW101%'
or you can make it a subquery
select P
from (
SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts
) t
WHERE P LIKE 'NW101%'

To expand on Oded's answer, your conceptual model needs a slight adjustment here. Aliasing of column names (AS clauses in the SELECT list) happens very late in the processing of a SELECT, which is why alias names are not available to WHERE clauses. In fact, the only thing that happens after column aliasing is sorting, which is why (to quote the docs on SELECT):
column_alias can be used in an ORDER BY clause. However, it cannot be used in a WHERE, GROUP BY, or HAVING clause.
If you have a convoluted expression in the SELECT list, you may be worried about it 'being evaluated twice' when it appears in the SELECT list and (say) a WHERE clause - however, the query engine is clever enough to work out what's going on. If you want to avoid having the expression appear twice in your query, you can do something like
SELECT c1, c2, c3, expr1
FROM
( SELECT c1, c2, c3, some_complicated_expression AS expr1 ) inner
WHERE expr1 = condition
which avoids some_complicated_expression physically appearing twice.

if you want any hope of ever using an index, store the data in a consistent manner (with the spaces removed). Either just remove the spaces or add a persisted computed column, Then you can just select from that column and not have to add all the space removing overhead every time you run your query.
add a PERSISTED computed column:
ALTER TABLE Contacts ADD PostcodeSpaceFree AS Replace(Postcode, ' ', '') PERSISTED
go
CREATE NONCLUSTERED INDEX IX_Contacts_PostcodeSpaceFree
ON Contacts (PostcodeSpaceFree) --INCLUDE (covered columns here!!)
go
to just fix the column by removing the spaces use:
UPDATE Contacts
SET Postcode=Replace(Postcode, ' ', '')
now you can search like this, either select can use an index:
--search the PERSISTED computed column
SELECT
PostcodeSpaceFree
FROM Contacts
WHERE PostcodeSpaceFree LIKE 'NW101%'
or
--search the fixed (spaces removed column)
SELECT
Postcode
FROM Contacts
WHERE PostcodeLIKE 'NW101%'

SELECT *
FROM Contacts
WHERE ContactId IN
(SELECT a.ContactID
FROM
(SELECT ContactId, Replace(Postcode, ' ', '') AS P
FROM Contacts
WHERE Postcode LIKE '%N%W%1%0%1%') a
WHERE a.P LIKE 'NW101%')

This will work:
SELECT Replace(Postcode, ' ', '') AS P
FROM Contacts
WHERE Replace(Postcode, ' ', '') LIKE 'NW101%'

Related

How to filter SQL result-set based on aliased column that uses STUFF FOR XML PATH

I have a column that uses an alias in my SQL query. The results of the aliased column are comma-separated lists of languages spoken by the teacher.
SELECT
Teacher.LastName,
STUFF((SELECT DISTINCT ', ' + COALESCE((TeacherLanguage.LanguageName), '')
FROM TeacherLanguage
INNER JOIN TeacherLanguageRel ON Teacher.TeacherId = TeacherLanguage.TeacherId
AND TeacherLanguage.LanguageId = TeacherLanguageRel.LanguageId
FOR XML PATH('')), 1, 1, '') AS 'Languages'
FROM
Teacher
Is there a way to filter the results using the values in the Languages column? Attaching a WHERE operator to the end of the query doesn't appear to work.
For example
WHERE Languages LIKE '%spanish%'
results in an error:
Invalid column name 'Languages'.
You can't do as you intend with your existing query as the where clause is evaluated before the Languages alias is defined.
You could do what you intend by turning your existing query into a derived table by wrapping in an outer select,
select * from (
<your existing query>
)t
where Languages like '%spanish%'
However that will mean you are reading rows you don't want, which you could just filter out in your current query. I believe the following should work - untested of course.
select t.LastName, Stuff((
select distinct ', ' + Coalesce((TeacherLanguage.LanguageName), '')
from TeacherLanguage tl
join TeacherLanguageRel tlr on tlr.LanguageId=tl.LanguageId
where tl.TeacherId=t.TeacherId
and tl.language like '%spanish%'
for xml path('')
), 1, 1, '') as Languages
from Teacher t
Note I've used column aliases in your query, and quotes should be reserved for string literals, not column aliases - Languages is not a reserved word so is fine as-is. This also of course assumes SQL Server.
Additionally since SQL Server 2017, for XML path is obsolete and can be replaced with string_agg.

Use STUFF function to bring only those values which match the column in the table

I am using a stuff function as:
STUFF((SELECT DISTINCT ', ' + CAST(id as varchar)
FROM users
WHERE userID = depatments.userID
FOR XML PATH('')), 1, 1, '') AS Users
Every user has one department, I want to return those values of users table which match that department but instead it is returning all values of all rows in this column. How can I fix it?
First, my guess is that users has id, but not userid. So:
STUFF((SELECT DISTINCT ', ' + CAST(id as varchar(255))
FROM users u
WHERE u.id = departments.userID
FOR XML PATH('')
), 1, 1, ''
) AS Users
Notes:
Never use varchar (or related types) without a length. SQL Server has default lengths that vary by context and may not be big enough.
Always qualify all column references in a query, so it is clear where they come from. If you use u.userid - departments.userId in your query, then you would have gotten an error and just fixed the code.
Use table aliases so queries are easier to write and to read.

SQL Server concat/join different words in a string to get all substrings

I want to join words in a sentence in SQL server. I need it to loop through using the next word as the sgtarting word to concat each time. For example the string/sentence could be 'This is a sentence'.
I need the possible outcomes to be:
This
Thisis
Thisisa
Thisisasentence
is
isa
isasentence
a
asentence
sentence
I know how to concat but I'm not too sure how I would go about doing each word with differet first word each time.
Thanks in advance!
EDIT:
More information as requested.
I have a table Account(IS(PK), Name, Country)
I have another table accountSubstrings(SubID(pk), AccountID, Substring)
I need to break the 'Name' column into the above example 'This is a sentence' so that each substring has its own row entry in accountSubstrings. The subID is unique to each row and the AccountID will map to whichever 'Name' the substring came from. This is being done for matching purposes.
Thanks
You can do this with a recursive CTE. Basically, you want adjacent combinations. I prefer to put spaces between the words, so they are visible. The following also assumes that the words are unique:
declare #s varchar(max) = 'This is a sentence';
with words as (
select s.value as word, row_number() over (order by charindex(s.value, #s)) as seqnum
from string_split(#s, ' ') s
),
cte as (
select seqnum, word as combined, format(seqnum, '000') as seqnums
from words
union all
select w.seqnum, concat(cte.combined, ' ', w.word), concat(seqnums, ':', format(w.seqnum, '000'))
from cte join
words w
on w.seqnum = cte.seqnum + 1
)
select *
from cte
order by seqnums;
Here is a db<>fiddle.
The actually tricky part here is keeping the words in order. That is what the row_number() is doing, capturing the order -- and where the uniqueness restriction comes from. Of course, this could be replaced by a recursive CTE, and that would be fine (and allow duplicate words).
Looking to the updates you made, you can do as
SELECT *
FROM (VALUES('This is a sentence')) T(Name)
JOIN (VALUES('This'), ('is'), ('a'), ('sentence')) TT(SubString)
ON CONCAT(' ', T.Name, ' ') LIKE CONCAT(' %', TT.SubString, ' %');
Here the table T is your table Account, and the table TT is your table accountSubstrings

Using subquery with IN clause

I am stuck at one particular problem here, I am fetching ID's from one column that are like this ',90132988,90133148,72964884,' Let's say this value is stored in a column ColumnA of Table1.
I want to use this in another query that's using IN clause something like this.
SELECT *
FROM USER_GROUP
WHERE ID IN (SELECT CONCAT(CONCAT('(',REPLACE(LTRIM(RTRIM(REPLACE(COLUMNA, ',', ' '))), ' ', ',')),')') FROM Table1)
When I run this query I get invalid number error. Any suggestions ?
You get a number error because id is a number. So, Oracle wisely assumes that the subquery returns numbers.
I think the logic you want is:
SELECT ug.*
FROM USER_GROUP ug
WHERE EXISTS (SELECT 1
FROM Table1 t1
WHERE t1.COLUMNA LIKE '%,' || ug.ID || ',%'
);
I strongly discourage you from storing number lists as strings. Just store one row per number. That is really much simpler and the resulting code will be more efficient.
You can try converting "ID" using TO_CHAR, but you will lose the use of any index on ID if you have one.
SELECT *
FROM USER_GROUP
WHERE TO_CHAR(ID) IN (SELECT CONCAT(CONCAT('(',REPLACE(LTRIM(RTRIM(REPLACE(COLUMNA, ',', ' '))), ' ', ',')),')') FROM Table1)

Access SQL Is a string-attribute part of another string in the same string-attribute of the same table?

In a Access Query, which is performed by the Microsoft JET 4.0 Driver, i have to ask if a string-attribute part of another string in the same string-attribute of the same table?
Example: In the table above, there is the string-attribute 'name' and i want to have a query that gives me all [Name]-words, that are contained in another [Name]-word (in this example i want to get at least 'Attack' but there should be more words than just this).
I've already tried s.th. like this:
SELECT [Name]
FROM [t_object]
WHERE '%'+[Name]+'%' IN (SELECT [Name] FROM [t_object])
I was thinking about, if it is possible to use the InStr-Function in Combination with LIKE but i do not know how.
How can i find all words in the attribute [Name], that are already contained in another word?
In Access the wildcard char that denotes any number of chars is '*' and not '%'.
You can use the LIKE operator like this:
SELECT [Name]
FROM [t_object]
WHERE [Name] LIKE '*Attack*';
which will give all the values of column that contain 'Attack'.
If you want all the rows of [Name] where this value is contained in another [Name], you can use EXISTS:
SELECT t.[Name]
FROM [t_object] AS t
WHERE EXISTS (
SELECT 1 FROM [t_object]
WHERE [Name] <> t.[Name] AND
[Name] LIKE '*' + t.[Name] + '*'
)
or with a join to get both values:
SELECT t.[Name] AS contained, tt.[Name] AS container
FROM [t_object] AS t INNER JOIN [t_object] AS tt
ON t.[Name] <> tt.[Name] AND tt.[Name] LIKE '*' + t.[Name] + '*'
Well this would require some extra work but i can give you a head start...
At first i can't see how you can do it in one shot..maybe other contributors have a better idea but this what i tried.
At first you need to extract all the words to an extra table..lets call it NameWords
The table should have this simple structure
ID --> AutoNumber
NameWord --> ShortText
Here you will put all the words you have in Name
1 Attack
2 Pattern
The you will perform a Left Join between your current table and this table using InStr or Like and you will get all the records that have a match against NameWords . (along with the ones that don't have)
I did a little test on this and it returned everything as expected using this
SELECT [t_object].*,NameWords.NameWord
FROM [t_object] LEFT JOIN NameWordsON Instr(t_object.Name,NameWords.NameWord );