Select count with Except clause takes too long - sql-server

I'm executing two queries. One that return the number of records and one that fetch the results. But the query where i only want the total of records , it takes the same time than the query that fetch the results. Why ? Here's my SQL. i'm using SQL 2014 :
(SELECT count(*) as id
FROM (
SELECT DISTINCT title, version
FROM book AS b
WHERE b.title IS NOT NULL AND NOT EXISTS (SELECT * FROM user AS u WHERE u.column1 = b.column1)
group by title, version
EXCEPT
SELECT DISTINCT title, version
FROM book AS b
WHERE b.title IS NOT NULL AND EXISTS (SELECT * FROM user AS u WHERE u.column2 = b.column1)
group by title, version
) c )

Try this:
(SELECT count(*) as id
FROM (
SELECT DISTINCT title, version
FROM book b left join user u on b.column1=u.column1
WHERE b.title IS NOT NULL and u.column1 is null
EXCEPT
SELECT DISTINCT title, version
FROM book b join user u on b.column1=u.column2
WHERE b.title IS NOT NULL
) c )

Hmmm . . . for performance, try removing the group by and select distinct (except does this anyway):
SELECT count(*) as id
FROM (SELECT title, version
FROM book b
WHERE b.title IS NOT NULL AND NOT EXISTS (SELECT 1 FROM user u WHERE u.column1 = b.column1)
EXCEPT
SELECT title, version
FROM book b
WHERE b.title IS NOT NULL AND EXISTS (SELECT 1 FROM user u WHERE u.column2 = b.column1)
) c;
Next: Be sure you have an index on user(column2) and user(column1).
With more information about the data, it might be possible to make further suggestions.

Related

SQL : Im tring to work out, how to return last action per member

Id Mshp_Id Action
1 9029 Register
2 9029 Create CV
3 8476 Register
4 8476 Create CV
5 8476 JOB SEARCH
I want to return the two membership ID's and their latest action.
so what would be left is ID 2 AND 5 ONLY.
If you are using SQL Server 2012+, you can use LAST_VALUE
SELECT ID,
,mshp_id
,action
FROM (
SELECT *,LAST_VALUE(id) OVER (PARTITION BY mshp_id
ORDER BY ID
ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING
) last_val
FROM YOUR_TABLE
) a
WHERE id = last_val
ORDER BY ID
Check Demo here
Output
Last action per member can be fetched through the following ways
Solution 1:
select Id, Mshp_Id, Action from (
select *, row_number() over (partition by Mshp_Id order by id desc) r from user_action
) a
where a.r = 1
order by id
Solution 2
select u.* from user_action u
join (select Mshp_Id, max(id) id from user_action
group by Mshp_Id ) a
on a.Mshp_Id = u.Mshp_Id and a.id = u.id
order by u.id
Good luck with your work !

T-SQL query to show all the past steps, active and future steps

I have 3 tables in SQL Server:
map_table: (workflow map path)
stepId step_name
----------------
1 A
2 B
3 C
4 D
5 E
history_table:
stepId timestamp author
----------------------------
1 9:00am John
2 9:20am Mary
current_stageTable:
Id currentStageId waitingFor
------------------------------------
12345 3 Kat
I would like to write a query to show the map with the workflow status. Like this result here:
step name time author
----------------------------
1 A 9:00am John
2 B 9:20am Mary
3 C waiting Kat
4 D
5 E
I tried left join
select
m.stepId, m.step_name, h.timestamp, h.author
from
map_table m
left join
history_table h on m.stepId = h.stepId
I thought it will list all the records from the map table, since I am using left join, but somehow it only shows 3 records which is from history table..
So I changed to
select
m.stepId, m.step_name, h.timestamp, h.author
from
map_table m
left join
history_table h on m.stepId = h.stepId
union
select
m.stepId, m.step_name, '' as timestamp, '' as author
from
map_table m
where
m.stageId not in (select stageId from history_table)
order by
m.stepId
Then it list the result almost as I expected, but how do I add the 3rd table in to show the current active stage?
Thank you very much for all your help!! Much appreciated.
Looks like it's what you asked:
with map_table as (
select * from (values (1,'A')
,(2,'B')
,(3,'C')
,(4,'D')
,(5,'E')) t(stepId, step_name)
)
, history_table as (
select * from (values
(1,'9:00am','John')
,(2,'9:20am','Mary')) t(stepId, timestamp, author)
)
, current_stapeTable as (
select * from (values (2345, 3, 'Kat')) t(Id, currentStageId, waitingFor)
)
select
m.stepId, m.step_name
, time = coalesce(h.timestamp, case when c.waitingFor is not null then 'waiting' end)
, author = coalesce(h.author, c.waitingFor)
from
map_table m
left join history_table h on m.stepId = h.stepId
left join current_stapeTable c on m.stepId = c.currentStageId
I think a union fits well with the data and avoids the coalescing the values on multiple joins.
with timeline as (
select stepId, "timestamp" as ts, author from history_table
union all
select currentStageId, 'waiting', waitingFor from current_stageTable
)
select step_id, step_name, "timestamp", author
from
map_table as m left outer join timeline as t
on t.stepId = m.stepId

