Why SQL Server has error when i use LIKE operator? - sql-server

I want to select employees that have the third character of the first name is ‘l’ (as image below)
After executing, there are 4 correct record and 1 incorrect.
I dont't understand why that record having first name is 'Philip' with l is fourth character is selected?
My SQL Statements

Yes this is a COLLATION specific probleme and those who gave a negative rating indiscriminately should think a little more than instinctively vote!
With the Vietnamese_100_... collation (which I think that is the case for our user HaNgocHieu) or for some others collations like Welsh_100... the Ph two letters are considered as only one and the result is that the query returns also Philip.
As a test :
CREATE TABLE #employee
( fname VARCHAR(20) NOT NULL,
lname VARCHAR(20) NOT NULL);
INSERT INTO #employee(fname,lname)
VALUES ('Philip','Cramer');
SELECT *
FROM #employee e
where fname + lname COLLATE Vietnamese_CI_AI LIKE '__l%';
fname lname
-------------------- --------------------
Philip Cramer
SELECT *
FROM #employee e
where fname + lname COLLATE French_CI_AI LIKE '__l%';
fname lname
-------------------- --------------------
SELECT *
FROM #employee e
where fname + lname COLLATE Welsh_100_CI_AI LIKE '__l%';
fname lname
-------------------- --------------------
Philip Cramer
So SQL Server has no error, nor HaNgocHieu does not make any mistake, but using a specific collations with non specific data can cause some trouble that can be solved in using an international COLLATION like those in latin

your data
declare #employee TABLE (
fname VARCHAR(20) NOT NULL
,lname VARCHAR(20) NOT NULL
);
INSERT INTO #employee(fname,lname) VALUES ('Helen','Bennett');
INSERT INTO #employee(fname,lname) VALUES ('Helvetius','Nagy');
INSERT INTO #employee(fname,lname) VALUES ('Palle','Ibsen');
INSERT INTO #employee(fname,lname) VALUES ('Philip','Cramer');
INSERT INTO #employee(fname,lname) VALUES ('Roland','Mendel');
for searching in column for specific charachter using CHARINDEX and SUBSTRING would be much better. In Addition, you must choose the best collate ( sorting rules, case, and accent sensitivity properties for your data) in your table such as COLLATE SQL_Latin1_General_CP1_CS_AS
SUBSTRING
select concat(fname,' ',lname) as fullname
FROM #employee e
where SUBSTRING(fname, 3, 1)='l' and fname SQL_Latin1_General_CP1_CS_AS
ORDER BY e.fname
CHARINDEX
select concat(fname,' ',lname) as fullname
FROM #employee e
where CHARINDEX('l', fname) = 3 and fname SQL_Latin1_General_CP1_CS_AS
ORDER BY e.fname

Related

Separated by Comma in SQL Server [duplicate]

