My CASE statement is wrong. Any idea what I am doing wrong? - sql

I am in a logjam.
When I run the following query, it works:
select DISTINCT l.Seating_Capacity - (select count(*)
from tblTrainings t1, tbllocations l
where l.locationId = t1.LocationId) as
availableSeats
from tblTrainings t1, tbllocations l
where l.locationId = t1.LocationId
However, we would like to add a CASE statement that says, when Seating_Capacity - total count as shown above = 0 then show 'FULL' message.
Otherwise, show remaining number.
Here is that query:
select DISTINCT case when l.Seating_Capacity - (select count(*)
from tblTrainings t1, tbllocations l
where l.locationId = t1.LocationId) = 0 then 'full' else STR(Seating_Capacity) end)
availableSeats
from tblTrainings t1, tbllocations l
where l.locationId = t1.LocationId
I am getting 'Incorrect syntax near ')' which is near 'End'
I am also getting an error that the inner Seating_Capacity is invalid column name.
Your assistance is greatly appreciated.
I must have been in a dream land because I thought it was working during testing.
Now, the app is live and it isn't working.
Thanks a lot in advance
select DISTINCT l.LocationId,c.courseId, c.coursename, l.Seating_Capacity - (select count(*)
from tblTrainings t1
where l.locationId = t1.LocationId and c.courseId = t1.courseId) as
availableSeats,d.dateid,d.trainingDates,d.trainingtime,c.CourseDescription,
i.instructorName,l.location,l.seating_capacity
from tblLocations l
Inner Join tblCourses c on l.locationId = c.locationId
left join tblTrainings t on l.locationId = t.LocationId and c.courseId = t.courseId
Inner Join tblTrainingDates d on c.dateid=d.dateid
Inner Join tblCourseInstructor ic on c.courseId = ic.CourseId
Inner Join tblInstructors i on ic.instructorId = i.instructorId
WHERE CONVERT(VARCHAR(10), d.trainingDates, 101) >= CONVERT(VARCHAR(10), GETDATE(), 101)

To avoid repeating the expression, you can use a WITH clause to simplify your query:
WITH (
-- Start with your query that already works
SELECT DISTINCT l.Seating_Capacity - (select count(*)
from tblTrainings t1, tbllocations l
where l.locationId = t1.LocationId) AS availableSeats
FROM tblTrainings t1, tbllocations l
WHERE l.locationId = t1.LocationId
) AS source
SELECT
-- Add a CASE statement on top of it
CASE WHEN availableSeats = 0 THEN 'Full'
ELSE STR(availableSeats)
END AS availableSeats
FROM source

You have an extra ) at the end of your case statement remove that.
0 then 'full' else STR(Seating_Capacity) end)
^^^
for Seating_Capacity try accessing it with table alias like l.Seating_Capacity