Recursive query SQL Server not working as expected

thanks in advance for you help. I'm still quite new to MS SQL db but I was wondering why my recursive query for MSSQL below does not return the value i'm expecting. I've done my research and at the bottom is the code I came up with. Lets say I have the following table...
CategoryID ParentID SomeName
1 0 hmm
2 0 err
3 0 woo
4 3 ppp
5 4 ttt
I'm expecting the query below to return 3 4 5. I basically wanted to get the list of category id's heirarchy below it self inclusive based on the category id I pass in the recursive query. Thanks for you assistance.
GO
WITH RecursiveQuery (CategoryID)
AS
(
-- Anchor member definition
SELECT a.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS a
WHERE a.ParentID = CategoryID
UNION ALL
-- Recursive member definition
SELECT b.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS b
INNER JOIN RecursiveQuery AS d
ON d.CategoryID = b.ParentID
)
-- Statement that executes the CTE
SELECT o.CategoryID
FROM [SomeDB].[dbo].[SomeTable] AS o
INNER JOIN RecursiveQuery AS d
ON d.CategoryID = 3
GO
If you want tree from specific root:
DECLARE #rootCatID int = 3
;WITH LessonsTree (CatID)
AS
(
SELECT a.CategoryID
FROM [EducationDatabase].[dbo].[LessonCategory] AS a
WHERE a.CategoryID = #rootCatID ---<<<
UNION ALL
SELECT b.CategoryID
FROM LessonsTree as t
INNER JOIN [EducationDatabase].[dbo].[LessonCategory] AS b
ON b.ParentID = t.CatID
)
SELECT o.*
FROM LessonsTree t
INNER JOIN [EducationDatabase].[dbo].[LessonCategory] AS o
ON o.CategoryID = t.CatID
As stated in the comments, the anchor isn't restricted. Easiest solution is to add the criterium in the anchor
with RecursiveQuery (theID)
AS
(
SELECT a.ParentID --root id=parentid to include it and to prevent an extra trip to LessonCategory afterwards
FROM [LessonCategory] AS a
WHERE a.ParentID = 3 --restriction here
UNION ALL
SELECT b.CategoryID
FROM [LessonCategory] AS b
INNER JOIN RecursiveQuery AS d
ON d.theID = b.ParentID
)
SELECT* from RecursiveQuery
Another option is to have the recursive query be general (no restricted anchor) and have it keep the rootid as well. Then the query on the cte can restrict on the rootid (the first option is probably better, this second one is mainly suitable if you are created some sort of root-view)
with RecursiveQuery
AS
(
SELECT a.ParentID theID, a.ParentID RootID
FROM [LessonCategory] AS a
UNION ALL
SELECT b.CategoryID, d.RootID
FROM [LessonCategory] AS b
INNER JOIN RecursiveQuery AS d
ON d.theID = b.ParentID
)
SELECT theID from RecursiveQuery where RootID = 3

TSQL optimizing code for NOT IN

