I have been struck while writing a query to pick a string . What I'm posting the query is sample data
Declare #tbl table (tblname varchar(20),Query VARCHAR(MAX))
Insert into #tbl (tblname,Query) values ('Employee','select EmpId,
(Select top 1 Dept_ID from Stg.Dept
where Deptid = Deptid) Dept_ID,
(Select top 1 Dept_ID from Stg.Sub_dept
where Deptid = D.Deptid) SubDept_ID
from stg.Employee E
left join stg.Dept D
ON D.EMPID = E.EmpID
WHERE EMPID = (Select EMPID from stg.dept where Deptid = Deptid)')
Query :
select tblname,SUBSTRING(LTRIM(SUBSTRING(Query, CHARINDEX('FROM', Query)+4, LEN(Query))),
CHARINDEX('.', LTRIM(SUBSTRING(Query, CHARINDEX('FROM', Query)+4, LEN(Query))))+1,
CHARINDEX(' ',
SUBSTRING(LTRIM(SUBSTRING(Query, CHARINDEX('FROM', Query)+4, LEN(Query))),
CHARINDEX('.', LTRIM(SUBSTRING(Query, CHARINDEX('FROM', Query)+4, LEN(Query))))+1, LEN(Query)))-1) from
#tbl
Result :
tblname Req_tbl
Employee Dept
Actually this query is picking the stg.Dept table name from sub query in that query column. What I exactly want is to pick up stg.Employee table which is the main table.
Output :
tblname Req_tbl
Employee Employee
Can you please suggest on this
This is not 100% clear but what you're trying to do appears to parse a query to determine is the object defined by #tbl.tblname. exists in that query with a FROM clause in it. For example - for "Employee" you are looking for "stg.employee" (excluding the schema).
If I understand this correctly, you can do this easily with a splitter such as DelimitedSplit8K and do something like this:
Declare #tbl table (tblname varchar(20),Query VARCHAR(MAX))
Insert into #tbl (tblname,Query) values ('Employee','select EmpId,
(Select top 1 Dept_ID from Stg.Dept
where Deptid = Deptid) Dept_ID,
(Select top 1 Dept_ID from Stg.Sub_dept
where Deptid = D.Deptid) SubDept_ID
from stg.Employee E
left join stg.Dept D
ON D.EMPID = E.EmpID
WHERE EMPID = (Select EMPID from stg.dept where Deptid = Deptid)')
SELECT TOP (1) t.tblname, req_table = t.tblname
FROM #tbl AS t
CROSS APPLY dbo.delimitedSplit8K(t.Query,CHAR(10)) AS s
WHERE PATINDEX('%[^a-zA-Z]from %'+t.tblname+'%', s.item) > 0;
The problem here, however, is that, depending on how uniform your T-SQL code is - this can get complicated and hairy.
Related
I need to select one column for each matching row in another table. Sounds simple, but there's a twist.
I have one table that has Company IDs, and defines Companies.
I have another table that defines Departments in those companies.
I have a third table that contains the various Department StatusCodes.
The code that I have below works just fine, and is exactly what I want it to do. But I have to hardcode into the query which departments to look for. I'd like it to sub-select based on department codes it finds in the Departments table, and not require me to hard code each possible department that might exist at that company.
I need to select the matching department status from DepartmentStatus based upon what Departments exist for a company as defined in DepartmentsStatus table.
I suspect it's a pivot table, but they are a bit above my level.
(Company Table)
Company_ID Company_Name
-------------------------
1 Home Office
2 Stanton Office
3 NYC Office
(Departments Table)
CompanyID Department_Code
----------------------------
1 Sales
1 Inventory
1 Retail
1 Maint
2 OtherDept
2 ThatDept
2 BobsDept
(DepartmentStatus Table)
Company_ID Department StatusCode
-----------------------------------------
1 Sales InReview
1 Inventory InReview
1 Retail Ready
1 Maint Done
2 OtherDept InReview
2 ThatDept Research
2 BobsDept InReview
Note: I use "TOP 1", even though there is a unique index on Company_ID+Department, so there will never be more than one matching row.
So, for Company_ID=1:
select Company_ID,
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Sales') as SalesStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Inventory') as InvStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Retail') as RetailStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.Company_ID = cm.Company_ID and ds.Department='Main') as MaintStatus
from Company cm
Where cm.CompanyID=1
Results:
Company_ID SalesStatus InvStatus RetailStatus MaintStatus
--------------- --------------- ---------- ------------- ------------
1 InReview InReview Ready Done
Or, for CompanyID=2:
select Company_ID,
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='OtherDept') as OtherDeptStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='ThatDept') as ThatDeptStatus
(select top 1 StatusCode from DepartmentStatus ds where ds.CompanyID = cm.Company_ID and ds.Department='BobsDept') as BobsDeptStatus
from Company cm
Where cm.CompanyID=2
Results:
Company_ID OtherDeptStatus ThatDeptStatus BobsDeptStatus
---------- ---------------- -------------- --------------
2 InReview Research InReview
Thus, For Company 1, I need to go get the status for Departments Sales, Inventory, Retail, and Maint.
But for Company 2, I need to get the status for Departments OtherDept, ThatDept, and BobsDept.
The steps that I think describe what I am trying to do is:
Get list of Departments for Company 1 from Departments Table.
For each of these Departments in Step 1, query DepartmentStatus table for Department equals that department.
For Company 1, query get StatusCode for Departments "Sales, Inventory, Retail, and Maint"
For Company 2, query get StatusCode for Departments "OtherDept, thatDept, and BobsDept."
etc etc
The problem is, I do not know ahead of time (at query time) which departments exist for each Company, so I need to sub-select based upon what departments actually exist for each company.
There are a few other S.O. answers already that are close what I'm asking for, suggesting the use of a JOIN to accomplish this, however, they all assume that you know the values you want to query ahead of time when writing the join statement.
I'm looking for a solution to this problem, not just a correction on my current attempt. If you have a better idea entirely on how to accomplish this, I'd love to see it.
You seem to be looking for conditional aggregation. Using this technique, your query can be simplified as follows, to avoid the need for multiple inline subqueries:
select Company_ID,
max(case when ds.Department='Sales' then ds.StatusCode end) as SalesStatus,
max(case when ds.Department='Inventory' then ds.StatusCode end) as InvStatus,
max(case when ds.Department='Retail' then ds.StatusCode end) as RetailStatus,
max(case when ds.Department='Main' then ds.StatusCode end) as MaintStatus
from
Company cm
inner join DepartmentStatus ds on ds.Company_ID = cm.Company_ID
Where cm.CompanyID=1
group by cm.CompanyID
this would be my (untested) caveman way of solving your problem.
the only thing you need to know in advance is how many different departments you have in the company with the most departments.
DROP TABLE IF EXISTS Iddepartmentstatus;
DECLARE #Result TABLE(
Companyid INT,
Department1 NVARCHAR(MAX),
Department2 NVARCHAR(MAX),
Department3 NVARCHAR(MAX),
Department4 NVARCHAR(MAX),
Department5 NVARCHAR(MAX));
CREATE TABLE Iddepartmentstatus(
Num INT IDENTITY(1, 1),
Department NVARCHAR(MAX),
Statuscode NVARCHAR(MAX));
DECLARE #IDCompany TABLE(
Num INT IDENTITY(1, 1),
Companyid INT,
Companyname NVARCHAR(MAX));
INSERT INTO #IDCompany (Companyid,
Companyname)
SELECT Company_Id,
Company_Name
FROM Company;
DECLARE #Departmentcount INT = 0,
#Departmentstatuscount INT = 0,
#Companycount INT = 0;
WHILE #Companycount <=
(
SELECT MAX(Num)
FROM #IDCompany)
BEGIN
INSERT INTO Iddepartmentstatus (Department,
Statuscode)
SELECT Department,
Statuscode
FROM Departmentstatus
WHERE Company_Id = #Companycount;
INSERT INTO #Result (Companyid)
SELECT #Companycount AS T;
INSERT INTO #Result (Department1)
SELECT IIF(1 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 1;
INSERT INTO #Result (Department2)
SELECT IIF(2 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 2;
INSERT INTO #Result (Department3)
SELECT IIF(3 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 3;
INSERT INTO #Result (Department4)
SELECT IIF(4 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 4;
INSERT INTO #Result (Department5)
SELECT IIF(5 <=
(
SELECT MAX(Num)
FROM #IDCompany), Department + ': ' + Departmentstatus, NULL)
FROM Iddepartmentstatus
WHERE Num = 5;
TRUNCATE TABLE Iddepartmentstatus;
SET #Companycount = #Companycount + 1;
END;
DROP TABLE IF EXISTS Iddepartmentstatus;
So lets say I have a Table "Stuff".
It has 3 columns.
So Job Code is supposed to be the same for both manager and the employee under that manager(like it is where jobcode= 000 ). That is the normal scenario.
However in some cases, The Manager will have "ABC" as jobcode.
In those cases, I need to replace "ABC" with the jobcode value of the most recent employee under that manager.
For example, for Manager A1, I need to replace his jobcode of ABC with 234,considering B1 is the most recent employee under him.
For manager A, his jobcode of ABC will be replaced with 121 since B is the only employee under him.
I wrote this query but it doesn't seem to work.
Update X
Set X.JobCode=Y.JobCode
FROM STUFF X
INNER JOIN STUFF Y
ON X.MGRCODE=Y.MGRCODE
AND X.JOBCODE = 'ABC"
AND Y.JOBCODE = ( SELECT TOP 1 JOBCODE FROM STUFF WHERE EMPCODE<>Y.MGRCODE AND
Y.MGRCODE IN (SELECT MGRCODE FROM STUFF WHERE EMPCODE=MGRCODE AND JOBCODE='ABC')
;WITH cteJobCodeRowNum AS (
SELECT
ManagerCode
,JobCode
,ROW_NUMBER() OVER (PARTITION BY ManagerCode ORDER BY UpdateDate DESC) as RowNumber
FROM
#Table
)
UPDATE t
SET JobCode = r.JobCode
FROM
#Table t
INNER JOIN cteJobCodeRowNum r
ON t.EmpCode = r.ManagerCode
AND r.RowNumber = 1
AND t.JobCode <> r.JobCode
WHERE
t.JobCode = 'ABC'
Use a partitioned window function to generate a Row Number to choose the Job Code you want. Partition by ManagerCode and order by UpdateDate descending. Then join that common table expression (or derived table if you nest it) to your table based on EmpCode = ManagerCode To Update The Managers record. You can also then constrain it only to when the Manager has a Job Code of 'ABC' and the job code returned by the row number is different so you only update a specific set of rows.
Another similar method is to create your own row number by using a related cross apply such as:
UPDATE t1
SET JobCode = NewJobCode
FROM
#Table t1
CROSS APPLY (SELECT TOP 1 JobCode as NewJobCode FROM #Table t2 WHERE t1.EmpCode = t2.ManagerCode ORDER BY t2.UpdateDate DESC) n
WHERE
t1.JobCode = 'ABC'
AND t1.JobCode <> n.NewJobCode
Here is a full working example of the window function method:
DECLARE #Table AS TABLE (JobCode CHAR(3), EmpCode VARCHAR(2), ManagerCode VARCHAR(2), UpdateDate DATETIME)
INSERT INTO #Table (JobCode, EmpCode, ManagerCode, UpdateDate)
VALUES ('ABC','A','A',GETDATE()-1),('121','B','B',GETDATE()-1)
,('ABC','A1','A1',GETDATE()-1)
,('234','B1','A1',GETDATE()+1)
,('342','C1','A1',GETDATE()-1)
,('000','A2','A2',GETDATE()-1)
,('000','B2','B2',GETDATE()-1)
SELECT *
FROM
#Table
;WITH cteJobCodeRowNum AS (
SELECT
ManagerCode
,JobCode
,ROW_NUMBER() OVER (PARTITION BY ManagerCode ORDER BY UpdateDate DESC) as RowNumber
FROM
#Table
)
UPDATE t
SET JobCode = r.JobCode
FROM
#Table t
INNER JOIN cteJobCodeRowNum r
ON t.EmpCode = r.ManagerCode
AND r.RowNumber = 1
AND t.JobCode <> r.JobCode
WHERE
t.JobCode = 'ABC'
SELECT *
FROM
#Table
I have a recursive query that I have working for the most part. Here is what I have so far:
DECLARE #table TABLE(mgrQID VARCHAR(64), QID VARCHAR(64), NTID VARCHAR(64), FullName VARCHAR(64), lvl int, dt DATETIME, countOfDirects INT)
WITH empList(mgrQID, QID, NTID, FullName, lvl, metadate)
AS
(
SELECT TOP 1 mgrQID, QID, NTID, FirstName+' '+LastName, 0, Meta_LogDate
FROM dbo.EmployeeTable_Historical
WHERE QID IN (SELECT director FROM dbo.attritionDirectors) AND Meta_LogDate <= #pit
ORDER BY Meta_LogDate DESC
UNION ALL
SELECT b.mgrQID, b.QID, b.NTID, b.FirstName+' '+b.LastName, lvl+1, b.Meta_LogDate
FROM empList a
CROSS APPLY dbo.Fetch_DirectsHistorical_by_qid(a.QID, #pit)b
)
INSERT INTO #table(mgrQID, QID, NTID, FullName, lvl, dt)
SELECT empList.mgrQID ,
empList.QID ,
empList.NTID ,
empList.FullName ,
empList.lvl ,
empList.metadate
FROM empList
ORDER BY lvl
OPTION(MAXRECURSION 10)
Now, #table has a list of QIDs in it. I need to then join my employee table and find out how many people report to each of those QID's.
So, there will need to be an UPDATE that happens to #table which provides the count of employees that report to each of those QID's.
Here is the catch.. The employee table is a historical table that can contain multiple records for the same people. Any time a piece of their information is updated a new record is created with those changes.
If I wanted to pull the most recent record for some one right now, i would use this:
SELECT TOP 1 E.*
FROM employeeTable_historical AS E
WHERE E.qid = A.[subQID]
AND CONVERT (DATE, GETDATE()) > CONVERT (DATE, E.[Meta_LogDate])
ORDER BY meta_logDate DESC
The question..
I need to be able to get the count of employees in the historical table that report directly to each QID in the #table. The historical table has a column called mgrQID. Is there a way I can get this count in the original recursive query?
I would recommend first that you look at the approach you're taking. The historical table you're dealing with will certainly need to select the greatest Meta_LogDate for any given employee, but in the structure you've set up here, you'll never select more than one record from matching attritionDirectors, thanks to the TOP 1 in your anchor query. As such, I'd recommend a lightweight function on which you base your query:
create function dbo.EmployeesAsOf(#date datetime)
returns table
as return
select mgrQID, QID, NTID, FirstName, LastName, Meta_LogDate
from dbo.EmployeeTable_Historical A
where Meta_LogDate = (select max(Meta_LogDate) from dbo.EmployeeTable_Historical B where A.QID = B.QID and Meta_LogDate <= #date)
This will allow you to get the most recent record for anyone, and as long as EmployeeTable_Historical has an index on (QID, Meta_LogDate), this view will perform well.
Having said that, looking at your recursive query, you'll likely want to tweak the recursive query somewhat:
create function empList(#thisDate datetime)
returns #emptbl table (
mgrQID varchar(10)
, QID varchar(10)
, NTID varchar(10)
, Name varchar(21)
, Meta_LogDate datetime
, DirectsThisMany int
)
as
begin
;with empList AS (
select E.mgrQID, E.QID, E.NTID, E.FirstName + ' ' + E.LastName AS Name, E.Meta_LogDate
from dbo.EmployeesAsOf(#thisDate) E
inner join dbo.attritionDirectors D on E.QID = D.QID
union all
select E.mgrQID, E.QID, E.NTID, E.FirstName + ' ' + E.LastName AS Name, E.Meta_LogDate
from dbo.EmployeesAsOf(#thisDate) E
inner join empList D on E.mgrQID = D.QID
)
insert into #emptbl
select A.mgrQID, A.QID, A.NTID, A.Name, A.Meta_LogDate, count(b.QID) AS DirectsThisMany
from empList A
left join empList B on A.QID = B.mgrQID
group by A.mgrQID, A.QID, A.NTID, A.Name, A.Meta_LogDate
return
end
In this way, you'll be able to feed in any date and get a read of the tables, including counts from history as of that date. The self-join of the CTE is what enables us to get the current count of directs, as one can't use aggregates in the CTE. This function is easy to use, and the indexing strategy should become apparent by looking at the query plan in SSMS. A simple SELECT * FROM EmpList(GETDATE()) will give the current situation.
I have 2 tables tbl_emp & tbl_EmpSal where Emp_Id is primary_key in tbl_emp and foreign key for tbl_EmpSal as shown below.
create table tbl_emp
( Emp_Id int,
Emp_Name Varchar(20)
)
insert into tbl_emp(Emp_Id,Emp_Name)
select 1,'aaa'
union
select 2,'bbb'
union
select 3,'ccc'
union
select 4,'ddd'
--select * from tbl_emp
create table tbl_EmpSal
(Emp_id int,
EmpSal int)
insert into tbl_EmpSal
select 1,2000
union
select 2,4000
union
select 3,NULL
--select * from tbl_EmpSal
Now I want to write the SQL query to show output as below:
EMP_Name EmpSal/Details
aaa 2000
bbb 4000
ccc NEW JOINEE
ddd Contractor
The output shows that for any given Emp_id in tbl_EmpSal table if EmpSal column is NULL we have to show output as 'NEW JOINEE' in EmpSal/Details column.
And when there is no Row for any given Emp_id in tbl_EmpSal table we have to show output as 'Contractor' in EmpSal/Details column.
SELECT e.Emp_Name,
[EmpSal/Details] = CASE
WHEN s.EmpSal > 0 THEN CONVERT(VARCHAR(12), s.EmpSal)
WHEN s.Emp_id IS NOT NULL THEN 'NEW JOINEE'
WHEN s.EmpSal IS NULL THEN 'Contractor'
END
FROM dbo.tbl_emp AS e
LEFT OUTER JOIN dbo.tbl_EmpSal AS s
ON e.Emp_id = s.Emp_id;
This is a rather tricky question. It involves both a left outer join and distinguishing between a NULL value that is found and a missing match:
select Emp_Name,
(case when es.Emp_Id is NULL then 'Contractor'
when es.EmpSal is NULL then 'NEW JOINEE'
else cast(EmpSal as varchar(255))
end) as EmpSal_Details
from tbl_emp e left outer join
tbl_EmpSal es
on e.Emp_id = es.Emp_Id;
I am getting the following output
Code Manager Employee
1 A Emp1
1 A Emp2
1 A Emp3
2 B Emp4
2 B Emp5
but I want result as
Code Manager Employee
1 A Emp1
Emp2
Emp3
2 B Emp4
Emp5
Code and manager columns should not repeat.It should be blank.
select case when Code = lag(Code) over(order by Code, Manager, Employee)
then null
else Code
end as Code,
case when Manager = lag(Manager) over(order by Code, Manager, Employee)
then null
else Manager
end as Manager,
Employee
from YourTable Y
order by Y.Code, Y.Manager, Y.Employee
Try on SQL Fiddle
You need something like Access or Crystal Reports to do this sort of formatting. Its not possible in plain SQL.
That is not possible by SQL. You should manually loop the data in code after receiving it from database.
Edit
After comments by Vashh and Lieven, I realized that it is possible. So if he needs for display purpose he can either use Func (suggested by Vaassh), Join with null (s. by Lieven) or may be loop and add to datagridview or table or whatever he wants to use.
For the fun of it, following is one way to do it but in the end, this is better done in the end-user application
;WITH YourTable (Code, Manager, Employee) AS(
SELECT * FROM (VALUES
(1, 'A', 'Emp1')
, (1, 'A', 'Emp2')
, (1, 'A', 'Emp3')
, (2, 'B', 'Emp4')
, (2, 'B', 'Emp5')
) a (b, c, d)
)
, q AS (
SELECT rn = ROW_NUMBER() OVER (ORDER BY Code, Manager, Employee), *
FROM YourTable
)
SELECT Code = CASE WHEN q1.Code = q2.Code THEN NULL ELSE q1.Code END
, Manager = CASE WHEN q1.Code = q2.Code THEN NULL ELSE q1.Manager END
, q1.Employee
FROM q q1
LEFT OUTER JOIN q q2 ON q1.rn = q2.rn + 1
I know you're asking for an answer in Oracle, but maybe this SQL Server example will help you (if you really, really need to do it like this and not in a reporting environment):
DECLARE #TBL TABLE(
Code INT,
Manager CHAR(1),
Employee VARCHAR(4))
INSERT #TBL VALUES (1,'A','Emp1')
INSERT #TBL VALUES (1,'A','Emp2')
INSERT #TBL VALUES (1,'A','Emp3')
INSERT #TBL VALUES (2,'B','Emp4')
INSERT #TBL VALUES (2,'B','Emp5')
;WITH cte
AS (SELECT Code
,Manager
,Employee
,ROW_NUMBER() OVER(ORDER BY Code) rownum
FROM #TBL)
SELECT CASE curr.Code
WHEN prev.Code THEN ''
ELSE CAST(curr.Code AS VARCHAR(20))
END AS _Code
,CASE curr.Manager
WHEN prev.Manager THEN ''
ELSE curr.Manager
END AS _Manager
,curr.Employee
FROM cte curr
LEFT JOIN cte prev
ON curr.rownum = prev.rownum + 1
If you're just using SQL Server 2005/2008, you can achieve this with the following.
declare #table table (
Code int,
Manager Varchar(1),
Employee varchar(10)
)
insert into #table values
(1,'A','Emp1'),
(1,'A','Emp2'),
(1,'A','Emp3'),
(2,'A','Emp4'),
(2,'A','Emp5')
select * from #table
select
case when number=1 then Code else null end as Code,
case when number=1 then Manager else null end as Manager,
employee
from (
select *,
row_number() over (partition by code, manager order by code,manager) as number
from #table
) x
Which will give you:
(5 row(s) affected)
Code Manager Employee
----------- ------- ----------
1 A Emp1
1 A Emp2
1 A Emp3
2 A Emp4
2 A Emp5
(5 row(s) affected)
Code Manager employee
----------- ------- ----------
1 A Emp1
NULL NULL Emp2
NULL NULL Emp3
2 A Emp4
NULL NULL Emp5
(5 row(s) affected)
Done:)
You can change NULL values to '' if you want to.
WITH
CTE1 AS (
SELECT DISTINCT [Code], [Manager],
( SELECT TOP 1 Employee
FROM [dbo].[table] t2
WHERE t1.Code = t2.Code AND t1.Manager = t2.Manager
ORDER BY Employee) AS [Employee]
FROM [dbo].[table] t1)
,
CTE2 AS (
SELECT * FROM [dbo].[table]
EXCEPT
SELECT * FROM CTE1)
SELECT * FROM CTE1
UNION
SELECT NULL as Code, NULL as Manager, Employee
FROM CTE2
ORDER BY Employee
My SQL*Plus is a little rusty but if that's the tool that you're using then it should be fairly simple but using the BREAK command.
As mentioned in one of the comments about, this is best left to your reporting tool rather than doing in the actual SQL because an individual row without all the values doesn't make any sense outside the context of the result set.
BREAK on code on manager
SELECT code, manager, employee
FROM yourTable y
order by code, manager;
Please not that my SQL*Plus is a little rusty so this might not work exactly but details of the BREAK command can be found in the Oracle documentation.