I think you are over complicating the query with your subquery. As I understand it then the following should work as you need:
SELECT AvailableSeats = CASE WHEN l.Seating_Capacity - COUNT(*) = 0 THEN 'Full'
ELSE STR(l.Seating_Capacity - COUNT(*))
END
FROM tblTrainings t1
INNER JOIN tblLocations l
ON l.LocationID = t1.LocationID
GROUP BY l.Seating_Capacity;
I have changed your else to STR(l.Seating_Capacity - COUNT(*)) because I assume you want to know the seats remaining, rather than just the capacity? If I have misinterpreted the requirement, just change it to STR(l.Seating_Capacity).
I have also switched your ANSI 89 implicit joins to ANSI 92 explicit joins, the standard changed over 20 years, and there are plenty of good reasons to switch to the newer syntax. But for completeness the ANSI 89 version of the above query would be:
SELECT AvailableSeats = CASE WHEN l.Seating_Capacity - COUNT(*) = 0 THEN 'Full'
ELSE STR(l.Seating_Capacity - COUNT(*))
END
FROM tblTrainings t1, tblLocations l
WHERE l.LocationID = t1.LocationID
GROUP BY l.Seating_Capacity;
EDIT
To adapt your full query you can simply replace your subquery in the select, with a joined subquery:
SELECT l.LocationId,
c.courseId,
c.coursename,
CASE WHEN l.Seating_Capacity - t.SeatsTaken = 0 THEN 'Full'
ELSE STR(l.Seating_Capacity - t.SeatsTaken)
END AS availableSeats,
d.dateid,
d.trainingDates,
d.trainingtime,
c.CourseDescription,
i.instructorName,
l.location,
l.seating_capacity
FROM tblLocations l
INNER JOIN tblCourses c
ON l.locationId = c.locationId
LEFT JOIN
( SELECT t.LocationID, t.CourseID, SeatsTaken = COUNT(*)
FROM tblTrainings t
GROUP BY t.LocationID, t.CourseID
) t
ON l.locationId = t.LocationId
AND c.courseId = t.courseId
INNER JOIN tblTrainingDates d
ON c.dateid=d.dateid
INNER JOIN tblCourseInstructor ic
ON c.courseId = ic.CourseId
INNER JOIN tblInstructors i
ON ic.instructorId = i.instructorId
WHERE d.trainingDates >= CAST(GETDATE() AS DATE);
JOINs tend to optimise better than correlated subqueries (although sometimes the optimiser can determine that a JOIN would work instead), it also means that you can reference the result (SeatsTaken) multiple times without re-evaluating the subquery.
In addition, by moving the count to a subquery, and removing the join to tblTrainings I think you eliminate the need for DISTINCT which should improve the performance.
Finally I have changed this line:
WHERE CONVERT(VARCHAR(10), d.trainingDates, 101) >= CONVERT(VARCHAR(10), GETDATE(), 101)
To
WHERE d.trainingDates >= CAST(GETDATE() AS DATE);
I don't know if you do, but if you had an index on d.TrainingDates then by converting it to varchar to compare it to today you remove the ability for the optimiser to use this index, since you are only saying >= midnight today, there is no need to perform any conversion on d.TrainingDates, and all you need to do is remove the time portion of GETDATE(), which can be done by casting to DATE. More on this is contained in this article (Yet another gem from Aaron Bertrand)

Related

Getting rid of an expensive self join