I inherit an old SQL script that I want to optimize but after several tests, I must admit that all my tests only creates huge SQL with repetitive blocks. I would like to know if someone can propose a better code for the following pattern (see code below). I don't want to use temporary table (WITH). For simplicity, I only put 3 levels (table TMP_C, TMP_D and TMP_E) but the original SQL have 8 levels.
WITH
TMP_A AS (
SELECT
ID,
Field_X
FROM A
TMP_B AS(
SELECT DISTINCT
ID,
Field_Y,
CASE
WHEN Field_Z IN ('TEST_1','TEST_2') THEN 'CATEG_1'
WHEN Field_Z IN ('TEST_3','TEST_4') THEN 'CATEG_2'
WHEN Field_Z IN ('TEST_5','TEST_6') THEN 'CATEG_3'
ELSE 'CATEG_4'
END AS CATEG
FROM B
INNER JOIN TMP_A
ON TMP_A.ID=TMP_B.ID),
TMP_C AS (
SELECT DISTINCT
ID,
CATEG
FROM TMP_B
WHERE CATEG='CATEG_1'),
TMP_D AS (
SELECT DISTINCT
ID,
CATEG
FROM TMP_B
WHERE CATEG='CATEG_2' AND ID NOT IN (SELECT ID FROM TMP_C)),
TMP_E AS (
SELECT DISTINCT
ID,
CATEG
FROM TMP_B
WHERE CATEG='CATEG_3'
AND ID NOT IN (SELECT ID FROM TMP_C)
AND ID NOT IN (SELECT ID FROM TMP_D))
SELECT * FROM TMP_C
UNION
SELECT * FROM TMP_D
UNION
SELECT * FROM TMP_E
Many thanks in advance for your help.
First off, select DISTINCT will prevent duplicates from the result set, so you are overworking the condition. By adding the "WITH" definitions and trying to nest their use makes it more confusing to follow. The data is ultimately all coming from the "B" table where also has key match in "A". Lets start with just that... And since you are not using anything from the (B)Field_Y or (A)Field_X in your result set, don't add them to the mix of confusion.
SELECT DISTINCT
B.ID,
CASE WHEN B.Field_Z IN ('TEST_1','TEST_2') THEN 'CATEG_1'
WHEN B.Field_Z IN ('TEST_3','TEST_4') THEN 'CATEG_2'
WHEN B.Field_Z IN ('TEST_5','TEST_6') THEN 'CATEG_3'
ELSE 'CATEG_4'
END AS CATEG
FROM
B JOIN A ON B.ID = A.ID
WHERE
B.Field_Z IN ( 'TEST_1', 'TEST_2', 'TEST_3', 'TEST_4', 'TEST_5', 'TEST_6' )
The where clause will only include those category qualifying values you want and still have the results per each category.
Now, if you actually needed other values from your "Field_Y" or "Field_X", then that would generate a different query. However, your Tmp_C, Tmp_D and Tmp_E are only asking for the ID and CATEG columns anyhow.
This may perform better
SELECT DISTINCT B.ID, 'CATEG_1'
FROM
B JOIN A ON B.ID = A.ID
WHERE
B.Field_Z IN ( 'TEST_1', 'TEST_2')
UNION
SELECT DISTINCT B.ID, 'CATEG_2'
FROM
B JOIN A ON B.ID = A.ID
WHERE
B.Field_Z IN ( 'TEST_3', 'TEST_4')
...

Join the table valued function in the query

I have one table vwuser. I want join this table with the table valued function fnuserrank(userID). So I need to cross apply with table valued function:
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
For each userID it generates multiple records. I only want the last record for each empid that does not have a Rank of Term(inated). How can I do this?
Data:
HistoryID empid Rank MonitorDate
1 A1 E1 2012-8-9
2 A1 E2 2012-9-12
3 A1 Term 2012-10-13
4 A2 E3 2011-10-09
5 A2 TERM 2012-11-9
From this 2nd record and 4th record must be selected.
In SQL Server 2005+ you can use this Common Table Expression (CTE) to determine the latest record by MonitorDate that doesn't have a Rank of 'Term':
WITH EmployeeData AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY empId, ORDER BY MonitorDate DESC) AS RowNumber
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
WHERE Rank != 'Term'
)
SELECT *
FROM EmployeeData AS ed
WHERE ed.RowNumber = 1;
Note: The statement before this CTE will need to end in a semi-colon. Because of this, I have seen many people write them like ;WITH EmployeeData AS...
You'll have to play with this. Having trouble mocking your schema on sqlfiddle.
Select bar.*
from
(
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
where rank != 'TERM'
) foo
left join
(
SELECT *
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empId = bar.empId
and foo.MonitorDate > bar.MonitorDate
where bar.empid is null
I always need to test out left outers on dates being higher. The way it works is you do a left outer. Every row EXCEPT one per user has row(s) with a higher monitor date. That one row is the one you want. I usually use an example from my code, but i'm on the wrong laptop. to get it working you can select foo., bar. and look at the results and spot the row you want and make the condition correct.
You could also do this, which is easier to remember
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
) foo
join
(
select empid, max(monitordate) maxdate
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empid = bar.empid
and foo.monitordate = bar.maxdate
I usually prefer to use set based logic over aggregate functions, but whatever works. You can tweak it also by caching the results of your TVF join into a table variable.
EDIT:
http://www.sqlfiddle.com/#!3/613e4/17 - I mocked up your TVF here. Apparently sqlfiddle didn't like "go".
select foo.*, bar.*
from
(
SELECT f.*
FROM vwuser AS a
join fnuserrank f
on a.empid = f.empid
where rank != 'TERM'
) foo
left join
(
SELECT f1.empid [barempid], f1.monitordate [barmonitordate]
FROM vwuser AS b
join fnuserrank f1
on b.empid = f1.empid
where rank != 'TERM'
) bar
on foo.empId = bar.barempid
and foo.MonitorDate > bar.barmonitordate
where bar.barempid is null

Resources