Consider a database table holding names, with three rows:
Peter
Paul
Mary
Is there an easy way to turn this into a single string of Peter, Paul, Mary?
If you are on SQL Server 2017 or Azure, see Mathieu Renda answer.
I had a similar issue when I was trying to join two tables with one-to-many relationships. In SQL 2005 I found that XML PATH method can handle the concatenation of the rows very easily.
If there is a table called STUDENTS
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Result I expected was:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
I used the following T-SQL:
SELECT Main.SubjectID,
LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
(
SELECT DISTINCT ST2.SubjectID,
(
SELECT ST1.StudentName + ',' AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)') [Students]
FROM dbo.Students ST2
) [Main]
You can do the same thing in a more compact way if you can concat the commas at the beginning and use substring to skip the first one so you don't need to do a sub-query:
SELECT DISTINCT ST2.SubjectID,
SUBSTRING(
(
SELECT ','+ST1.StudentName AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)'), 2, 1000) [Students]
FROM dbo.Students ST2
This answer may return unexpected results For consistent results, use one of the FOR XML PATH methods detailed in other answers.
Use COALESCE:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') + Name
FROM People
Just some explanation (since this answer seems to get relatively regular views):
Coalesce is really just a helpful cheat that accomplishes two things:
1) No need to initialize #Names with an empty string value.
2) No need to strip off an extra separator at the end.
The solution above will give incorrect results if a row has a NULL Name value (if there is a NULL, the NULL will make #Names NULL after that row, and the next row will start over as an empty string again. Easily fixed with one of two solutions:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL
or:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') +
ISNULL(Name, 'N/A')
FROM People
Depending on what behavior you want (the first option just filters NULLs out, the second option keeps them in the list with a marker message [replace 'N/A' with whatever is appropriate for you]).
SQL Server 2017+ and SQL Azure: STRING_AGG
Starting with the next version of SQL Server, we can finally concatenate across rows without having to resort to any variable or XML witchery.
STRING_AGG (Transact-SQL)
Without grouping
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
With grouping:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
With grouping and sub-sorting
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
One method not yet shown via the XML data() command in SQL Server is:
Assume a table called NameList with one column called FName,
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
returns:
"Peter, Paul, Mary, "
Only the extra comma must be dealt with.
As adopted from #NReilingh's comment, you can use the following method to remove the trailing comma. Assuming the same table and column names:
STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
In SQL Server 2005
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
In SQL Server 2016
you can use the FOR JSON syntax
i.e.
SELECT per.ID,
Emails = JSON_VALUE(
REPLACE(
(SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
FROM Person per
And the result will become
Id Emails
1 abc#gmail.com
2 NULL
3 def#gmail.com, xyz#gmail.com
This will work even your data contains invalid XML characters
the '"},{"_":"' is safe because if you data contain '"},{"_":"', it will be escaped to "},{\"_\":\"
You can replace ', ' with any string separator
And in SQL Server 2017, Azure SQL Database
You can use the new STRING_AGG function
In MySQL, there is a function, GROUP_CONCAT(), which allows you to concatenate the values from multiple rows. Example:
SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people
FROM users
WHERE id IN (1,2,3)
GROUP BY a
Use COALESCE - Learn more from here
For an example:
102
103
104
Then write the below code in SQL Server,
Declare #Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers
SELECT #Numbers = COALESCE(#Numbers + ',', '') + Number
FROM TableName where Number IS NOT NULL
SELECT #Numbers
The output would be:
102,103,104
PostgreSQL arrays are awesome. Example:
Create some test data:
postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');
INSERT 0 3
test=# select * from names;
name
-------
Peter
Paul
Mary
(3 rows)
Aggregate them in an array:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
Convert the array to a comma-delimited string:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
DONE
Since PostgreSQL 9.0 it is even easier, quoting from deleted answer by "horse with no name":
select string_agg(name, ',')
from names;
Oracle 11g Release 2 supports the LISTAGG function. Documentation here.
COLUMN employees FORMAT A50
SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;
DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
Warning
Be careful implementing this function if there is possibility of the resulting string going over 4000 characters. It will throw an exception. If that's the case then you need to either handle the exception or roll your own function that prevents the joined string from going over 4000 characters.
In SQL Server 2005 and later, use the query below to concatenate the rows.
DECLARE #t table
(
Id int,
Name varchar(10)
)
INSERT INTO #t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d'
SELECT ID,
stuff(
(
SELECT ','+ [Name] FROM #t WHERE Id = t.Id FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT ID FROM #t ) t
A recursive CTE solution was suggested, but no code was provided. The code below is an example of a recursive CTE.
Note that although the results match the question, the data doesn't quite match the given description, as I assume that you really want to be doing this on groups of rows, not all rows in the table. Changing it to match all rows in the table is left as an exercise for the reader.
;WITH basetable AS (
SELECT
id,
CAST(name AS VARCHAR(MAX)) name,
ROW_NUMBER() OVER (Partition BY id ORDER BY seq) rw,
COUNT(*) OVER (Partition BY id) recs
FROM (VALUES
(1, 'Johnny', 1),
(1, 'M', 2),
(2, 'Bill', 1),
(2, 'S.', 4),
(2, 'Preston', 5),
(2, 'Esq.', 6),
(3, 'Ted', 1),
(3, 'Theodore', 2),
(3, 'Logan', 3),
(4, 'Peter', 1),
(4, 'Paul', 2),
(4, 'Mary', 3)
) g (id, name, seq)
),
rCTE AS (
SELECT recs, id, name, rw
FROM basetable
WHERE rw = 1
UNION ALL
SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw + 1
FROM basetable b
INNER JOIN rCTE r ON b.id = r.id AND b.rw = r.rw + 1
)
SELECT name
FROM rCTE
WHERE recs = rw AND ID=4
OPTION (MAXRECURSION 101)
I don't have access to a SQL Server at home, so I'm guess at the syntax here, but it's more or less:
DECLARE #names VARCHAR(500)
SELECT #names = #names + ' ' + Name
FROM Names
In SQL Server 2017 or later versions, you can use the STRING_AGG() function to generate comma-separated values. Please have a look below at one example.
SELECT
VendorId, STRING_AGG(FirstName,',') UsersName
FROM Users
WHERE VendorId != 9
GROUP BY VendorId
You need to create a variable that will hold your final result and select into it, like so.
Easiest Solution
DECLARE #char VARCHAR(MAX);
SELECT #char = COALESCE(#char + ', ' + [column], [column])
FROM [table];
PRINT #char;
In SQL Server vNext this will be built in with the STRING_AGG function. Read more about it in STRING_AGG (Transact-SQL).
A ready-to-use solution, with no extra commas:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
An empty list will result in NULL value.
Usually you will insert the list into a table column or program variable: adjust the 255 max length to your need.
(Diwakar and Jens Frandsen provided good answers, but need improvement.)
This worked for me (SQL Server 2016):
SELECT CarNamesString = STUFF((
SELECT ',' + [Name]
FROM tbl_cars
FOR XML PATH('')
), 1, 1, '')
Here is the source: https://www.mytecbits.com/
And a solution for MySQL (since this page show up in Google for MySQL):
SELECT [Name],
GROUP_CONCAT(DISTINCT [Name] SEPARATOR ',')
FROM tbl_cars
From MySQL documentation.
Using XML helped me in getting rows separated with commas. For the extra comma we can use the replace function of SQL Server. Instead of adding a comma, use of the AS 'data()' will concatenate the rows with spaces, which later can be replaced with commas as the syntax written below.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
Here's a sample:
DECLARE #t TABLE (name VARCHAR(10))
INSERT INTO #t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM #t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
With the other answers, the person reading the answer must be aware of a specific domain table such as vehicle or student. The table must be created and populated with data to test a solution.
Below is an example that uses SQL Server "Information_Schema.Columns" table. By using this solution, no tables need to be created or data added. This example creates a comma separated list of column names for all tables in the database.
SELECT
Table_Name
,STUFF((
SELECT ',' + Column_Name
FROM INFORMATION_SCHEMA.Columns Columns
WHERE Tables.Table_Name = Columns.Table_Name
ORDER BY Column_Name
FOR XML PATH ('')), 1, 1, ''
)Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME
On top of Chris Shaffer's answer:
If your data may get repeated, such as
Tom
Ali
John
Ali
Tom
Mike
Instead of having Tom,Ali,John,Ali,Tom,Mike
You can use DISTINCT to avoid duplicates and get Tom,Ali,John,Mike:
DECLARE #Names VARCHAR(8000)
SELECT DISTINCT #Names = COALESCE(#Names + ',', '') + Name
FROM People
WHERE Name IS NOT NULL
SELECT #Names
MySQL complete example:
We have users who can have much data and we want to have an output, where we can see all users' data in a list:
Result:
___________________________
| id | rowList |
|-------------------------|
| 0 | 6, 9 |
| 1 | 1,2,3,4,5,7,8,1 |
|_________________________|
Table Setup:
CREATE TABLE `Data` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);
CREATE TABLE `User` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `User` (`id`) VALUES
(0),
(1);
Query:
SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
DECLARE #Names VARCHAR(8000)
SELECT #name = ''
SELECT #Names = #Names + ',' + Names FROM People
SELECT SUBSTRING(2, #Names, 7998)
This puts the stray comma at the beginning.
However, if you need other columns, or to CSV a child table you need to wrap this in a scalar user defined field (UDF).
You can use XML path as a correlated subquery in the SELECT clause too (but I'd have to wait until I go back to work because Google doesn't do work stuff at home :-)
To avoid null values you can use CONCAT()
DECLARE #names VARCHAR(500)
SELECT #names = CONCAT(#names, ' ', name)
FROM Names
select #names
I really liked elegancy of Dana's answer and just wanted to make it complete.
DECLARE #names VARCHAR(MAX)
SET #names = ''
SELECT #names = #names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET #sSql = LEFT(#sSql, LEN(#sSql) - 1)
If you want to deal with nulls you can do it by adding a where clause or add another COALESCE around the first one.
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(COALESCE(#Names + ', ', '') + Name, #Names) FROM People
This answer will require some privilege on the server to work.
Assemblies are a good option for you. There are a lot of sites that explain how to create it. The one I think is very well explained is this one.
If you want, I have already created the assembly, and it is possible to download the DLL file here.
Once you have downloaded it, you will need to run the following script in your SQL Server:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE;
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;
CREATE Assembly concat_assembly
AUTHORIZATION dbo
FROM '<PATH TO Concat.dll IN SERVER>'
WITH PERMISSION_SET = SAFE;
GO
CREATE AGGREGATE dbo.concat (
#Value NVARCHAR(MAX)
, #Delimiter NVARCHAR(4000)
) RETURNS NVARCHAR(MAX)
EXTERNAL Name concat_assembly.[Concat.Concat];
GO
sp_configure 'clr enabled', 1;
RECONFIGURE
Observe that the path to assembly may be accessible to server. Since you have successfully done all the steps, you can use the function like:
SELECT dbo.Concat(field1, ',')
FROM Table1
Since SQL Server 2017 it is possible to use the STRING_AGG function.
I usually use select like this to concatenate strings in SQL Server:
with lines as
(
select
row_number() over(order by id) id, -- id is a line id
line -- line of text.
from
source -- line source
),
result_lines as
(
select
id,
cast(line as nvarchar(max)) line
from
lines
where
id = 1
union all
select
l.id,
cast(r.line + N', ' + l.line as nvarchar(max))
from
lines l
inner join
result_lines r
on
l.id = r.id + 1
)
select top 1
line
from
result_lines
order by
id desc
In Oracle, it is wm_concat. I believe this function is available in the 10g release and higher.
For Oracle DBs, see this question: How can multiple rows be concatenated into one in Oracle without creating a stored procedure?
The best answer appears to be by #Emmanuel, using the built-in LISTAGG() function, available in Oracle 11g Release 2 and later.
SELECT question_id,
LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id
as #user762952 pointed out, and according to Oracle's documentation http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php, the WM_CONCAT() function is also an option. It seems stable, but Oracle explicitly recommends against using it for any application SQL, so use at your own risk.
Other than that, you will have to write your own function; the Oracle document above has a guide on how to do that.

SQL to join nvarchar(max) column with int column

I need some expert help to do left join on nvarchar(max) column with an int column. I have a Company table with EmpID as nvarchar(max) and this column holds multiple employee ID's separated with commas:
1221,2331,3441
I wanted to join this column with Employee table where EmpID is int.
I did something like below, But this doesn't work when I have 3 empId's or just 1 empID.
SELECT
A.*, B.empName AS empName1, D.empName AS empName2
FROM
[dbo].[Company] AS A
LEFT JOIN
[dbo].[Employee] AS B ON LEFT(A.empID, 4) = B.empID
LEFT JOIN
[dbo].[Employee] AS D ON RIGHT(A.empID, 4) = D.empID
My requirement is to get all empNames if there are multiple empID's in separate columns. Would highly appreciate any valuable input.
You should, if possible, normalize your database.
Read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutly yes!.
If, however, you can't change the database structure, you can use LIKE:
SELECT A.*, B.empName AS empName1, D.empName AS empName2
FROM [dbo].[Company] AS A
LEFT JOIN [dbo].[Employee] AS B ON ',' + A.empID + ',' LIKE '%,'+ B.empID + ',%'
You can give STRING_SPLIT a shot.
SQL Server (starting with 2016)
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
CREATE TABLE #Test
(
RowID INT IDENTITY(1,1),
EID INT,
Names VARCHAR(50)
)
INSERT INTO #Test VALUES (1,'John')
INSERT INTO #Test VALUES (2,'James')
INSERT INTO #Test VALUES (3,'Justin')
INSERT INTO #Test VALUES (4,'Jose')
GO
CREATE TABLE #Test1
(
RowID INT IDENTITY(1,1),
ID VARCHAR(MAX)
)
INSERT INTO #Test1 VALUES ('1,2,3,4')
GO
SELECT Value,T.* FROM #Test1
CROSS APPLY STRING_SPLIT ( ID , ',' )
INNER JOIN #Test T ON value = EID
It sounds like you need a table to link employees to companies in a formal way. If you had that, this would be trivial. As it is, this is cumbersome and super slow. The below script creates that linkage for you. If you truly want to keep your current structure (bad idea), then the part you want is under the "insert into..." block.
--clean up the results of any prior runs of this test script
if object_id('STACKOVERFLOWTEST_CompanyEmployeeLink') is not null
drop table STACKOVERFLOWTEST_CompanyEmployeeLink;
if object_id('STACKOVERFLOWTEST_Employee') is not null
drop table STACKOVERFLOWTEST_Employee;
if object_id('STACKOVERFLOWTEST_Company') is not null
drop table STACKOVERFLOWTEST_Company;
go
--create two example tables
create table STACKOVERFLOWTEST_Company
(
ID int
,Name nvarchar(max)
,EmployeeIDs nvarchar(max)
,primary key(id)
)
create table STACKOVERFLOWTEST_Employee
(
ID int
,FirstName nvarchar(max)
,primary key(id)
)
--drop in some test data
insert into STACKOVERFLOWTEST_Company values(1,'ABC Corp','1,2,3,4,50')
insert into STACKOVERFLOWTEST_Company values(2,'XYZ Corp','4,5,6,7,8')--note that annie(#4) works for both places
insert into STACKOVERFLOWTEST_Employee values(1,'Bob') --bob works for abc corp
insert into STACKOVERFLOWTEST_Employee values(2,'Sue') --sue works for abc corp
insert into STACKOVERFLOWTEST_Employee values(3,'Bill') --bill works for abc corp
insert into STACKOVERFLOWTEST_Employee values(4,'Annie') --annie works for abc corp
insert into STACKOVERFLOWTEST_Employee values(5,'Matthew') --Matthew works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(6,'Mark') --Mark works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(7,'Luke') --Luke works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(8,'John') --John works for xyz corp
insert into STACKOVERFLOWTEST_Employee values(50,'Pat') --Pat works for XYZ corp
--create a new table which is going to serve as a link between employees and their employer(s)
create table STACKOVERFLOWTEST_CompanyEmployeeLink
(
CompanyID int foreign key references STACKOVERFLOWTEST_Company(ID)
,EmployeeID INT foreign key references STACKOVERFLOWTEST_Employee(ID)
)
--this join looks for a match in the csv column.
--it is horrible and slow and unreliable and yucky, but it answers your original question.
--drop these messy matches into a clean temp table
--this is now a formal link between employees and their employer(s)
insert into STACKOVERFLOWTEST_CompanyEmployeeLink
select c.id,e.id
from
STACKOVERFLOWTEST_Company c
--find a match based on an employee id followed by a comma or preceded by a comma
--the comma is necessary so we don't accidentally match employee "5" on "50" or similar
inner join STACKOVERFLOWTEST_Employee e on
0 < charindex( convert(nvarchar(max),e.id) + ',',c.employeeids)
or 0 < charindex(',' + convert(nvarchar(max),e.id) ,c.employeeids)
order by
c.id, e.id
--show final results using the official linking table
select
co.Name as Employer
,emp.FirstName as Employee
from
STACKOVERFLOWTEST_Company co
inner join STACKOVERFLOWTEST_CompanyEmployeeLink link on link.CompanyID = co.id
inner join STACKOVERFLOWTEST_Employee emp on emp.id = link.EmployeeID

SQL Distinct function din't work

Here i have a table called tblemployee which consists id,name and salary column.The name and salary column consists five rows, name column consists 3 different name (i.e each name in name column does not match with another name) while the salary column consists the same integer value (i.e 40,000 in each row of salary column).
Table tblemployee structure
name|salary
-----------
max |40000
rob |40000
jon |40000
Now what i want is that, i want all the names from name column but only one salary value from salary column as shown below:
name|salary
-----------
max |40000
rob |
jon |
Sql Server query i have tried which didn't give the expected output
select DISTINCT salary,name from tblabca
Declare #tblemployee table (name varchar(25),salary int)
Insert Into #tblemployee values
('max',40000),
('rob',40000),
('jon',40000),
('joseph',25000),
('mary',25000)
Select Name
,Salary = case when RN=1 then cast(Salary as varchar(25)) else '' end
From (
Select *
,RN = Row_Number() over (Partition By Salary Order By Name)
,DR = Dense_Rank() over (Order By Salary)
From #tblemployee
) A
Order by DR Desc,RN
Returns
Name Salary
jon 40000
max
rob
joseph 25000
mary
"GROUP BY" and group_concat would suit your case. Please try like this
select salary, group_concat(name) from tblabca group by salary;
Reference: GROUP BY, GROUP_CONCAT
You will never get the result as you stated. Because DISTINCT operator works on a SET. not on individual column. In a relational database you will only work with sets.
So the combination of Salary and Name will be treated as Distinct.
But if you want you can get names in comma concatenated list like below
SELECT SALARY
, STUFF ((SELECT ','+NAME From TableA T2
WHERE T1.SALARY = T2.SALARY FOR XML PATH('')
),1,1,'') FROM TABLEA T1
As others already stated, you are definately not looking for DISTINCT operator.
The distinct operator will work upon the entire result set, meaning you'll get result rows that are unique (column by column).
Although with some rework you might end up with the result you want, do you really want the result in such a not uniform way? I mean, getting a list of names on the name column and only one salary on the salary column do not look like a nice result set to work with.
Maby you should work on your code to account for the change you want to make in the query.
declare #tblemployee Table(
id int identity(1,1) primary key not null,
name nvarchar(MAX) not null,
salary int not null
);
declare #Result Table(
name nvarchar(MAX) not null,
salaryString nvarchar(MAX)
);
insert into #tblemployee(name,salary) values ('joseph' ,25000);
insert into #tblemployee(name,salary) values ('mary' ,25000);
insert into #tblemployee(name,salary) values ('Max' ,40000);
insert into #tblemployee(name,salary) values ('rob' ,40000);
insert into #tblemployee(name,salary) values ('jon' ,40000);
declare #LastSalary int = 0;
declare #name nvarchar(MAX);
declare #salary int;
DECLARE iterator CURSOR LOCAL FAST_FORWARD FOR
SELECT name,
salary
FROM #tblemployee
Order by salary desc
OPEN iterator
FETCH NEXT FROM iterator INTO #name,#salary
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#salary!=#LastSalary)
BEGIN
SET #LastSalary = #salary
insert into #Result(name,salaryString)
values(#name,#salary+'');
END
ELSE
BEGIN
insert into #Result(name,salaryString)
values(#name,'');
END
FETCH NEXT FROM iterator INTO #name,#salary
END
Select * from #Result

How to query with unknown combination of optional parameters in SQL Server without cursors?

I have a search that has three input fields (for arguments sake, let's say LastName, Last4Ssn, and DateOfBirth). These three input fields are in a dynamic grid where the user can choose to search for one or more combinations of these three fields. For example, a user might search based on the representation below:
LastName Last4Ssn DateOfBirth
-------- -------- -----------
Smith NULL 1/1/1970
Smithers 1234 NULL
NULL 5678 2/2/1980
In the example, the first row represents a search by LastName and DateOfBirth. The second, by LastName and Last4Ssn. And, the third, by Last4Ssn and DateOfBirth. This example is a bit contrived as the real-world scenario has four fields. At least two of the fields must be filled (don't worry about how to validate) with search data and it is possible that all fields are filled out.
Without using cursors, how does one use that data to join to existing tables using the given values in each row as the filter? Currently, I have a cursor that goes through each row of the above table, performs the join based on the columns that have values, and inserts the found data into a temp table. Something like this:
CREATE TABLE #results (
Id INT,
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
DECLARE #lastName VARCHAR (26)
DECLARE #last4Ssn VARCHAR (4)
DECLARE #dateOfBirth DATETIME
DECLARE search CURSOR FOR
SELECT LastName, Last4Ssn, DateOfBirth
FROM #searchData
OPEN search
FETCH NEXT FROM search
INTO #lastName, #last4Ssn, #dateOfBirth
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #results
SELECT s.Id, s.LastName, s.Last4Ssn, s.DateOfBirth
FROM SomeTable s
WHERE Last4Ssn = ISNULL(#last4Ssn, Last4Ssn)
AND DateOfBirth = ISNULL(#dateOfBirth, DateOfBirth)
AND (
LastName = ISNULL(#lastName, LastName)
OR LastName LIKE #lastName + '%'
)
FETCH NEXT FROM search
INTO #lastName, #last4Ssn, #dateOfBirth
END
CLOSE search
DEALLOCATE search
I was hoping there was some way to avoid the cursor to make the code a bit more readable. Performance is not an issue as the table used to search will never have more than 5-10 records in it, but I would think that for more than a few, it'd be more efficient to query the data all at once rather than one row at a time. The SomeData table in my example can be very large.
I don't see why you can't just join the two tables together:
CREATE TABLE #results (
Id INT,
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
INSERT INTO #results
select s.id, s.lastname, s.last4ssn, s.dateofbirth
from SomeTable s
join #searchData d
ON s.last4ssn = isnull(d.last4ssn, s.last4ssn)
AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth)
AND (s.lastname = isnull(d.lastname, s.lastname) OR
OR s.lastname like d.lastname + '%')
EDIT:
Since the data is large, we'll need some good indices. One index isn't good enough since you effectively have 3 clauses OR'd together. So the first step is to create those indices:
CREATE TABLE SomeData (
Id INT identity(1,1),
LastName VARCHAR (26),
Last4Ssn VARCHAR (4),
DateOfBirth DATETIME
)
create nonclustered index ssnlookup on somedata (last4ssn)
create nonclustered index lastnamelookup on somedata (lastname)
create nonclustered index doblookup on somedata (dateofbirth)
The next step involves crafting the query to use those indices. I'm not sure what the best way here is, but I think it's to have 4 queries union'd together:
with searchbyssn as (
select somedata.* from somedata join #searchData
on somedata.last4ssn = #searchData.last4ssn
), searchbyexactlastname as (
select somedata.* from somedata join #searchData
on somedata.lastname = #searchData.lastname
), searchbystartlastname as (
select somedata.* from somedata join #searchData
on somedata.lastname like #searchdata.lastname + '%'
), searchbydob as (
select somedata.* from somedata join #searchData
on somedata.dateofbirth = #searchData.dateofbirth
), s as (
select * from searchbyssn
union select * from searchbyexactlastname
union select * from searchbystartlastname
union select * from searchbydob
)
select s.id, s.lastname, s.last4ssn, s.dateofbirth
from s
join #searchData d
ON (d.last4ssn is null or s.last4ssn = d.last4ssn)
AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth)
AND (s.lastname = isnull(d.lastname, s.lastname)
OR s.lastname like d.lastname + '%')
Here's a fiddle showing the 4 index seeks: http://sqlfiddle.com/#!6/3741d/3
It shows significant resource usage for the union, but I think that would be negligible compared to the index scans for large tables. It wouldn't let me generate more than a few hundred rows of sample data. Since the number of result rows is small, it is not expensive to join to #searchData at the end and filter all the results again.

New database: should it be accent sensitive?

I am preparing a new database server, where I will migrate data from a big, existing, multilingual database (mostly english/french/spanish text, rarely special characters from other languages for e.g. city names).
My question is: should it be accent sensitive?
Users would be happy if the search made no difference between cafe and café.
But as a DBA, I am worried: I have never seen a database not suffering from bad characters conversions at least once in a while. If I choose accent insensitive, how will I query the database and ask "give me all books where the title contains a special characters"?
If I have a way to do this, I would happily go for accent insensitive.
It should depend on your general usage.
This doesn't preclude you changing it for a specific query
eg
declare #t1 table (word nvarchar(50) collate Latin1_General_CI_AI)
declare #t2 table (word nvarchar(50) collate Latin1_General_CI_AS)
insert #t1 values ('cafe'),('restaurant'), ('café')
insert #t2 select * from #t1
select * from #t1 where word like 'cafe'
select * from #t1 where word like 'cafe' collate Latin1_General_CI_AS
select * from #t1 where word like 'café'
select * from #t1 where word like 'café' collate Latin1_General_CI_AS
select * from #t2 where word like 'cafe'
select * from #t2 where word like 'cafe' collate Latin1_General_CI_AI
select * from #t2 where word like 'café'
select * from #t2 where word like 'café' collate Latin1_General_CI_AI
You can change collation at select time:
with t as (
select 'ali' as w union
select 'àli' as w
)
select *
into #t
from t;
select * from #t
where w collate Latin1_General_CS_AS_KS_WS like '%à%'
w
---
àli
select * from #t
where w collate Traditional_Spanish_ci_ai like '%à%'
w
---
ali
àli

Resources