I have a SQL statement like this
SELECT
pa.col1,
SUM(ps.col2) col2,
SUM(psl.col2) col2_previous_month
FROM
pa
LEFT JOIN
ps ON pa.Id = ps.Id AND ps.date = #currDate
LEFT JOIN
ps as psl ON psl.Id = ps.Id AND psl.date = dateadd(month, - 1, #currDate)
GROUP BY
pa.col1;
This SQL is called often and since the table ps has 100M rows the left join is hurting. Is there a way to rewrite this using left Join?
Regards
Nick
Perhaps this will help
Select pa.col1
,col2 =isnull(sum(case when ps.date=#currDate then ps.col2 else null end),0)
,col2_prior=isnull(sum(case when ps.date=dateadd(month,-1,#currDate) then ps.col2 else null end),0)
From pa
JOIN ps as ps ON pa.Id = ps.Id
and ps.date in (#currDate,dateadd(month,-1,#currDate))
Group By pa.col1
If the query of John doesn't help you can also try this one:
SELECT
pa.col1
,SUM(ps1.col2) col2
,SUM(ps2.col2) col2_previous_month
FROM pa
LEFT JOIN
(
SELECT col2
FROM ps
WHERE date = #currDate
) ps1 ON ON pa.Id = ps1.Id
LEFT JOIN
(
SELECT col2
FROM ps
WHERE date = dateadd(month, - 1, #currDate)
) ps2 ON ON pa.Id = ps2.Id
GROUP BY pa.col1;
I thought to it after having read your comments.
It is exactly the same as your initial query except that I moved the search on dates inside a nested query, which might help the optimizer to properly use the index.
The query looks fine. In order to have it perform fast, you should have the following indexes:
pa (id)
ps (id, date)
If you want it still faster, use covering indexes:
pa (id, col1)
ps (id, date, col2)

Self join on joined table

My query looks like
Select m.cw_sport_match_id as MatchId,
m.season_id as SeasonId,
s.title as SeasonName,
c.title as ContestName
from dbo.cw_sport_match m
inner join dbo.cw_sport_season s
ON m.season_id = s.cw_sport_season_id
inner join dbo.cw_sport_contest c
ON m.contest_id = c.cw_sport_contest_id
Where s.date_start <= GETDATE() AND s.date_end >= GETDATE()
order by s.date_start
No i need the name parent of the sport_contest (if there is one, it can be null). So basically a self join but no on the same table as the query is for. All the examples that i find do the self join are not done on another table.
can any sql pro help me?
So how can i join the cw_sport_season itself with the season_parent_id and get the title of it?
If I'm understanding your question correctly, you want to outer join the cw_sport_season table to itself using the season_parent_id field. Maybe something on these lines:
Select m.cw_sport_match_id as MatchId,
m.season_id as SeasonId,
s.title as SeasonName,
parent.title as ParentSeasonName,
c.title as ContestName
from dbo.cw_sport_match m
inner join dbo.cw_sport_season s
ON m.season_id = s.cw_sport_season_id
inner join dbo.cw_sport_contest c
ON m.contest_id = c.cw_sport_contest_id
left join dbo.cw_sport_season parent
ON s.season_parent_id = parent.cw_sport_season_id
Where s.date_start <= GETDATE() AND s.date_end >= GETDATE()
order by s.date_start

Query takes forever to output info - tips on optimization

I have a query that works awesomely - but - it takes about 10 minutes to load up. Which is insane. And I would like for it to run faster than it currently does now.
I was wondering if there were any tips I could take to optimize my query to make it run faster?
select DISTINCT
c.PaperID,
cdd.CodesF,
c.PageCount,
prr.projectname,
u.firstname + ' ' + u.lastname as Name,
ett.EventName,
cast(c.AssignedDate as DATE) [AssignedDate],
cast(ev.EventCompletionDate as DATE) [CompletionDate],
ar.ResultDescription,
a.Editor
from tbl_Papers c
left outer join (select cd.PaperId, count(*) as CodesF
from tbl_PaperCodes cd group by cd.PaperId) cdd
on cdd.PaperId = c.PaperId
left outer join
(SELECT
wfce.PaperEventActionNum,
c.PaperId,
CONVERT(varchar,wfce.ActionDate,101) CompletionDate,
pr.ProjectName,
wfce.ActionUserId,
u.firstname+' '+u.lastname [Editor]
FROM
dbo.tbl_WFPaperEventActions wfce
INNER JOIN dbo.tbl_Papers c ON wfce.PaperId = c.PaperId
INNER JOIN tbl_Providers p ON p.ProviderID = c.ProviderID
INNER JOIN tbl_Sites s ON s.SiteID = p.SiteID
INNER JOIN tbl_Projects pr ON s.ProjectId=pr.ProjectId
INNER JOIN tbl_Users u ON wfce.ActionUserId=u.UserId
WHERE
wfce.EventId = 204
AND c.Papersource =0
GROUP BY
wfce.PaperEventActionNum,
c.PaperId,
CONVERT(varchar,wfce.ActionDate,101),
pr.ProjectName,
wfce.ActionUserId,
u.firstname+' '+u.lastname
)a ON a.PaperId=c.PaperId,
tbl_Providers p, tbl_Sites s,
tbl_Projects prr, tbl_WFPaperEvents ev,
tbl_Users u, tbl_WFPaperEventTypes ett,
tbl_WFPaperEventActions arr, tbl_WFPaperEventActionResults ar
where s.SiteId = p.SiteId
and p.ProviderId = c.ProviderId
and s.ProjectId = prr.ProjectId
and ev.PaperId = c.PaperId
and ev.EventCreateUserId = u.UserId
and ev.EventCompletionDate >= dateadd(day,datediff(day,1,GETDATE()),0)
and ev.EventCompletionDate < dateadd(day,datediff(day,0,GETDATE()),0)
and ev.EventStatusId = 3
and ev.EventId in (201, 203)
and c.Papersource =0--Offshore
and ev.EventId=ett.EventID
and arr.PaperId=c.PaperId
and arr.EventId=ev.EventId
and arr.EventId=ar.EventID
and arr.ActionResultId=ar.ResultID
and arr.ActionResultId in (1,2,3,4)
order by paperid, u.FirstName + ' ' + u.LastName
You need to re-look carefully at every piece of this query and ask yourself, is that needed?
Take the subquery with alias a.
It joins 6 tables, but if you trace up to your final select clause only [Editor] is supplied from that alias. So do you need 6 tables to arrive at editor? No you don't in fact you only need 2 tbl_WFPaperEventActions and tbl_Users. Furthermore, this subquery is grouping by 6 items including a date, but 3 of those items are not used anywhere else in the overall query - so why go include these in the grouping? This allows us to drop 3 of the joined tables.
Of the remaining 3 grouping items a further 1 can be substituted to avoid the join between tbl_WFPaperEventActions and tbl_Papers because the join condition is "wfce.PaperId = c.PaperId", all we need then is to group by wfce.PaperId instead of c.PaperId
Finally we are then interested in the field wfce.PaperEventActionNum this is supplied by the subquery but isn't used in the larger query? Why provide that field is it isn't used? Well it turns out that it should be used to complete a join. The subquery aliased as a needs joining into the outer query on both PaperEventActionNum and PaperId. This by the way also requires that the whole subquery needs to be pushed down the joining structure to comply with ANSI join syntax rules.
Never "mix" ANSI join syntax with joins done "the old fashioned way"
This really is a recipe for a disaster.
Below I have "started" some amendments to your query, but I cannot really complete it as I have no way to test any part of it; and I don't know your data model at all.
Personally, I would re-start this query from scratch, starting lean and adding item by item to ensure it remains lean.
SELECT DISTINCT /* distinct isn't a good solution here */
c.PaperID
, cdd.CodesF
, c.PageCount
, prr.projectname
, u.firstname + ' ' + u.lastname AS Name
, ett.EventName
, CAST(c.AssignedDate AS date) [AssignedDate]
, CAST(ev.EventCompletionDate AS date) [CompletionDate]
, ar.ResultDescription
, a.Editor
FROM tbl_Papers c
LEFT OUTER JOIN ( -- can this be an inner join instead?
SELECT
cd.PaperId
, COUNT(*) AS CodesF
FROM tbl_PaperCodes cd
GROUP BY
cd.PaperId
) cdd
ON cdd.PaperId = c.PaperId
INNER JOIN tbl_Providers p ON c.ProviderId = p.ProviderId
INNER JOIN tbl_Sites s ON p.SiteId = s.SiteId
INNER JOIN tbl_Projects prr ON s.ProjectId = prr.ProjectId
INNER JOIN tbl_WFPaperEvents ev ON c.PaperId = ev.PaperId
INNER JOIN tbl_Users u ON ev.EventCreateUserId = u.UserId
INNER JOIN tbl_WFPaperEventTypes ett ON ev.EventId = ett.EventID
INNER JOIN tbl_WFPaperEventActions arr ON c.PaperId = arr.PaperId
AND ev.EventId = arr.EventId
INNER JOIN tbl_WFPaperEventActionResults ar ON arr.EventId = ar.EventID
AND arr.ActionResultId = ar.ResultID
AND arr.ActionResultId IN (1, 2, 3, 4)
LEFT OUTER JOIN (
SELECT
wfce.PaperEventActionNum
, wfce.PaperId
--, c.PaperId
--, CONVERT(varchar, wfce.ActionDate, 101) CompletionDate -- cast to date here
--, pr.ProjectName
--, wfce.ActionUserId
, u.firstname + ' ' + u.lastname [Editor]
FROM dbo.tbl_WFPaperEventActions wfce
--INNER JOIN dbo.tbl_Papers c ON wfce.PaperId = c.PaperId
--INNER JOIN tbl_Providers p ON p.ProviderID = c.ProviderID
--INNER JOIN tbl_Sites s ON s.SiteID = p.SiteID
--INNER JOIN tbl_Projects pr ON s.ProjectId = pr.ProjectId
tbl_Users INNER JOIN u ON wfce.ActionUserId = u.UserId
WHERE wfce.EventId = 204
AND c.Papersource = 0
GROUP BY
wfce.PaperEventActionNum
, wfce.PaperId
--, c.PaperId
--, CONVERT(varchar, wfce.ActionDate, 101)
--, pr.ProjectName
--, wfce.ActionUserId
, u.firstname + ' ' + u.lastname
) a
ON c.PaperId = a.PaperId AND arr.PaperEventActionNum = a.PaperEventActionNum
WHERE ev.EventCompletionDate >= DATEADD(DAY, DATEDIFF(DAY, 1, GETDATE()), 0)
AND ev.EventCompletionDate < DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0)
AND ev.EventStatusId = 3
AND ev.EventId IN (201, 203)
AND c.Papersource = 0--Offshore
ORDER BY
paperid, u.FirstName + ' ' + u.LastName
I really do hate DISTINCT. It is nasty. It does not solve problems, it just hides them; AND slows down everything to do the hiding.
Use distinct in inverse proportion to query complexity:
if a query is really simple you can use distinct
If a query is complex do not use distinct
Check how many of fields on which you have join, where and group by clauses, have indexes. Every non-indexed field can negatively affect performance.
Calculated fields in GROUP BY are probably a pain, as well as DISTINCT (especially if they are not indexed). E.g. grouping on something like u.ID instead of u.firstname+' '+u.lastname or pr.ProjectId i/o pr.ProjectName should make things faster (you can sort the output according to the other criteria if needed).
Do you really need left join where you use it? I.e. do you want to keep the tables from the other side of the join even when there is no match on the other side? If not, replace it with inner join.
Various small improvements here, e.g.:
(assuming Papersource and EventId are indexes):
FROM
(SELECT * FROM dbo.tbl_WFPaperEventActions WHERE EventId = 204) wfce
INNER JOIN
(SELECT * FROM dbo.tbl_Papers WHERE Papersource = 0) c
ON wfce.PaperId = c.PaperId
INNER JOIN tbl_Providers p ON p.ProviderID = c.ProviderID
INNER JOIN tbl_Sites s ON s.SiteID = p.SiteID
INNER JOIN tbl_Projects pr ON s.ProjectId=pr.ProjectId
INNER JOIN tbl_Users u ON wfce.ActionUserId=u.UserId
instead of
FROM
dbo.tbl_WFPaperEventActions wfce
INNER JOIN dbo.tbl_Papers c ON wfce.PaperId = c.PaperId
INNER JOIN tbl_Providers p ON p.ProviderID = c.ProviderID
INNER JOIN tbl_Sites s ON s.SiteID = p.SiteID
INNER JOIN tbl_Projects pr ON s.ProjectId=pr.ProjectId
INNER JOIN tbl_Users u ON wfce.ActionUserId=u.UserId
WHERE
wfce.EventId = 204
AND c.Papersource =0
or (if I understood the idea correctly):
and ev.EventCompletionDate BETWEEN (
dateadd(day, -1, GETDATE()) and dateadd(ns, -1, GETDATE())
instead of:
and ev.EventCompletionDate >= dateadd(day,datediff(day,1,GETDATE()),0)
and ev.EventCompletionDate < dateadd(day,datediff(day,0,GETDATE()),0)
In general: ask yourself what exactly you want to achieve with this query, which parts of the data are relevant for it, how many of your source tables can be replaced with snippets from them (this can make JOINs work faster), and try to be consistent regarding the usage of JOIN and WHERE clauses.

Update with join takes long time

I have a problem updating a table. I'm running the following query:
UPDATE Table1 SEt entrena = c.Count
FROM Table1 AS p INNER JOIN (
SELECT e.EmplID, COUNT(e.EmplID ) as Count
FROM Table2 AS e WHERE e.Start >= #Start AND e.Start <=#End
GROUP BY e.EmplID ) AS c ON p.EmplID = c.EmplID
WHERE P.Date = '2050-12-31'
The Table1 has 12000 rows and the select inside the join get only 51 rows but the update takes around 2 minutes, but if I delete the where clase p.date = '2050-12-31' the update takes less than a second. And I can't figure out how to solve it. I'm using SQL Server 2008.
Both tables don't have indexes.
This is your query:
UPDATE Table1
SET entrena = c.Count
FROM Table1 p INNER JOIN
(SELECT e.EmplID, COUNT(e.EmplID ) as Count
FROM Table2 e
WHERE e.Start >= #Start AND e.Start <=#End
GROUP BY e.EmplID
) c
ON p.EmplID = c.EmplID
WHERE P.Date = '2050-12-31';
First, you need to change the first line to:
UPDATE p
when you define an alias in the from clause, you need to use that in the update for the right thing to happen. (I wish your query generated an error in SQL Server, but it does not.)
To optimize this query, you want to add indexes. I would suggest these two:
Table1(date, EmplID)
Table2(EmplId, Start)
You may then find that the correlated subquery version is faster, particularly if the where clause is highly selective:
UPDATE p
SET entrena = (SELECT COUNT(e.EmplID ) as Count
FROM Table2 e
WHERE e.Start >= #Start AND e.Start <=#End AND
p.EmplID = e.EmplID
)
FROM Table1 p
WHERE P.Date = '2050-12-31';
However, I suspect that the p versus table1 is the root of your performance problem.

Date comparison function in SQL Server

I am trying to display records which are created after Oct 1 2010. But my query doesn't seem to work. It also display records from 2004 - Sept 2010 which is not wanted.
What is wrong with the query below?
select Distinct app.app_id,
(convert(varchar, creation_date,101) + ' ' + convert(varchar,creation_date ,108)) as creation_date,
dbo.oea_fn_get_amc_mem_name(app.app_id,primary_msn,getdate(), 'EN', 30000) PIName,
dbo.oea_fn_get_pid_countyname(app.app_id,primary_msn,'OC')as PIpid,
primary_msn,
zip,
home_tel,
work_tel,
work_extn,
other_contact,
other_ext,
cell_tel,
dbo.oea_fn_get_amc_mem_name(app.app_id,mem.msn,getdate(), 'EN', 30000)as Kname,
dbo.oea_fn_get_pid_countyname(app.app_id,mem.msn,'OC')as Knamepid,
mem.msn as Kmsn,
(select count(reminder_id) from reminders (nolock) where app_id=app.app_id) as reminder
from app_application app (nolock)
inner join app_member mem with (nolock) on app.app_id=mem.app_id
--left outer join Oea_App_Program_Disposition disp with (nolock) on mem.app_id = disp.app_id and mem.msn=disp.msn
inner join app_address aadd with (nolock) on app.app_id=aadd.app_id
--inner join app_calc_results calc with (nolock) on mem.app_id=calc.app_id and calc.msn=mem.msn
left outer join app_member_benefits ben with (nolock) on mem.app_id = ben.app_id and mem.msn=ben.msn
where
isnull(mem.coverage_required,0) = 1
and app.app_status = 's'
and ben.ins_end_date < getdate()
and app.client_id = 30000
and app.app_id not in (select app_id from app_renewal)
and (mem.msn in (select calc.msn from app_calc_results calc
inner join app_application app on calc.app_id = app.app_id and calc.prog_id = 'CK' and calc.opt_out = 1))
and (mem.msn in (select msn from app_calc_results where app_id=app.app_id and status not in ('A','X')))
or (mem.msn in (select msn from Oea_App_Program_Disposition where app_id=app.app_id and disp_status not in ('A','P')) )
and app.creation_date >= '10/01/2010'
Thanks for all the help.
You probably want this:
and (
(mem.msn in (select calc.msn from app_calc_results calc
inner join app_application app on calc.app_id = app.app_id and calc.prog_id = 'CK' and calc.opt_out = 1))
or (mem.msn in (select msn from app_calc_results where app_id=app.app_id and status not in ('A','X')))
or (mem.msn in (select msn from Oea_App_Program_Disposition where app_id=app.app_id and disp_status not in ('A','P')) )
)
and app.creation_date >= '10/01/2010'
The problem is with the logic behind the or in the where clause.
As others have stated, the problem is likely the Or clause in the Where clause. In effect, your query is:
Select ...
From ..
Where (A And B And C
And D And E
And F And G
And app.creation_date >= '10/01/2010'
)
Or mem.msn In (
Select msn
From Oea_App_Program_Disposition
Where app_id=app.app_id
And disp_status not in ('A','P')
)
Thus, if for any row, if the Or is true, the rest of the "Ands" are ignored. I would assume that the Or is supposed to be paired with one of the And clauses.