select query to match multiple columns with multiple keywords - sql

I need to write a select query that will search a single table for words taken from a user input query string like "John Doe Engineering". The string can consist of a single or multiple words. The query string will be passed into a stored procedure as a parameter. In total there area about 20 columns that need to be searched. My first thought was something like this:
SELECT *
FROM Employees
WHERE FirstName LIKE '%John%' OR FirstName LIKE '%Doe%' OR FirstName LIKE '%Engineering%'
WHERE LastName LIKE '%John%' OR LastName LIKE '%Doe2%' OR LastName LIKE '%Engineering%'
WHERE Manager LIKE '%John%' OR Manager LIKE '%Doe%'OR Manager LIKE '%Engineering%'
WHERE Department LIKE '%John%' OR Department LIKE '%Doe%'OR Department LIKE '%Engineering%'
--repeat for 16 more table columns
But I'm not sure how to best generate the query syntax based upon the user query string input. Furthermore this seems like it would be a highly inefficient query. Would it be better to look at using full text search in this case? I'm wondering what the best approach might be?

As everyone else said - Full text search is probably the best solution for this type of thing. That said, I thought it would be fun to offer a T-SQL solution.
Quick Disclaimer 1
*I would strongly encourage you not to use the solutions below - this was intended to be a fun little SQL exercise; the performance would be bad. Also - I demonstrate two very efficient ways to split a string: one using Jeff Moden's DelimitedSplit8K, the other technique using PARSENAME *
Quick Disclaimer 2
I should point out a problem with concatenating the columns into a single string as a couple people suggested -- it can lead to false positives; consider the following query:
DECLARE #search varchar(100) = 'ab';
WITH sampleData AS (SELECT fn, ln FROM (VALUES ('aa', 'bb'), ('cc', 'dd')) t(fn,ln))
SELECT *
FROM sampleData
WHERE CONCAT(fn,ln) LIKE '%'+#search+'%';
The above query will return the first record even though the "ab" does not exist in either column. For that reason you would change the WHERE (or CHARINDEX in John's example) to look like this:
WHERE CONCAT(fn, '|||', ln) LIKE '%'+#search+'%';
My Solutions
-- SAMPLE DATA
-------------------------------------------------
DECLARE #employees TABLE
(
FirstName varchar(100),
LastName varchar(100),
Manager varchar(100),
Department varchar(100)
);
INSERT #employees
SELECT *
FROM
(
VALUES
('bob', '****', 'ddd', 'sss'),
('fff', 'fred', 'obx', 'ccc'),
('Sue', 'abcd', 'ddd', 'zzz'),
('ddd', 'dcba', '123', 'fobbb')
) xx(x1, x2, x3, x4);
-- Solution #1: when #search has <= 4 "items"
-------------------------------------------------
DECLARE #search varchar(100) = 'xx bb ff zz';
SELECT e.*
--,PARSENAME(REPLACE(#search,' ','.'), N) AS matchedPattern
FROM (VALUES (1),(2),(3),(4)) t(n)
CROSS JOIN #employees e
WHERE
CHARINDEX
(
PARSENAME(REPLACE(#search,' ','.'), N),
CONCAT(FirstName, '|||', LastName, '|||', Manager, '|||', Department)
) > 0;
-- Solution #2: when #search has (or can have) > 4 "items"
-------------------------------------------------
-- for this you will need delimitedsplit8k: http://www.sqlservercentral.com/articles/Tally+Table/72993/
SELECT e.*
FROM dbo.delimitedsplit8k(#search, ' ')
CROSS JOIN #employees e
WHERE
CHARINDEX
(
item,
CONCAT(FirstName, '|||', LastName, '|||', Manager, '|||', Department)
) > 0;

Related

Get all results in a single row

When I take a 'SELECT *' query from a table I would like all the results to be displayed in a single 'string' format.
For example:
SELECT * FROM Employees;
Returns:
Id FirstName LastName Age
1 John Smith 30
Instead I would like to get:
Id=1,FirstName=John,LastName=Smith,Age=30
But, if I do exactly the same for the query: SELECT * FROM Cars;
I want this query to adapt and just dynamically gets the columns from the table 'Cars' and do the same with it.
Does one of this select meets your requirements ?
Declare #Employees table(Id integer, FirstName varchar(100), LastName varchar(100), Age integer)
insert into #Employees values (1, 'John', 'Smith', 30), (2, 'John', 'Doe', 23)
select CONCAT('Id=', Id, ', FirstName=', FirstName, ', LastName=', LastName, ', Age=', Age) as Employees from #Employees
select
STUFF ((
select CONCAT('; ', 'Id=', Id, ', FirstName=', FirstName, ', LastName=', LastName, ', Age=', Age) from #Employees
FOR XML PATH('')),1,2, '') as employees
OUTPUT :
Employees
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Id=1, FirstName=John, LastName=Smith, Age=30
Id=2, FirstName=John, LastName=Doe, Age=23
Employees
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Id=1, FirstName=John, LastName=Smith, Age=30; Id=2, FirstName=John, LastName=Doe, Age=23
i don't have the Employees table so i made my own in a variable, but don't mind this, you can test this code without modifying the requests
I want to add that it's code I will use in a trigger, I will be using the 'inserted' and 'deleted' tables. This makes it much harder since I'm not able to use them in a stored procedure.

SQL Substring Functions

I have a column named full name. I want to split them into two columns, first name and last name.
I am having a trouble with a persons with two first names. If the first name encounters a space it was considered last name.
I want this:
Full name : John Michael Smith
Firstname: John Michael
Lastname: Smith
However, I am getting:
Firstname: John
Lastname: Michael Smith
Try:
declare #n varchar(max) = 'John Michael Smith'
select REVERSE(SUBSTRING(REVERSE(#n), CHARINDEX(' ', REVERSE(#n)), 100)) FirstName
Check the below link:
SQL SERVER – Reverse String Word By Word – Part 2
You can split the string by checking for the index of last space and using it with LEFT and RIGHT function.
DECLARE #name varchar(100)= 'John Michael Smith'
SELECT LEFT(#name,LEN(#name)-CHARINDEX(' ',REVERSE(#name))) AS [Firstname],RIGHT(#name,CHARINDEX(' ',REVERSE(#name))) AS [LastName]
--Create table from below script
create table users(id int IDENTITY(1,1) PRIMARY KEY, name varchar(50));
--Insert Record for query for testing
insert into users(name)
values('John Abraham'),
('John Mark Abraham'),
('John Vens Abraham'),
('John Abraham');
--Run below query for getting all inserted records.
select * from users;
--Run below query for getting first and last name from name
select id , name, SUBSTRING(name,0, CHARINDEX(last_name,name)) as first_name, last_name from
(
select id, name, REVERSE(SUBSTRING(REVERSE(name),0,CHARINDEX(' ',REVERSE(name)))) as last_name from users
) as A
You can hi-jack 'parsename'. This only works up to 4 intervals but a nice option;
declare #name varchar(100)
set #name='John Michael Smith'
select PARSENAME(REPLACE(#name,' ','.'),1) -- returns Smith
select PARSENAME(REPLACE(#name,' ','.'),2) -- returns Micheal
select PARSENAME(REPLACE(#name,' ','.'),3) -- Returns John
select PARSENAME(REPLACE(#name,' ','.'),4) -- returns NULL

SQL Extract Values from a String

How do I extract values from a string? I'm trying to separate into 3 new columns. A separate column for city, state and zipcode.
I've tried
select address2,
left(address2, charindex('',address2)-1)
from table
and ---when I try the below code I get "Invalid length parameter passed to the left or substring function"
,LTRIM(substring(a.Address2, CHARINDEX(' ', a.Address2)+1, CHARINDEX(' ', substring(a.address2, charindex(' ',
a.address2)+1, len(a.address2)))-1))
I can break out the city (except for West Warwick) using the following code, but not sure how to make it work for state and zip. This also removes the error.
SUBSTRING(Address2,1,CHARINDEX(' ', a.address2+ ' ')-1) as city
Any ideas what to try?
It looks like your zip codes and your states are all the same length. If that is true, you should be able to use something like this:
SELECT
LEFT(a.Address2,LEN(a.Address2) - 13) AS City,
RIGHT(LEFT(a.Address2,LEN(a.Address2) - 11),2) AS State,
RIGHT(a.Address2,10) AS Zip_Code
FROM
table;
DEMO CODE
Create the table and data:
CREATE TABLE MyTable (Address2 VARCHAR(100));
INSERT INTO MyTable
VALUES
('SAN DIEGO CA 92128-1234'),
('WEST WARWICK RI 02893-1349'),
('RICHMOND IN 47374-9409');
The query:
SELECT
LEFT(Address2,LEN(Address2) - 13) AS City,
RIGHT(LEFT(Address2,LEN(Address2) - 11),2) AS State,
RIGHT(Address2,10) AS Zip_Code
FROM
MyTable;
The output:
Since you only have 3 parts (City/State/Zip) you can take advantage of a function called parsename in SQL Server 2008 and later. (The original intent of the function is to parse out object names.)
Using a combination of the replace and parsename functions will allow you to be able to separate the data into 3 parts, even if the length of the State (not likely) or the Zip (more likely) change.
Example Data:
create table #my_table
(
address2 varchar(75) not null
)
insert into #my_table values ('CONNERSVILLE IN 47331-3351')
insert into #my_table values ('WEST WARWICK RI 02893-1349')
insert into #my_table values ('RICHMOND IN 47374-9409')
insert into #my_table values ('WILLIAMSBURG IN 47393-9617')
insert into #my_table values ('FARMERSVILLE OH 45325-9226')
--this record is an example of a likely scenario for when the zip length would change.
insert into #my_table values ('WILLIAMSBURG IN 47393')
Solution:
with len_vals as
(
select t.address2
, len(parsename(replace(t.address2,' ','.'), 1)) as zip_len
, len(parsename(replace(t.address2,' ','.'), 2)) as st_len
from #my_table as t
group by t.address2
)
select left(a.address2, len(a.address2) - b.zip_len - b.st_len - 2) as city
, substring(a.address2, len(a.address2) - b.zip_len - 2, b.st_len) as st
, right(a.address2, b.zip_len) as zip_code
from #my_table as a
inner join len_vals as b on a.address2 = b.address2
Results:

Matching First and Last Name on two different tables

I am trying to match the first name varchar (50) and last name varchar(50) from table A to the first name varchar(50) and last name varchar(50) on table B. The issue is that both table contain a lot of shortened first names like the name Andrew in table A and there might be a matching record with the last name but the first name is Andy so it comes up as not a match. Is there anyway to get around this in SQL. The shortened names is a vice verse problem meaning that both Table A and Table B have some shortened names.
Here are some more examples:
This is my current code.
Select *
FROM TableA p
JOIN TableB e ON e.CompanyNumber = 1 and e.LastName like '%' + rtrim(ltrim(p.lastname)) + '%'
and e.FirstName like '%' + ltrim(rtrim(p.firstname)) + '%'
NOTE: This is the only way to match the tables together.
Create a third table that associates Long-form and short-form names.
For examle:
Long Form Short Form
Andrew Andy
Andrew Drew
David Dave
William Will
William Bill
William Billy
William Willy
Provided you use a 3rd Table to hold you Long/Short Names as so.
CREATE TABLE TableNames
([Id] int, [OfficialName] varchar(7), [Alias] varchar(7))
;
INSERT INTO TableNames
([Id], [OfficialName], [Alias])
VALUES
(1, 'Andrew', 'Andy'),
(2, 'Andrew', 'Andrew'),
(3, 'William', 'Bill'),
(4, 'William', 'William'),
(5, 'David', 'Dave'),
(6, 'David', 'David')
The following query should give you what you are looking for.
SELECT *
FROM (
SELECT TableA.Id AS T1_Id
,CompanyId AS T1_CompanyId
,FirstName AS T1_FirstName
,LastName AS T1_LastName
,TableNames.OfficialName AS OfficialName
FROM tableA
INNER JOIN tableNames ON TableA.FirstName = TableNames.Alias
) T1
,(
SELECT tableB.Id AS T2_Id
,CompanyId AS T2_CompanyId
,FirstName AS T2_FirstName
,LastName AS T2_LastName
,TableNames.OfficialName AS OfficialName
FROM tableB
INNER JOIN tableNames ON TableB.FirstName = TableNames.Alias
) T2
WHERE T1.T1_CompanyId = T2.T2_CompanyId
AND T1.OfficialName = T2.OfficialName
AND T1.T1_LastName = T2.T2_LastName
I set up my solution sqlfiddle at http://sqlfiddle.com/#!3/64514/2
I hope this helps.
Select *
<br>FROM TableA pJOIN TableB e
<br>ON e.CompanyNumber = 1
<br>and e.LastName like '%' + rtrim(ltrim(p.lastname)) + '%'
<br>OR
<br>e.FirstName like '%' + ltrim(rtrim(p.firstname)) + '%'
Now this depends how you determine it is match,
example:
TableA:
--------
Rownum FristName LastName
1 Andy Smith
2 Andy Mathew
TableB:
--------
Rownum FristName LastName
1 Logan Andy
2 Mathew Andy
Now will you consider first record from both the tables as a match
What about the second record in both the tables?
Basing on this we can even change the query

Inserting multiple names in same cell with separated commas

Previously, I was trying to keep ALL previous last names of an employee in a table cell with commas (see below) but I didn’t know how. Someone then suggested using normalization which I’m not sure if it’ll be easier in this situation. My boss wants to display all previous last names of an employee on a web page each time she edits her account info. Simply put, when Judy changes her last name again – her last name Kingsley should be inserted behind Smith. So, my question is back to whether or not it is possible to add multiple last names in the same cell, separated with commas as I thought it’ll be simpler when I use a variable on the web page to display all the Alias at once? Yet, I’ve no clue the complexity to write the codes for this. Any help is truly appreciated.
Current SQL table
+---------------+-----------------+----------------+--------------------+
People FirstName LastName Alias
+---------------+-----------------+----------------+--------------------+
002112 Judy Smith Hall
Preferred
+---------------+-----------------+----------------+--------------------+
People FirstName LastName Alias
+---------------+-----------------+----------------+--------------------+
002112 Judy Kingsley Hall, Smith
Keep the database normalized.
People:
(Id, Firstname, Lastname)
LastnameHistory:
(PeopleId, OldLastname, NewLastname, DateChanged)
You can the create a view which would be a "GROUP_CONCAT" type of query to transform the data as required.
An example:
DECLARE #people TABLE ( id INT IDENTITY(1,1), fname VARCHAR(50), lname VARCHAR(50))
DECLARE #lnameHistory TABLE ( id INT IDENTITY(1,1), people_id INT, lname VARCHAR(50), date_changed DATETIME)
INSERT INTO #people (fname, lname)
VALUES ('john', 'smith'), ('jane', 'doe')
INSERT INTO #lnameHistory (people_id, lname, date_changed)
VALUES (2, 'shakespeare', '2012-01-01'), (2, 'einstein', '2013-12-12')
;WITH group_concat AS
(
SELECT people_id, LEFT(lnames , LEN(lnames )-1) AS lnames
FROM #lnameHistory AS o
CROSS APPLY
(
SELECT lname + ', '
FROM #lnameHistory AS i
WHERE o.people_id = i.people_id
ORDER BY date_changed ASC
FOR XML PATH('')
) pre_trimmed (lnames)
GROUP BY people_id, lnames
)
SELECT p.*, gc.lnames FROM #people p
JOIN group_concat gc ON gc.people_id = p.id
Some reference for syntax:
SQL Server CROSS APPLY and OUTER APPLY
XML Data (SQL Server)
Assuming your update statement is a stored procedure taking in parameters of #personId and #newLastName:
EDIT
Sorry, wrong version of SQL. Have to do it the old school way!
UPDATE PeopleTable
SET Alias = Alias + ', ' + LastName,
LastName = #newLastName
WHERE
People = #personId
When you update the table for a new LastName, use something like this:
UPDATE <table> SET Alias = Alias + ', ' + LastName, LastName = <newLastName>