Function in SQL for JOIN condition - sql-server

This is my query:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main JOIN
dbo.MainDataTable mainData ON
(main.dataId = mainData.dataId) AND
(mainData.Type = 1 OR mainData.Type = 2 OR mainData.Type = 3)
I use similar query in many views. But the last condition is always the same everywhere: main.Type = 1 OR main.Type = 2 OR main.Type = 3.
I wondering how I can extract it to some SQL function. I never do any function before.
So it would be looks like this:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main
JOIN dbo.MainDataTable mainData ON main.dataId = mainData.dataId
AND (GetConditionForType()) -- more or less ;)

You can create a view like this:
create view dbo.FilteredMainDataTable
as
select ...
from dbo.MainDataTable
where [Type] in (1,2,3);
And then use this view in all your queries instead of dbo.MainDataTable. What the compiler does then it "opens" the view as its definition in every query that uses this view and that is what you want. Functions do not do this and they are not thought as "macro substitution"
If you insist on function you can create it but it will not have a "look" as you want. It can be inline table-valued function like this:
create function dbo.fn_FilteredMainDataTable(#n1 int, #n2 int, #n3 int)
returns TABLE
return select Id, ...
from FilteredMainDataTable
where type in (#n1, #n2, #n3);
Then you join to this function instead of MainDataTable like this:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main
JOIN dbo.fn_FilteredMainDataTable(1,2,3) mainData ON main.dataId = mainData.dataId
^^^^^^^^^^^^^^^^^^^^^^^
The following code shows how inline table function like a view push in the seek predicate:
if object_id('dbo.num') is not null drop table dbo.num;
go
select top 1000000
isnull(row_number() over(order by 1 / 0), 0) as n,
isnull(row_number() over(order by 1 / 0), 0) as n1
into dbo.num
from sys.columns c1 cross join sys.columns c2 cross join sys.columns c3;
go
alter table dbo.num add constraint PK_num_n primary key (n);
go
create index ix_n1_n on dbo.num (n1, n);
go
if object_id('dbo.fn_num_between') is not null drop function dbo.fn_num_between;
go
create function dbo.fn_num_between(#n1 int, #n2 int)
returns table
as
return
select n, n1
from dbo.num
where n between #n1 and #n2;
go
select *
from dbo.fn_num_between(1, 1000)
where n1 = 5;

You can do this with an inline table valued function.
You would define the function as:
create function fnFilteredTypes ()
returns table as return
(
select 1 as type
union
select 2 as type
union
select 3 as type
);
Then you can join against this function like so:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable AS main
JOIN dbo.MainDataTable AS mainData
ON main.dataId = mainData.dataId
JOIN fnFilteredTypes() As typeFilter
ON(mainData.Type=typeFilter.type)
I'm not sure this query is really what you want, but basically you get the idea that you can use the function like it's a parameterized view & join directly against it's fields. Just don't forget it's a function & you call it with round brackets.

Related

Select distinct on declared table

I need to create concatenated query will get data with checking data in other tables.
Firstly I declared (and filled it) two tables with list of ID I'll check in other tables. On next step (2) I declared new table and filled it values I get by some checking params. This table contains only one column (ID).
After filling if I execute SELECT DISTINCT query from this table I'll get really unique ID. It's OK.
But on next step (3) I declare more one table and filling it by 3 tables. Of course it contains many duplicates. But I must create this query for checking and concatenating. And after that if I execute select distinct h from #NonUnicalConcat it returns many duplicate IDs.
What I did wrong? Where is an error?
USE CurrentBase;
--STEP 1
DECLARE #TempCSTable TABLE(TempTableIDColumn int);
INSERT INTO #TempCSTable VALUES('3'),('4');
DECLARE #TempCVTable TABLE(TempTableIDColumn int);
INSERT INTO #TempCVTable VALUES('2'),('13');
--STEP 2
DECLARE #TempIdTable TABLE(id int);
INSERT INTO #TempIdTable
SELECT TT1.ID
FROM Table1 AS TT1
LEFT OUTER JOIN Table2 ON Table2.ID = TT1.OptionalColumn
LEFT OUTER JOIN Table3 AS TT2 ON TT2.ID = TT1.OptionalColumn
LEFT OUTER JOIN Table4 AS TT3 ON TT3.ID = TT2.OptionalColumn
WHERE TT1.ValueDate > '2020-06-30'
AND TT1.ValueDate < '2020-08-04'
AND TT1.OptBool = '1'
AND TT1.OptBool2 = '0'
AND EXISTS
(
SELECT Table5.ID
FROM Table5
WHERE Table5.ID = TT1.ID
AND Table5.CV IN
(
SELECT TempTableIDColumn
FROM #TempCVTable
)
AND Table5.OptBool = '1'
)
AND EXISTS
(
SELECT Table6.ID
FROM Table6
WHERE Table6.IID = TT3.ID
AND Table6.CS IN
(
SELECT TempTableIDColumn
FROM #TempCSTable
)
);
SELECT distinct * FROM #TempIdTable;--this code realy select distinct
--STEP 3
DECLARE #NonUnicalConcat TABLE(c int, s int, h int);
INSERT INTO #NonUnicalConcat
SELECT TT1.TempTableIDColumn AS cc,
TT2.TempTableIDColumn AS ss,
TT3.id AS hh
FROM #TempCVTable AS TT1,
#TempCSTable AS TT2,
#TempIdTable AS TT3
WHERE NOT EXISTS
(
SELECT HID
FROM OtherBase.dbo.Table1
WHERE HID = TT3.id
AND CS = TT2.TempTableIDColumn
AND CV = TT1.TempTableIDColumn
);
select distinct h from #NonUnicalConcat;--this code return many duplicates

SQL COLUMN ERROR for JOIN

I have a Dtltable
tabid TickNUM TickType Amount
001-FGF C2001 Credit 133
001-FGF Tk002 Token 23
001-FGF Tk003 Token 43
Is there anyway, i can pull all the tabid data using single TickNum, coz the tabid is same for all TickNum.
Select * from Dtltable
where tickNum = 'C2001'
but it displays only particluar TickNum row. I need all rows with similar tabid's as well. Not sure how to write logic.
Using a JOIN
SELECT d1.*
FROM Dtltable d1
INNER JOIN Dtltable d2 ON d2.tabid = d1.tabid AND d2.TickNUM = 'C2001'
One method is a subquery:
select d.*
from dtltable d
where d.tabid = (select d2.tabid from dtltable d2 where d2.tickNum = 'C2001');
If the subquery could return more than one row, use in instead of =.
Though this is a bit long, you can achieve this by first selecting distinct data, then by using inner join. Below how I did that.
CReate table #temptab
(
tabid nvarchar(20) null,
ticknum nvarchar(10)null,
ticktype varchar(20)null
)
Create table #temptab1
(
tabid nvarchar(20)null,
ticknum nvarchar(10)null,
ticktype varchar(20)null
)
insert into #temptab1(tabid,ticknum)
select distinct tabid , ticknum from #temptab where ticknum='C2001' // you can pass #param instead of hardcoded value.
//below line, where you will get data:
select t.tabid,t.ticknum,t.ticktype from #temptab1 t1 inner join #temptab t on t1.tabid= t.tabid

Unable concate NULL value in SQL using CONCAT, COALESCE and ISNULL

I have a query with multiple joins where I want to combine records from two columns into one. If one column is empty then I want to show one column value as result. I tried with CONCAT, COALEASE and ISNULL but no luck. What am I missing here?
My objective is, create one column which has combination of s.Script AS Original and FromAnotherTable from query. Below query runs but throws Invalid column name 'Original' and Invalid column name 'FromAnotherTable'. when I try to use CONCAT, COALEASE or ISNULL .
SQL Query:
SELECT DISTINCT
c.Name AS CallCenter,
LTRIM(RTRIM(s.Name)) Name,
d.DNIS,
s.ScriptId,
s.Script AS Original,
(
SELECT TOP 5 CCSL.Line+'; '
FROM CallCenterScriptLine CCSL
WHERE CCSL.ScriptId = s.ScriptId
ORDER BY ScriptLineId FOR XML PATH('')
) AS FromAnotherTable,
--CONCAT(s.Script, SELECT TOP 5 CCSL.Line+'; ' FROM dbo.CallCenterScriptLine ccsl WHERE ccsl.ScriptId = s.ScriptId ORDER BY ccsl.ScriptLineId xml path(''))
--CONCAT(Original, FromAnotherTable) AS Option1,
--COALESCE(Original, '') + FromAnotherTable AS Option2,
--ISNULL(Original, '') + FromAnotherTable AS Option3,,
r.UnitName AS Store,
r.UnitNumber
FROM CallCenterScript s WITH (NOLOCK)
INNER JOIN CallCenterDNIS d WITH (NOLOCK) ON d.ScriptId = s.ScriptId
INNER JOIN CallCenter c WITH (NOLOCK) ON c.Id = s.CallCenterId
INNER JOIN CallCenterDNISRestaurant ccd WITH (NOLOCK) ON ccd.CallCenterDNISId = d.CallCenterDNISId
INNER JOIN dbo.Restaurant r WITH (NOLOCK) ON r.RestaurantID = ccd.CallCenterRestaurantId
WHERE c.Id = 5
AND (1 = 1)
AND (s.IsDeleted = 0 OR s.IsDeleted IS NULL)
ORDER BY DNIS ASC;
Output:
This works:
DECLARE #Column1 VARCHAR(50) = 'Foo',
#Column2 VARCHAR(50) = NULL;
SELECT CONCAT(#Column1,#Column2);
SELECT COALESCE(#Column2, '') + #Column1
SELECT ISNULL(#Column2, '') + #Column1
So I am not sure what I am missing in my original query.
Look at row 3 in the results you are getting. In your concatenated columns (Option1, 2, 3) you are getting the first script column twice. Not the first one + the second one like you expect.
The reason is because you've aliased your subquery "script" which is the same name as another column in your query, which makes it ambiguous.
Change the alias of the subquery and the problem should go away. I'm frankly surprised your query didn't raise an error.
EDIT: You can't use a column alias in another column's definition in the same level of the query. In other words, you can't do this:
SELECT
SomeColumn AS A
, (Subquery that returns a column) AS B
, A + B --this is not allowed
FROM ...
You can either create a CTE that returns the aliased columns and then concatenate them in the main query that selects from the CTE, or you have to use the original sources of the aliases, like so:
SELECT
SomeColumn AS A
, (Subquery that returns a column) AS B
, SomeColumn + (Subquery that returns a column) --this is fine
FROM ...
I took another approach where instead on creating separate column, I used ISNULL in my subQuery which returns my desired result.
Query:
SELECT DISTINCT
c.Name AS CallCenter,
LTRIM(RTRIM(s.Name)) Name,
d.DNIS,
s.ScriptId,
s.Script AS Original,
(
SELECT TOP 5 ISNULL(CCSL.Line, '')+'; ' + ISNULL(s.Script, '')
FROM CallCenterScriptLine CCSL
WHERE CCSL.ScriptId = s.ScriptId
ORDER BY ScriptLineId FOR XML PATH('')
) AS FromAnotherTable,
r.UnitName AS Store,
r.UnitNumber
FROM CallCenterScript s WITH (NOLOCK)
INNER JOIN CallCenterDNIS d WITH (NOLOCK) ON d.ScriptId = s.ScriptId
INNER JOIN CallCenter c WITH (NOLOCK) ON c.Id = s.CallCenterId
INNER JOIN CallCenterDNISRestaurant ccd WITH (NOLOCK) ON ccd.CallCenterDNISId = d.CallCenterDNISId
INNER JOIN dbo.Restaurant r WITH (NOLOCK) ON r.RestaurantID = ccd.CallCenterRestaurantId
WHERE c.Id = 5
AND (1 = 1)
AND (s.IsDeleted = 0 OR s.IsDeleted IS NULL)
ORDER BY DNIS ASC;
Here's a simplified example using table variables.
Instead of using a subquery for a field, it uses a CROSS APPLY.
And CONCAT in combination with STUFF is used to glue the strings together.
declare #Foo table (fooID int identity(1,1) primary key, Script varchar(30));
declare #Bar table (barID int identity(1,1) primary key, fooID int, Line varchar(30));
insert into #Foo (Script) values
('Test1'),('Test2'),(NULL);
insert into #Bar (fooID, Line) values
(1,'X'),(1,'Y'),(2,NULL),(3,'X'),(3,'Y');
select
f.fooID,
f.Script,
x.Lines,
CONCAT(Script+'; ', STUFF(x.Lines,1,2,'')) as NewScript
from #Foo f
cross apply (
select '; '+b.Line
from #Bar b
where b.fooID = f.fooID
FOR XML PATH('')
) x(Lines)
Result:
fooID Script Lines NewScript
----- ------- ------- -----------
1 Test1 ; X; Y Test1; X; Y
2 Test2 NULL Test2;
3 NULL ; X; Y X; Y

How to use table valued function as part of select statement in sql server

I have a select statement which returns a table. I want to select a table valued function as a part of that select statement, which also returns a table.. How to do that.
This is my select code which returns a table
SELECT
dbo.TC_User.JobTitle, dbo.TC_User.UserID,
dbo.TC_User.LocalID, dbo.TC_User.NokiaID, dbo.TC_User.NameCN,
dbo.TC_User.职阶 as EmployeeGroup,
dbo.TC_User2.LockDateID, dbo.TC_User.StartDate,
dbo.TC_User.EndDate, dbo.TC_User.StartDateN,
dbo.TC_User.聘用前工号, dbo.TC_User.NoCheckFlag,
dbo.GetGroupPath2('465') AS Path
FROM
dbo.TC_User
INNER JOIN
dbo.TC_User2 ON dbo.TC_User.UserID = dbo.TC_User2.UserID
WHERE
(dbo.TC_User.UserID IN (SELECT UserID
FROM dbo.TC_User
WHERE (GroupID IN (SELECT GroupID
FROM dbo.VSGetSubGroupTab(10, 1) AS VSGetSubGroupTab))))
This is what I want
SELECT
dbo.TC_User.JobTitle, dbo.TC_User.UserID,
dbo.GetGroupPath2Clone('465') as grouppath,
dbo.TC_User.LocalID, dbo.TC_User.NokiaID, dbo.TC_User.NameCN,
dbo.TC_User.职阶 as EmployeeGroup,
dbo.TC_User2.LockDateID, dbo.TC_User.StartDate,
dbo.TC_User.EndDate, dbo.TC_User.StartDateN,
dbo.TC_User.聘用前工号, dbo.TC_User.NoCheckFlag,
dbo.GetGroupPath2('465') AS Path
FROM
dbo.TC_User
INNER JOIN
dbo.TC_User2 ON dbo.TC_User.UserID = dbo.TC_User2.UserID
WHERE
(dbo.TC_User.UserID IN (SELECT UserID
FROM dbo.TC_User
WHERE (GroupID IN (SELECT GroupID
FROM dbo.VSGetSubGroupTab(10, 1) AS VSGetSubGroupTab))))
dbo.GetGroupPath2Clone('465') as grouppath, is the table valued function which I want to call. How to do that?
the dbo.GetGroupPath2 looks like a scalar function. However, if it is a table function then you need to place it in the join list.
SELECT
dbo.TC_User.JobTitle, dbo.TC_User.UserID,
dbo.TC_User.LocalID, dbo.TC_User.NokiaID, dbo.TC_User.NameCN,
dbo.TC_User.职阶 as EmployeeGroup,
dbo.TC_User2.LockDateID, dbo.TC_User.StartDate,
dbo.TC_User.EndDate, dbo.TC_User.StartDateN,
dbo.TC_User.聘用前工号, dbo.TC_User.NoCheckFlag,
tf.yourfield1, tf.yourfield2, ...etc.
FROM
dbo.TC_User
join dbo.GetGroupPath2('465') AS tf on tf.yourkey=somekeys
INNER JOIN
dbo.TC_User2 ON dbo.TC_User.UserID = dbo.TC_User2.UserID
WHERE
(dbo.TC_User.UserID IN (SELECT UserID
FROM dbo.TC_User
WHERE (GroupID IN (SELECT GroupID
FROM dbo.VSGetSubGroupTab(10, 1) AS VSGetSubGroupTab))))
to create the table function:
CREATE FUNCTION [dbo].GetGroupPath2
(
#Parm varchar(10)
)
Returns Table
As
Return (
Select yourfield1, yourfield2
From yourTable
where [yourKey] = #Parm
)

Conditional JOIN Statement SQL Server

Is it possible to do the following:
IF [a] = 1234 THEN JOIN ON TableA
ELSE JOIN ON TableB
If so, what is the correct syntax?
I think what you are asking for will work by joining the Initial table to both Option_A and Option_B using LEFT JOIN, which will produce something like this:
Initial LEFT JOIN Option_A LEFT JOIN NULL
OR
Initial LEFT JOIN NULL LEFT JOIN Option_B
Example code:
SELECT i.*, COALESCE(a.id, b.id) as Option_Id, COALESCE(a.name, b.name) as Option_Name
FROM Initial_Table i
LEFT JOIN Option_A_Table a ON a.initial_id = i.id AND i.special_value = 1234
LEFT JOIN Option_B_Table b ON b.initial_id = i.id AND i.special_value <> 1234
Once you have done this, you 'ignore' the set of NULLS. The additional trick here is in the SELECT line, where you need to decide what to do with the NULL fields. If the Option_A and Option_B tables are similar, then you can use the COALESCE function to return the first NON NULL value (as per the example).
The other option is that you will simply have to list the Option_A fields and the Option_B fields, and let whatever is using the ResultSet to handle determining which fields to use.
This is just to add the point that query can be constructed dynamically based on conditions.
An example is given below.
DECLARE #a INT = 1235
DECLARE #sql VARCHAR(MAX) = 'SELECT * FROM [sourceTable] S JOIN ' + IIF(#a = 1234,'[TableA] A ON A.col = S.col','[TableB] B ON B.col = S.col')
EXEC(#sql)
--Query will be
/*
SELECT * FROM [sourceTable] S JOIN [TableB] B ON B.col = S.col
*/
You can solve this with union
select a, b
from tablea
join tableb on tablea.a = tableb.a
where b = 1234
union
select a, b
from tablea
join tablec on tablec.a = tableb.a
where b <> 1234
I disagree with the solution suggesting 2 left joins. I think a table-valued function is more appropriate so you don't have all the coalescing and additional joins for each condition you would have.
CREATE FUNCTION f_GetData (
#Logic VARCHAR(50)
) RETURNS #Results TABLE (
Content VARCHAR(100)
) AS
BEGIN
IF #Logic = '1234'
INSERT #Results
SELECT Content
FROM Table_1
ELSE
INSERT #Results
SELECT Content
FROM Table_2
RETURN
END
GO
SELECT *
FROM InputTable
CROSS APPLY f_GetData(InputTable.Logic) T
I think it will be better to think about your query in a different way and treat them more like sets.
I do believe if you make two separate queries then join them using UNION, It will be much better in performance and more readable.

Resources