TSQL Conditional Where Based on Another Table - sql-server

I have a stored procedure that contains a simple query like below. The query pulls down all the bids that have been submitted on a job.
I need to add in an additional piece to the WHERE clause however.
There is a second table called Admins and a field within the table called employeeID. If the employees ID that's being passed to the stored procedure exists in Admins it will pull down all bids. If they do not exist in Admins it will only pull down bids where b.actor = #employee.
In short, if #employee exists in Admin, leave the where clause how it is (pulling down all information for the requestID. However if they are not in that table, need to add in AND b.actor = #employee
SELECT b.bidID ,
b.requestID ,
b.notes ,
b.team ,
b.devDays ,
b.complexity ,
b.estimatedCost ,
b.actor ,
b.status,
REPLACE(CONVERT (VARCHAR (20), b.timestamp, 106), ' ', '-') AS timestamp,
e.PreferredName AS bidderFirstName,
e.LastName AS bidderLastName,
e.NTID AS bidderNTID
FROM dbo.BS_ToolRequests_Bids AS b
LEFT OUTER JOIN dbo.EmployeeTable AS e
ON b.actor = e.QID
WHERE b.requestID = t.requestID
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('bids')
How can I go about doing this? The code above is a sub select in a larger query but need to add some type of condition to it.

Can you add an OR clause to your existing WHERE clause?
WHERE
AND ( EXISTS(SELECT 'f' FROM Admins WHERE employeeid = #employeeid)
OR b.actor = #employeeid
)

Related

Splitting multiple fields by delimiter

I have to write an SP that can perform Partial Updates on our databases, the changes are stored in a record of the PU table. A values fields contains all values, delimited by a fixed delimiter. A tables field refers to a Schemes table containing the column names for each table in a similar fashion in a Colums fiels.
Now for my SP I need to split the Values field and Columns field in a temp table with Column/Value pairs, this happens for each record in the PU table.
An example:
Our PU table looks something like this:
CREATE TABLE [dbo].[PU](
[Table] [nvarchar](50) NOT NULL,
[Values] [nvarchar](max) NOT NULL
)
Insert SQL for this example:
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','John Doe;26');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Jane Doe;22');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mike Johnson;20');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mary Jane;24');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Mathematics');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','English');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Geography');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus A;Schools Road 1;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus B;Schools Road 31;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus C;Schools Road 22;Educationville');
And we have a Schemes table similar to this:
CREATE TABLE [dbo].[Schemes](
[Table] [nvarchar](50) NOT NULL,
[Columns] [nvarchar](max) NOT NULL
)
Insert SQL for this example:
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Person','[Name];[Age]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Course','[Name]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Campus','[Name];[Address];[City]');
As a result the first record of the PU table should result in a temp table like:
The 5th will have:
Finally, the 8th PU record should result in:
You get the idea.
I tried use the following query to create the temp tables, but alas it fails when there's more that one value in the PU record:
DECLARE #Fields TABLE
(
[Column] INT,
[Value] VARCHAR(MAX)
)
INSERT INTO #Fields
SELECT TOP 1
(SELECT Value FROM STRING_SPLIT([dbo].[Schemes].[Columns], ';')),
(SELECT Value FROM STRING_SPLIT([dbo].[PU].[Values], ';'))
FROM [dbo].[PU] INNER JOIN [dbo].[Schemes] ON [dbo].[PU].[Table] = [dbo].[Schemes].[Table]
TOP 1 correctly gets the first PU record as each PU record is removed once processed.
The error is:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
In the case of a Person record, the splits are indeed returning 2 values/colums at a time, I just want to store the values in 2 records instead of getting an error.
Any help on rewriting the above query?
Also do note that the data is just generic nonsense. Being able to have 2 fields that both have delimited values, always equal in amount (e.g. a 'person' in the PU table will always have 2 delimited values in the field), and break them up in several column/header rows is the point of the question.
UPDATE: Working implementation
Based on the (accepted) answer of Sean Lange, I was able to work out followin implementation to overcome the issue:
As I need to reuse it, the combine column/value functionality is performed by a new function, declared as such:
CREATE FUNCTION [dbo].[JoinDelimitedColumnValue]
(#splitValues VARCHAR(8000), #splitColumns VARCHAR(8000),#pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH MyValues AS
(
SELECT ColumnPosition = x.ItemNumber,
ColumnValue = x.Item
FROM dbo.DelimitedSplit8K(#splitValues, #pDelimiter) x
)
, ColumnData AS
(
SELECT ColumnPosition = x.ItemNumber,
ColumnName = x.Item
FROM dbo.DelimitedSplit8K(#splitColumns, #pDelimiter) x
)
SELECT cd.ColumnName,
v.ColumnValue
FROM MyValues v
JOIN ColumnData cd ON cd.ColumnPosition = v.ColumnPosition
;
In case of the above sample data, I'd call this function with the following SQL:
DECLARE #FieldValues VARCHAR(8000), #FieldColumns VARCHAR(8000)
SELECT TOP 1 #FieldValues=[dbo].[PU].[Values], #FieldColumns=[dbo].[Schemes].[Columns] FROM [dbo].[PU] INNER JOIN [dbo].[Schemes] ON [dbo].[PU].[Table] = [dbo].[Schemes].[Table]
INSERT INTO #Fields
SELECT [Column] = x.[ColumnName],[Value] = x.[ColumnValue] FROM [dbo].[JoinDelimitedColumnValue](#FieldValues, #FieldColumns, #Delimiter) x
This data structure makes this way more complicated than it should be. You can leverage the splitter from Jeff Moden here. http://www.sqlservercentral.com/articles/Tally+Table/72993/ The main difference of that splitter and all the others is that his returns the ordinal position of each element. Why all the other splitters don't do this is beyond me. For things like this it is needed. You have two sets of delimited data and you must ensure that they are both reassembled in the correct order.
The biggest issue I see is that you don't have anything in your main table to function as an anchor for ordering the results correctly. You need something, even an identity to ensure the output rows stay "together". To accomplish I just added an identity to the PU table.
alter table PU add RowOrder int identity not null
Now that we have an anchor this is still a little cumbersome for what should be a simple query but it is achievable.
Something like this will now work.
with MyValues as
(
select p.[Table]
, ColumnPosition = x.ItemNumber
, ColumnValue = x.Item
, RowOrder
from PU p
cross apply dbo.DelimitedSplit8K(p.[Values], ';') x
)
, ColumnData as
(
select ColumnName = replace(replace(x.Item, ']', ''), '[', '')
, ColumnPosition = x.ItemNumber
, s.[Table]
from Schemes s
cross apply dbo.DelimitedSplit8K(s.Columns, ';') x
)
select cd.[Table]
, v.ColumnValue
, cd.ColumnName
from MyValues v
join ColumnData cd on cd.[Table] = v.[Table]
and cd.ColumnPosition = v.ColumnPosition
order by v.RowOrder
, v.ColumnPosition
I recommended not storing values like this in the first place. I recommend having a key value in the tables and preferably not using Table and Columns as a composite key. I recommend to avoid using reserved words. I also don't know what version of SQL you are using. I am going to assume you are using a fairly recent version of Microsoft SQL Server that will support my provided stored procedure.
Here is an overview of the solution:
1) You need to convert both the PU and the Schema table into a table where you will have each "column" value in the list of columns isolated in their own row. If you can store the data in this format rather than the provided format, you will be a little better off.
What I mean is
Table|Columns
Person|Jane Doe;22
needs converted to
Table|Column|OrderInList
Person|Jane Doe|1
Person|22|2
There are multiple ways to do this, but I prefer an xml trick that I picked up. You can find multiple split string examples online so I will not focus on that. Use whatever gives you the best performance. Unfortunately, You might not be able to get away from this table-valued function.
Update:
Thanks to Shnugo's performance enhancement comment, I have updated my xml splitter to give you the row number which reduces some of my code. I do the exact same thing to the Schema list.
2) Since the new Schema table and the new PU table now have the order each column appears, the PU table and the schema table can be joined on the "Table" and the OrderInList
CREATE FUNCTION [dbo].[fnSplitStrings_XML]
(
#List NVARCHAR(MAX),
#Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN
(
SELECT y.i.value('(./text())[1]', 'nvarchar(4000)') AS Item,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as RowNumber
FROM
(
SELECT CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.') AS x
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
CREATE Procedure uspGetColumnValues
as
Begin
--Split each value in PU
select p.[Table],p.[Values],a.[Item],CHARINDEX(a.Item,p.[Values]) as LocationInStringForSorting,a.RowNumber
into #PuWithOrder
from PU p
cross apply [fnSplitStrings_XML](p.[Values],';') a --use whatever string split function is working best for you (performance wise)
--Split each value in Schema
select s.[Table],s.[Columns],a.[Item],CHARINDEX(a.Item,s.[Columns]) as LocationInStringForSorting,a.RowNumber
into #SchemaWithOrder
from Schemes s
cross apply [fnSplitStrings_XML](s.[Columns],';') a --use whatever string split function is working best for you (performance wise)
DECLARE #Fields TABLE --If this is an ETL process, maybe make this a permanent table with an auto incrementing Id and reference this table in all steps after this.
(
[Table] NVARCHAR(50),
[Columns] NVARCHAR(MAX),
[Column] VARCHAR(MAX),
[Value] VARCHAR(MAX),
OrderInList int
)
INSERT INTO #Fields([Table],[Columns],[Column],[Value],OrderInList)
Select pu.[Table],pu.[Values] as [Columns],s.Item as [Column],pu.Item as [Value],pu.RowNumber
from #PuWithOrder pu
join #SchemaWithOrder s on pu.[Table]=s.[Table] and pu.RowNumber=s.RowNumber
Select [Table],[Columns],[Column],[Value],OrderInList
from #Fields
order by [Table],[Columns],OrderInList
END
GO
EXEC uspGetColumnValues
GO
Update:
Since your working implementation is a table-valued function, I have another recommendation. The problem I see is that your using a table valued function which ultimately handles one record at a time. You are going to have better performance with set based operations and batching as needed. With a tabled valued function, you are likely going to be looping through each row. If this is some sort of ETL process, your team will be better off if you have a stored procedure that processes the rows in bulk. It might make sense to stage the results into a better table that your team can work with down stream rather than have them use a potentially slow table-valued function.

SQL Server Selecting multiple LIKE values in Report Builder

I have a report where I'm trying to allow the user to select multiple predetermined LIKE values from a drop down list for their results in report builder. Is there a way I can do this? I have tried to use LIKE IN() but those two keywords don't seem to work together. Here is the code that I have. The code I have only works if I select one option.
DECLARE #Warehouse nvarchar(10)
DECLARE #Location nvarchar(10)
SET #Warehouse = 'Warehouse1'
SET #Location = 'IB'
SELECT Part
, Tag
, CurrentLocation AS 'Location'
, TotalQty
, DateTimeCreated
, datediff(hour, DateTimeCreated, getdate()) AS 'Hours in Location'
, User AS 'Last User'
FROM table1
WHERE datediff(hour, DateTimeCreated, getdate())>=1
AND Warehouse IN(#Warehouse)
AND(CurrentLocation LIKE '%' + #Location + '%')
ORDER BY 'Hours in Location' DESC, CurrentLocation
This would be best handled with a stored procedure. Here is a high-level description of how it would work. Each of the techniques can be learned at a low-level with some astute googling:
For your report dataset, call the stored procedure, pass your multi-valued parameter to a varchar parameter in the stored proc. For the rest of this answer, we'll call that parameter #MVList
In the stored proc, #MVList will be received as a comma-delimited string of all the options the user chose in the parameter list.
Write your SELECT query from Table1, JOINing to a Table-Valued Function that splits the #MVList (google SQL Split Function to get pre-written code), and produces a table with one row for each value that the user chose.
For the JOIN condition, instead of equals, do a LIKE:
INNER JOIN MySplitFunction(#MVList, ',') AS SplitFunction
ON Table1.CurrentLocation LIKE '%'+SplitFunction.value+'%'
The result of the query will be the IN/LIKE result you are looking for.
Thank you for your responses. This is what I ended up doing that fixed my problem.
SELECT Part
, Tag
, CurrentLocation AS 'Location'
, TotalQty
, DateTimeCreated
, datediff(hour, DateTimeCreated, getdate()) AS 'Hours in Location'
, User AS 'Last User'
FROM table 1
WHERE datediff(hour, DateTimeCreated, getdate())>=1
AND Warehouse in (#Warehouse)
AND LEFT(CurrentLocation,2) IN(#Location)
ORDER BY 'Hours in Location' DESC, CurrentLocation

Generate hierarchy of employees

I have two tables: EmployeeMaster and EmployeeDetails. The schema of both are as below:
Sample data in both tables is shown:
I want to generate the hierarchy using EmployeeDetails table primarily. This table contains a column named: Manager. The EmployeeId of the Manager needs to be picked from the table EmployeeMaster table.
This is how the hierarchy needs to be formed. An EmployeeId is passed as a parameter to a stored procedure. The two supervisors of this Employee needs to be picked and 10 employees below this employee in seniority needs to be picked.
For instance, I pass the EmployeeId of Josh.Berkus to the stored procedure. The stored procedure query should return hierarchy as below:
I want the final output in this format:
Employee_Id .... Manager_Id
----------- .... ------------
Please note that Manager_Id is the EmployeeId of Manager.
I tried using a CTE with union all query, but not able to get it correctly.
Actually you will need to work out the recursivity since on manager can have a manager...
take a look at:
http://msdn.microsoft.com/en-us/library/ms190766(v=sql.105).aspx
http://msdn.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
The thing is that your going to need 2 queries... one to go "up" the hierarchy and one to go down... and then union the results...
why don't you merge the two tables, since one person cant have 2 managers right?!? Specially because a manager is also a employee... this will simplify everything...
You can use a CROSS JOIN to create a link between all your records and then you can put the condition to select only those columns that have a manager-employee relationship between them.
The code should be something like this:
SELECT
ed.employeeid 'Employee ID',
em.employeeid 'Manager ID',
FROM EMPLOYEEMASTER em CROSS JOIN EMPLOYEEDETAILS ed
WHERE ed.manager = em.username
You’ll need to implement some recursion here in order to get full hierarchy.
Here is a quick and dirty example of how you can implement this to get manager hierarchy. You would need something similar for lower level hierarchy too
create function dbo.GetManagerHierarchy
(
#EmpID int
)
returns varchar(100)
as
begin
declare #result varchar(100)
declare #managerId int
SET #managerId = (select top 1 Manager from EmployeeDetails where EMployeeId)
if #managerId is not null then
SET #result = dbo.GetManagerHierarchy(#managerID) + '-' + CONVERT(varchar(100), #managerId) +
else
SET #result = ''
return #result
end

SQL query taking a long time to execute

USE Pooja
GO
----Create TestTable
CREATE TABLE TestTable(RtJobCode VARCHAR(20), RtProfCode smallint,RtTestCode smallint,ProfCode smallint,TestCode smallint)
----INSERT INTO TestTable using SELECT
INSERT INTO TestTable (RtJobCode, RtProfCode,RtTestCode,ProfCode,TestCode)
SELECT RtJobCode,RtTestCode,TestCode,RtProfCode,ProfCode
FROM dbo.ResultTest,dbo.Test,dbo.Profiles
WHERE RtTestCode=ANY(Select TestCode from dbo.Test)
----Verify that Data in TestTable
SELECT *
FROM TestTable
GO
The above code tries to take out entries from a table called resutltest and profiles and test,
The problem was during creation of a cube i was encountering some data which was not consistent in all the tables,
So i tried a join on the tables but as the tables contained a huge number of columns it was'nt feasible so tried making this code which just keeps on executing without stopping
and not displaying any data
Resulttest's Rttestcode is foreign key from testcode
Your query is very slow because it is making a cartesian product between ResultTest, Test and Profiles. you need to provide "join" conditions to link the tables together.
SELECT RtJobCode
, RtTestCode
, TestCode
, RtProfCode
, ProfCode
FROM dbo.ResultTest r
JOIN dbo.Test t
ON r.RtTestCode = t.TestCode
JOIN dbo.Profiles p
ON r.RtProfCode = p.ProfCode
I speculate that this is the query you are looking for. Note the conditions that link ResultTest and Test together and the condition that links ResultTest and Profiles together.
USE Pooja
GO
----Create TestTable
CREATE TABLE TestTable(RtJobCode VARCHAR(20), RtProfCode smallint,RtTestCode smallint,RtCenCode smallint,LabNo int,ProfCode smallint,ProfRate money,ProfName varchar(100),TestCode smallint,TestRate money,TestName varchar(100),TestCategory varchar(50),Cost money)
----INSERT INTO TestTable using SELECT
INSERT INTO TestTable (RtJobCode, RtProfCode,RtTestCode,RtCenCode,LabNo,ProfCode,ProfRate,ProfName,TestCode,TestRate,TestName,TestCategory,Cost)
SELECT RtJobCode
, RtProfCode
, RtTestCode
, RtCenCode
, LabNo
, ProfCode
, ProfRate
, ProfName
, TestCode
, TestRate
, TestName
, TestCategory
, Cost
FROM dbo.ResultTest
JOIN dbo.Test
ON ResultTest.RtTestCode = Test.TestCode
JOIN dbo.Profiles
ON ResultTest.RtProfCode = Profiles.ProfCode

Is there a way to optimize the query given below

I have the following Query and i need the query to fetch data from SomeTable based on the filter criteria present in the Someothertable. If there is nothing present in SomeOtherTable Query should return me all the data present in SomeTable
SQL SERVER 2005
SomeOtherTable does not have any indexes or any constraint all fields are char(50)
The Following Query work fine for my requirements but it causes performance problems when i have lots of parameters.
Due to some requirement of Client, We have to keep all the Where clause data in SomeOtherTable. depending on subid data will be joined with one of the columns in SomeTable.
For example the Query can can be
SELECT
*
FROM
SomeTable
WHERE
1=1
AND
(
SomeTable.ID in (SELECT DISTINCT ID FROM SomeOtherTable WHERE Name = 'ABC' and subid = 'EF')
OR
0=(SELECT Count(1) FROM SomeOtherTable WHERE spName = 'ABC' and subid = 'EF')
)
AND
(
SomeTable.date =(SELECT date FROM SomeOtherTable WHERE Name = 'ABC' and subid = 'Date')
OR
0=(SELECT Count(1) FROM SomeOtherTable WHERE spName = 'ABC' and subid = 'Date')
)
EDIT----------------------------------------------
I think i might have to explain my problem in detail:
We have developed an ASP.net application that is used to invoke parametrize crystal reports, parameters to the crystal reports are not passed using the default crystal reports method.
In ASP.net application we have created wizards which are used to pass the parameters to the Reports, These parameters are not directly consumed by the crystal report but are consumed by the Query embedded inside the crystal report or the Stored procedure used in the Crystal report.
This is achieved using a table (SomeOtherTable) which holds parameter data as long as report is running after which the data is deleted, as such we can assume that SomeOtherTable has max 2 to 3 rows at any given point of time.
So if we look at the above query initial part of the Query can be assumed as the Report Query and the where clause is used to get the user input from the SomeOtherTable table.
So i don't think it will be useful to create indexes etc (May be i am wrong).
SomeOtherTable does not have any
indexes or any constraint all fields
are char(50)
Well, there's your problem. There's nothing you can do to a query like this which will improve its performance if you create it like this.
You need a proper primary or other candidate key designated on all of your tables. That is to say, you need at least ONE unique index on the table. You can do this by designating one or more fields as the PK, or you can add a UNIQUE constraint or index.
You need to define your fields properly. Does the field store integers? Well then, an INT field may just be a better bet than a CHAR(50).
You can't "optimize" a query that is based on an unsound schema.
Try:
SELECT
*
FROM
SomeTable
LEFT JOIN SomeOtherTable ON SomeTable.ID=SomeOtherTable.ID AND Name = 'ABC'
WHERE
1=1
AND
(
SomeOtherTable.ID IS NOT NULL
OR
0=(SELECT Count(1) FROM SomeOtherTable WHERE spName = 'ABC')
)
also put 'with (nolock)' after each table name to improve performance
The following might speed you up
SELECT *
FROM SomeTable
WHERE
SomeTable.ID in
(SELECT DISTINCT ID FROM SomeOtherTable Where Name = 'ABC')
UNION
SELECT *
FROM SomeTable
Where
NOT EXISTS (Select spName From SomeOtherTable Where spName = 'ABC')
The UNION will effectivly split this into two simpler queries which can be optiomised separately (depends very much on DBMS, table size etc whether this will actually improve performance -- but its always worth a try).
The "EXISTS" key word is more efficient than the "SELECT COUNT(1)" as it will return true as soon as the first row is encountered.
Or check if the value exists in db first
And you can remove the distinct keyword in your query, it is useless here.
if EXISTS (Select spName From SomeOtherTable Where spName = 'ABC')
begin
SELECT *
FROM SomeTable
WHERE
SomeTable.ID in
(SELECT ID FROM SomeOtherTable Where Name = 'ABC')
end
else
begin
SELECT *
FROM SomeTable
end
Aloha
Try
select t.* from SomeTable t
left outer join SomeOtherTable o
on t.id = o.id
where (not exists (select id from SomeOtherTable where spname = 'adbc')
OR spname = 'adbc')
-Edoode
change all your select statements in the where part to inner jons.
the OR conditions should be union all-ed.
also make sure your indexing is ok.
sometimes it pays to have an intermediate table for temp results to which you can join to.
It seems to me that there is no need for the "1=1 AND" in your query. 1=1 will always evaluate to be true, leaving the software to evaluate the next part... why not just skip the 1=1 and evaluate the juicy part?
I am going to stick to my original Query.

Resources