SQL Server : searching value doesn't have entry in table - sql

I have a table which has the following values:
ID | Name
---------------
1 | Anavaras
2 | Lamurep
I need a query which outputs the value which doesn't have entry in the table.
For e.g:
If my where clause contains id in('1','2','3','4'), should produce output has
3 |
4 |
for the above entries in the table.

You would put this into a "derived table" and use left join or a similar construct:
select v.id
from (values(1), (2), (3), (4)) v(id) left join
t
on t.id = v.id
where t.id is null;

Something like this:
"SELECT id FROM table WHERE name IS NULL"
I'd assume?

First you need to split your in to a table. Sample split function is here:
CREATE FUNCTION [dbo].[split]
(
#str varchar(max),
#sep char
)
RETURNS
#ids TABLE
(
id varchar(20)
)
AS
BEGIN
declare #pos int,#id varchar(20)
while len(#str)>0
begin
select #pos = charindex(#sep,#str + #sep)
select #id = LEFT(#str,#pos),#str = SUBSTRING(#str,#pos+1,10000000)
insert #ids(id) values(#id)
end
RETURN
END
Then you can use this function.
select id from dbo.split('1,2,3,4,5',',') ids
left join myTable t on t.id=ids.id
where t.id is null
-- if table ID is varchar then '''1'',''2'',''3'''

Related

Can't get table function to work with if statement

trying to create a table function that returns a two column table that must be conditioned with an if statement.
I have two other functions that do this separate but cant get it to work when combined.
create function dbo.PDM_GetCategorytable (#Mystring varchar(200) ,#appid int)
returns table
as -- begin
--return
begin
-- declare #MyString varchar(200);
--set #MyString= 'Fred mtf is dead'--'RE: [Encrypt]]FW: MTF Military ---
--UPDATE URGENT'
--declare #AppId int =5
declare #Dubb Table (Col varchar(200));
insert into #dubb
(Col )
values ( #Mystring);
if ((select top 1 categoryid from #dubb a
left join
(SELECT (pass.Recordid) as categoryid , pass.appid,
pass.Priority_Identifier, pass.PrioritySortorder, pass.Category,
ps.Search_String AS SrchVar
FROM dbo.PDM_PriorityAssignments AS pass INNER JOIN
dbo.PDM_Priority_Search AS ps ON pass.Recordid =
ps.PriorityAssignmentid where pass.appid=#AppId ) b on a.col like '%' +
b.SrchVar + '%'
order by PrioritySortorder) is not null)--'where
appid=#AppId
begin
select top 1 categoryid,Category from #dubb a
left join
(SELECT (pass.Recordid) as categoryid , pass.appid,
pass.Priority_Identifier, pass.PrioritySortorder, pass.Category,
ps.Search_String AS SrchVar
FROM dbo.PDM_PriorityAssignments AS pass INNER JOIN
dbo.PDM_Priority_Search AS ps ON pass.Recordid =
ps.PriorityAssignmentid where pass.appid=#AppId ) b on a.col like '%' +
b.SrchVar + '%'
order by PrioritySortorder;
end
else
begin
select recordid as categoryid,Category FROM dbo.PDM_PriorityAssignments
AS pass where appid=#AppId and Priority_Identifier like 'Routine'
end
return
end;
expected results will be the returning of two columns , category id, and category.
seems like a duplicate for Multi statement table valued function
Multi-statement Table Valued Function vs Inline Table Valued Function
here is an example how to achieve this.
CREATE FUNCTION foo
(#param int)
RETURNS
#T TABLE (id int, val varchar)
As
BEGIN
IF (#param = 1)
insert into #t Select 1, 'a';
else
insert into #t Select 0, 'a';
return ;
END
GO
The problem is exactly because of the algorithm you need to use.
That requirement prevents you from returning just a table. On this scenarios you need to do something a bit different.
First define the resulting table and then, insert the values as you need.
I canĀ“t run it without your data but it should work.
CREATE FUNCTION dbo.PDM_GetCategorytable (
#Mystring VARCHAR(200)
,#appid INT
)
RETURNS #GetCategorytable TABLE
(
[CategoryId] INT,
[Category] VARCHAR(200))
AS -- begin
--return
BEGIN
-- declare #MyString varchar(200);
--set #MyString= 'Fred mtf is dead'--'RE: [Encrypt]]FW: MTF Military ---
--UPDATE URGENT'
--declare #AppId int =5
DECLARE #Dubb TABLE (Col VARCHAR(200));
INSERT INTO #dubb (Col)
VALUES (#Mystring);
IF (
(
SELECT TOP 1 categoryid
FROM #dubb a
LEFT JOIN (
SELECT (pass.Recordid) AS categoryid
,pass.appid
,pass.Priority_Identifier
,pass.PrioritySortorder
,pass.Category
,ps.Search_String AS SrchVar
FROM dbo.PDM_PriorityAssignments AS pass
INNER JOIN dbo.PDM_Priority_Search AS ps ON pass.Recordid = ps.PriorityAssignmentid
WHERE pass.appid = #AppId
) b ON a.col LIKE '%' + b.SrchVar + '%'
ORDER BY PrioritySortorder
) IS NOT NULL
) --'where
-- NOT SURE WHAT YOU ARE TRYING TO DO WITH THIS -- appid = #AppId
BEGIN
insert into #GetCategorytable(Categoryid, Category)
SELECT TOP 1 categoryid
,Category
FROM #dubb a
LEFT JOIN (
SELECT (pass.Recordid) AS categoryid
,pass.appid
,pass.Priority_Identifier
,pass.PrioritySortorder
,pass.Category
,ps.Search_String AS SrchVar
FROM dbo.PDM_PriorityAssignments AS pass
INNER JOIN dbo.PDM_Priority_Search AS ps ON pass.Recordid = ps.PriorityAssignmentid
WHERE pass.appid = #AppId
) b ON a.col LIKE '%' + b.SrchVar + '%'
ORDER BY PrioritySortorder;
END
ELSE
BEGIN
insert into #GetCategorytable(Categoryid, Category)
SELECT recordid AS categoryid ,Category FROM dbo.PDM_PriorityAssignments AS pass
WHERE appid = #AppId
AND Priority_Identifier LIKE 'Routine'
END
RETURN
END;

How select rows which contains at least one value from other table and is not equal? TSQL

As per subject, I have a problem with query. Problems occurs because table is not normalized. Schema of DB looks like:
Table A Columns:
ID - A,A,B,B,C,D (not unique)
AR - "N10 N12", "N1 N2 N3", "N1"
AR in A table is char(100)
ARID in AWS table is char(6)
The result from subselect is a column, with rows of chars. It can be inserted in a temporary table, but I suppose any other variable type can't store it.
It's simplified example, I want to select all rows where AR contains at least N1. In real case I have a lot of values to find in AR column, from other table.
I tried:
Contains - it will work, but #table variable can't be used with this statement
Inner Join and subquery in query - generally it works, but it's problem, it returns only rows where values are equals. For example, when I'm selecting for N1 it will be returned, but "N1 N2 N3" not and it should be in results.
My actual query:
select *
from A
where AR in (select ARID
From AWS
group by ARID
having count(*)>2)
order by EvaluationTime desc
Thanks and Regards,
B.
Given two tables with the values you've provided (tell me if there are more), you'll first need a way to split the values in A.AR into manageable strings. I use this User Defined Function:
CREATE FUNCTION [dbo].[UDF_StringDelimiter]
/*********************************************************
** Takes Parameter "LIST" and transforms it for use **
** to select individual values or ranges of values. **
** **
** EX: 'This,is,a,test' = 'This' 'Is' 'A' 'Test' **
*********************************************************/
(
#LIST VARCHAR(8000)
,#DELIMITER VARCHAR(255)
)
RETURNS #TABLE TABLE
(
[RowID] INT IDENTITY
,[Value] VARCHAR(255)
)
WITH SCHEMABINDING
AS
BEGIN
DECLARE
#LISTLENGTH AS SMALLINT
,#LISTCURSOR AS SMALLINT
,#VALUE AS VARCHAR(255)
;
SELECT
#LISTLENGTH = LEN(#LIST) - LEN(REPLACE(#LIST,#DELIMITER,'')) + 1
,#LISTCURSOR = 1
,#VALUE = ''
;
WHILE #LISTCURSOR <= #LISTLENGTH
BEGIN
INSERT INTO #TABLE (Value)
SELECT
CASE
WHEN #LISTCURSOR < #LISTLENGTH
THEN SUBSTRING(#LIST,1,PATINDEX('%' + #DELIMITER + '%',#LIST) - 1)
ELSE SUBSTRING(#LIST,1,LEN(#LIST))
END
;
SET #LIST = STUFF(#LIST,1,PATINDEX('%' + #DELIMITER + '%',#LIST),'')
;
SET #LISTCURSOR = #LISTCURSOR + 1
;
END
;
RETURN
;
END
;
Then, with those two tables:
DECLARE #TABLE TABLE (ID CHAR(1), AR VARCHAR(55));
INSERT INTO #TABLE VALUES ('A','N1 N3 N4');
INSERT INTO #TABLE VALUES ('B','N2');
INSERT INTO #TABLE VALUES ('C','N1');
INSERT INTO #TABLE VALUES ('D','N5');
INSERT INTO #TABLE VALUES ('E','N2 N1');
DECLARE #TABLE2 TABLE (RowID INT IDENTITY, ARID VARCHAR(55));
INSERT INTO #TABLE2 (ARID) VALUES ('N1');
Using a CROSS APPLY and LEFT JOIN, I get the responses:
SELECT A.ID AS [A.ID], A.AR AS [A.AR],B.ARID AS [B.ARID]
FROM #TABLE A
CROSS APPLY dbo.UDF_StringDelimiter(A.AR,' ') X
INNER JOIN #TABLE2 B
ON B.ARID = X.Value
ORDER BY ID
;
Returns:
A.ID A.AR B.ARID
-----------------------------------
A N1 N3 N4 N1
C N1 N1
E N2 N1 N1
Of course you could also group this, return it as a delimited list, etc. Give me more details and maybe I can give you a better response.
If you are using 2 tables, CROSS APPLY is your friend.
create table #ARID(id varchar(10), ar varchar(100));
insert into #ARID values
('a', 'n1 n2'),
('a', 'n10 n11'),
('b', 'n1'),
('b', 'n11 n13 15'),
('c', 'n3'),
('c', 'n14 n12');
This is the table that contains values to search:
create table #TO_SEARCH(val varchar(10))
insert into #TO_SEARCH values ('n10'), ('n11');
And this simple query returns each row that contains al least one of these values:
SELECT ca.*
FROM #TO_SEARCH ts
CROSS APPLY (SELECT id, ar
FROM #ARID
WHERE ar LIKE ('%' + ts.val + '%')
) ca
You can invert the tables, the result is the same:
SELECT id, ar
FROM #ARID
CROSS APPLY (SELECT val from #TO_SEARCH) ts
WHERE ar LIKE ('%' + ts.val + '%')
+----+------------+
| id | ar |
+----+------------+
| a | n10 n11 |
+----+------------+
| a | n10 n11 |
+----+------------+
| b | n11 n13 15 |
+----+------------+
Can check it here: http://rextester.com/CUEU92118

What is the best way to join between two table which have coma seperated columns

Table1
ID Name Tags
----------------------------------
1 Customer1 Tag1,Tag5,Tag4
2 Customer2 Tag2,Tag6,Tag4,Tag11
3 Customer5 Tag6,Tag5,Tag10
and Table2
ID Name Tags
----------------------------------
1 Product1 Tag1,Tag10,Tag6
2 Product2 Tag2,Tag1,Tag5
3 Product5 Tag1,Tag2,Tag3
what is the best way to join Table1 and Table2 with Tags column?
It should look at the tags column which coma seperated on table 2 for each coma seperated tag on the tags column in the table 1
Note: Tables are not full-text indexed.
The best way is not to have comma separated values in a column. Just use normalized data and you won't have trouble with querying like this - each column is supposed to only have one value.
Without this, there's no way to use any indices, really. Even a full-text index behaves quite different from what you might thing, and they are inherently clunky to use - they're designed for searching for text, not meaningful data. In the end, you will not get much better than something like
where (Col like 'txt,%' or Col like '%,txt' or Col like '%,txt,%')
Using a xml column might be another alternative, though it's still quite a bit silly. It would allow you to treat the values as a collection at least, though.
I don't think there will ever be an easy and efficient solution to this. As Luaan pointed out, it is a very bad idea to store data like this : you lose most of the power of SQL when you squeeze what should be individual units of data into a single cell.
But you can manage this at the slight cost of creating two user-defined functions. First, use this brilliant recursive technique to split the strings into individual rows based on your delimiter :
CREATE FUNCTION dbo.TestSplit (#sep char(1), #s varchar(512))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT pn AS SplitIndex,
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS SplitPart
FROM Pieces
)
Then, make a function that takes two strings and counts the matches :
CREATE FUNCTION dbo.MatchTags (#a varchar(512), #b varchar(512))
RETURNS INT
AS
BEGIN
RETURN
(SELECT COUNT(*)
FROM dbo.TestSplit(',', #a) a
INNER JOIN dbo.TestSplit(',', #b) b
ON a.SplitPart = b.SplitPart)
END
And that's it, here is a test roll with table variables :
DECLARE #A TABLE (Name VARCHAR(20), Tags VARCHAR(100))
DECLARE #B TABLE (Name VARCHAR(20), Tags VARCHAR(100))
INSERT INTO #A ( Name, Tags )
VALUES
( 'Customer1','Tag1,Tag5,Tag4'),
( 'Customer2','Tag2,Tag6,Tag4,Tag11'),
( 'Customer5','Tag6,Tag5,Tag10')
INSERT INTO #B ( Name, Tags )
VALUES
( 'Product1','Tag1,Tag10,Tag6'),
( 'Product2','Tag2,Tag1,Tag5'),
( 'Product5','Tag1,Tag2,Tag3')
SELECT * FROM #A a
INNER JOIN #B b ON dbo.MatchTags(a.Tags, b.Tags) > 0
I developed a solution as follows:
CREATE TABLE [dbo].[Table1](
Id int not null,
Name nvarchar(250) not null,
Tag nvarchar(250) null,
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Table2](
Id int not null,
Name nvarchar(250) not null,
Tag nvarchar(250) null,
) ON [PRIMARY]
GO
get sample data for Table1, it will insert 28000 records
INSERT INTO Table1
SELECT CustomerID,CompanyName, (FirstName + ',' + LastName)
FROM AdventureWorks.SalesLT.Customer
GO 3
sample data for Table2.. i need same tags for Table2
declare #tag1 nvarchar(50) = 'Donna,Carreras'
declare #tag2 nvarchar(50) = 'Johnny,Caprio'
get sample data for Table2, it will insert 9735 records
INSERT INTO Table2
SELECT ProductID,Name, (case when(right(ProductID,1)>=5) then #tag1 else #tag2 end)
FROM AdventureWorks.SalesLT.Product
GO 3
My Solution
create TABLE #dt (
Id int IDENTITY(1,1) PRIMARY KEY,
Tag nvarchar(250) NOT NULL
);
I've create temp table and i will fill with Distinct Tag-s in Table1
insert into #dt(Tag)
SELECT distinct Tag
FROM Table1
Now i need to vertical table for tags
create TABLE #Tags ( Tag nvarchar(250) NOT NULL );
Now i'am fill #Tags table with While, you can use Cursor but while is faster
declare #Rows int = 1
declare #Tag nvarchar(1024)
declare #Id int = 0
WHILE #Rows>0
BEGIN
Select Top 1 #Tag=Tag,#Id=Id from #dt where Id>#Id
set #Rows =##RowCount
if #Rows>0
begin
insert into #Tags(Tag) SELECT Data FROM dbo.StringToTable(#Tag, ',')
end
END
last step : join Table2 with #Tags
select distinct t.*
from Table2 t
inner join #Tags on (',' + t.Tag + ',') like ('%,' + #Tags.Tag + ',%')
Table rowcount= 28000 Table2 rowcount=9735 select is less than 2 second
I use this kind of solution with paths of trees. First put a comma at the very begin and at the very end of the string. Than you can call
Where col1 like '%,' || col2 || ',%'
Some database index the column also for the like(postgres do it partially), therefore is also efficient. I don't know sqlserver.

How to compare two sub queries in one sql statement

I have a table tbl_Country, which contains columns called ID and Name. The Name column has multiple country names separated by comma, I want the id when I pass multiple country names to compare with Name column values. I am splitting the country names using a function - the sample query looks like this:
#country varchar(50)
SELECT *
FROM tbl_Country
WHERE (SELECT *
FROM Function(#Country)) IN (SELECT *
FROM Function(Name))
tbl_country
ID Name
1 'IN,US,UK,SL,NZ'
2 'IN,PK,SA'
3 'CH,JP'
parameter #country ='IN,SA'
i have to get
ID
1
2
NOTE: The Function will split the string into a datatable
Try this
SELECT * FROM tbl_Country C
LEFT JOIN tbl_Country C1 ON C1.Name=C.Country
Try this:
SELECT *
FROM tbl_Country C
WHERE ',' + #country + ',' LIKE '%,' + C.Name + ',%';
Basically, by specifying multiple values in a single column, you are violating the 1st NF. Therefore, the following might not be a good approach but provides the solution that you are looking for:
declare #country varchar(50)= 'IN,SA'
declare #counterend int
declare #counterstart int =1
declare #singleCountry varchar(10)
set #counterend = (select COUNT(*) from fnSplitStringList(#country))
create table #temp10(
id int
,name varchar(50))
while #counterstart<= #counterend
begin
;with cte as (
select stringliteral country
, ROW_NUMBER() over (order by stringliteral) countryseq
from fnSplitStringList(#country))
select #singleCountry = (select country FROM cte where countryseq=#counterstart)
insert into #temp10(id, name)
select * from tbl_country t1
where not exists (select id from #temp10 t2 where t1.id=t2.id)
and name like '%' + #singleCountry +'%'
set #counterstart= #counterstart+1
end
select * from #temp10
begin drop table #temp10 end
How it works: It splits the passed string and ranks it. Afterwards, it loops through all the records for every single Value(country) produced and inserts them into temptable.
try this,
select a.id FROM tbl_Country a inner join
(SELECT country FROM dbo.Function(#Country)) b on a.name=b.country

sql query help - trying to get rid of temp tables

I have the following tables -
Resource
--------------------
Id, ProjectId, Hours, ApproverId
Project
--------------------
Id, Name
The input is ApproverId. I need to retrieve all the rows that have matching ApproverId (simple enough). And for every resource that I get back, I also need to get their hours (same table) whose approverId is not the one that is passed in (business requirement, to be grayed out in the UI). What I'm doing right now is - get all resources based on ApproverId, stored them in a temp table, then do a distinct on Resource.Id, store it in a different temp table, and then for every Resource.Id, get the rows where the ApproverId is not the one that is passed. Can I combine it all in a single query instead of using temp tables?
Thanks!
Edit: I'm using SQL Server 2008 R2.
Edit 2: Here's my stored procedure. I have changed the logic slightly after reading the comments. Can we get rid of all temp tables and make it faster -
ALTER PROCEDURE GetResourceDataByApprover
#ApproverId UNIQUEIDENTIFIER
AS
CREATE TABLE #Table1
(
Id SMALLINT PRIMARY KEY
IDENTITY(1, 1) ,
ResourceId UNIQUEIDENTIFIER
)
CREATE TABLE #Table2
(
ResourceId UNIQUEIDENTIFIER ,
ProjectId UNIQUEIDENTIFIER ,
ProjectName NVARCHAR(1024)
)
INSERT INTO #Table1
SELECT DISTINCT
ResourceId
FROM dbo.Resource T
WHERE T.ApproverId = #ApproverId
DECLARE #i INT
DECLARE #numrows INT
DECLARE #resourceId UNIQUEIDENTIFIER
SET #i = 1
SET #numrows = ( SELECT COUNT(*)
FROM #Table1
)
IF #numrows > 0
WHILE ( #i <= ( SELECT MAX(Id)
FROM #Table1
) )
BEGIN
SET #resourceId = ( SELECT ResourceId
FROM #Table1
WHERE Id = #i
)
INSERT INTO #Table2
SELECT
T.ResourceId ,
T.ProjectId ,
P.Name AS ProjectName
FROM dbo.[Resource] T
INNER JOIN dbo.Project P ON T.ProjectId = P.ProjectId
WHERE T.ResourceId = #resourceId
SET #i = #i + 1
END
SELECT *
FROM #Table1
SELECT *
FROM #Table2
DROP TABLE #Table1
DROP TABLE #Table2
This query should return two rows for every resource, one for the specified approver and one for all other approvers.
SELECT
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END AS Approver,
SUM(Hours) AS Hours
FROM Resource
GROUP BY
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END
Do you want to know how concrete Approver wastes his time?
SELECT p.Id, p.Name, SUM(r.Hours) as TotalHours
FROM Resource r
LEFT JOIN Project p
ON r.ProjectId = p.Id
WHERE ApproverId = %ConcreteApproverId%
GROUP BY p.Id, p.Name
HAVING SUM(r.Hours) > 0
This query will produce this table example:
+-----+----------+-------+
| Id | Project | Hours |
+-----+----------+-------+
| 203 | ProjectA | 25 |
| 202 | ProjectB | 34 |
| 200 | ProjectC | 46 |
+-----+----------+-------+