How to simplify below T-SQL statement in one single query?
IF #OrderByDescription = 1
BEGIN
SELECT d.DeptId, Description
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY Description
END
ELSE
BEGIN
SELECT d.DeptId, Description
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY
CASE WHEN o.[Order] IS NULL THEN 1
ELSE 0
END, o.[Order]
END
Move your IF condition into a CASE, then nest your other CASE inside of it.
SELECT
d.DeptId
,Description
FROM
Dept AS d
LEFT JOIN
DeptOrder AS o
ON
p.DeptId = o.DeptId
WHERE
d.DeptId IN
( 3, 7, 9, 10, 17, 20 )
ORDER BY
CASE
WHEN #OrderByDescription = 1 THEN Description
ELSE CASE
WHEN o.[Order] IS NULL THEN 1
ELSE 0
END
END
,o.[Order];
Try this:
SELECT d.DeptId, Description
FROM Dept d LEFT JOIN DeptOrder o on p.DeptId = o.DeptId
WHERE d.DeptId IN (3, 7, 9, 10, 17, 20)
ORDER BY
CASE
WHEN #OrderByDescription = 1 THEN Description
ELSE 1
END,
CASE
WHEN o.[Order] IS NULL THEN 1
ELSE 0
END,
o.[Order]
Try This.
DECLARE #OrderByDescription INT = 1;
WITH CTE_Order
AS (
SELECT d.DeptId
,Description
,CASE
WHEN #OrderByDescription = 1
THEN ROW_NUMBER() OVER (
ORDER BY Description
)
ELSE ROW_NUMBER() OVER (
ORDER BY CASE
WHEN o.[Order] IS NULL
THEN 1
ELSE 0
END
,o.[Order]
)
END RowNum
FROM Dept d
LEFT JOIN DeptOrder o ON p.DeptId = o.DeptId
WHERE d.DeptId IN (
3
,7
,9
,10
,17
,20
)
)
SELECT *
FROM CTE_Order
ORDER BY RowNum
Related
I have a table (Meetings) which contains the following columns:
HomeTeam(varchar)
AwayTeam(varchar)
Home(int)
Away(int)
My problem is that I need to output all teams which have more than a half wins.
The code provided below is working perfectly for the output, but I need to filter out the teams with more than a half wins. For Example:
Liverpool has won 3 out of 5 games, so I want it to be outputed, PSG has 4 out of 5 wins, so I want them also to be outputed. But Man United has only 2 wins out of 5 games and I do not want them in the output.
I am using MSSQL Server 18.
SELECT T.*,
(SELECT COUNT(*)
FROM Meetings AS M
WHERE (M.HomeTeam = T.Team and M.Home > M.Away) OR
(M.AwayTeam = T.Team and M.Away > M.Home)
GROUP BY COUNT(*) > 2
) AS Wins
FROM Teams AS T
The expected output is:
From Juventus 2/5,
Man United 1/5,
PSG 4/5,
Liverpool 3/5,
Barcelona 3/5,
Bayern Munchen 0/5,
I want in the output to be only PSG, Liverpool and Barcelona as they have more than a half wins.
Here is your solution in "one" statement, logically split in WITH statement:
DECLARE #Meetings TABLE (
HomeTeam VARCHAR(100) NOT NULL,
AwayTeam VARCHAR(100) NOT NULL,
Home INT NOT NULL,
Away INT NOT NULL
)
INSERT INTO #Meetings
(
HomeTeam,
AwayTeam,
Home,
Away
)
VALUES
('A', 'B', 2, 5),
('C', 'D', 1, 5),
('A', 'D', 3, 2),
('C', 'A', 1, 5),
('C', 'B', 4, 2),
('B', 'D', 1, 4)
;WITH Teams AS (
SELECT DISTINCT HomeTeam AS TeamName FROM #Meetings
UNION
SELECT DISTINCT AwayTeam FROM #Meetings
)
, TeamWins AS (
SELECT T.TeamName, (HW.HomeWins + AW.AwayWins) Wins, GM.Games FROM Teams T
OUTER APPLY
(SELECT COUNT(*) HomeWins FROM #Meetings M WHERE M.HomeTeam = T.TeamName AND M.Home > M.Away) HW
OUTER APPLY
(SELECT COUNT(*) AwayWins FROM #Meetings M WHERE M.AwayTeam = T.TeamName AND M.Away > M.Home) AW
OUTER APPLY
(SELECT COUNT(*) Games FROM #Meetings M WHERE M.AwayTeam = T.TeamName) GM
)
SELECT * FROM TeamWins TW WHERE 2*TW.Wins > TW.Games
Output:
TeamName Wins Games
A 2 1
C 1 0
D 2 3
You can LEFT JOIN twice Teams with Meetings (once for home games and once for away games), and then analyze and filter the output:
SELECT
t.*,
COALESCE(h.cnt_wins, 0) + COALESCE(a.cnt_wins, 0) total_wins,
COALESCE(h.cnt_games, 0) + COALESCE(a.cnt_games, 0) total_games
FROM Teams t
LEFT JOIN (
SELECT
HomeTeam Team,
SUM(CASE WHEN Home > Away THEN 1 ELSE 0 END) cnt_wins,
COUNT(*) cnt_games,
FROM Meetings
GROUP BY HomeTeam
) h ON m.Team = t.Team
LEFT JOIN (
SELECT
AwayTeam Team,
SUM(CASE WHEN Home < Away THEN 1 ELSE 0 END) cnt_wins,
COUNT(*) cnt_games,
FROM Meetings
GROUP BY AwayTeam
) a ON a.Team = t.Team
WHERE
COALESCE(h.cnt_wins, 0) + COALESCE(a.cnt_wins, 0)
> ( COALESCE(h.cnt_games, 0) + COALESCE(a.cnt_games, 0) ) / 2
Adding an answer using a cte to organize the table in winners and losers
declare #t table (HomeTeam varchar(100),AwayTeam varchar(100),Home int, Away int)
-- determine winner and loser
;with cte as
(
select Outcome,Team
from #t
cross apply (values
('W'
, case when Home>Away
then HomeTeam
else AwayTeam end)
,('L', case when Home>Away
then AwayTeam
else HomeTeam end)) ca(Outcome,Team)
where Home<>Away
)
select Team
,wins = SUM(case when Outcome = 'W' then 1 else 0 end )
,Losses = SUM(case when Outcome = 'L' then 1 else 0 end )
,Rate = SUM(case when Outcome = 'W' then 1 else 0 end )/COUNT(*)
from cte
group by Team
having SUM(case when Outcome = 'W' then 1 else 0 end )/COUNT(*)>.5
I need to return a zero if the subquery returns no rows. I have tried multiple suggestions found here on SO, to no avail, along with a case statement and coalesce.
My end goal is to sum the resulting subquery results together, but since the 'misc' query returns no rows, the TTL field is blank.
Where am I going wrong, and how do I correct?
Select
s.STID,
rent.amt As RENT,
misc.amt As MISC,
rent.amt + misc.amt As TTL
From
(Select
s.STID
From
Stores s
Where
s.STID Not In (7, 999) ) As s
Left Join
(Select
Income.STID,
Sum(Case When Income.Amount Is Null Then 0 Else Income.Amount End) As amt
From
Income
Inner Join
IncomeTypes On IncomeTypes.IncTypeID = Income.IncTypeID
Inner Join
IncomeSection On IncomeSection.IncSecID = IncomeTypes.IncSecID
Where
Income.IncomeDate = dbo.getdateparam(92, 999) And
IncomeTypes.IncTypeID In (1, 2)
Group By
Income.STID) As rent on s.STID = rent.STID
Left Join
(Select
Income.STID,
Coalesce(Sum(Income.Amount), 0) As amt
From
Income
Inner Join
IncomeTypes On IncomeTypes.IncTypeID = Income.IncTypeID
Inner Join
IncomeSection On IncomeSection.IncSecID = IncomeTypes.IncSecID
Where
Income.IncomeDate = dbo.getdateparam(92, 999) And
IncomeTypes.IncTypeID In (20, 21, 22)
Group By
Income.STID) As misc On s.STID = misc.STID
Order By
s.STID
Currently returns:
STID RENT MISC TTL
1 1234.56
2 1234.56
3 1234.56
4 1234.56
5 1234.56
I have spent the last three hours trying to return as below:
STID RENT MISC TTL
1 1234.56 0 1234.56
2 1234.56 0 1234.56
3 1234.56 0 1234.56
4 1234.56 0 1234.56
5 1234.56 0 1234.56
Use COALESCE:
Select s.STID
, rent.amt As RENT
, Coalesce(misc.amt, 0) As MISC
, rent.amt + Coalesce(misc.amt, 0) As TTL
From (Select s.STID
From Stores s
Where s.STID Not In (7, 999)
) As s
Left Join (Select Income.STID
, Sum(Case When Income.Amount Is Null Then 0
Else Income.Amount
End) As amt
From Income
Inner Join IncomeTypes
On IncomeTypes.IncTypeID = Income.IncTypeID
Inner Join IncomeSection
On IncomeSection.IncSecID = IncomeTypes.IncSecID
Where Income.IncomeDate = dbo.getdateparam(92, 999)
And IncomeTypes.IncTypeID In (1, 2)
Group By Income.STID
) As rent
On s.STID = rent.STID
Left Join (Select Income.STID
, Coalesce(Sum(Income.Amount), 0) As amt
From Income
Inner Join IncomeTypes
On IncomeTypes.IncTypeID = Income.IncTypeID
Inner Join IncomeSection
On IncomeSection.IncSecID = IncomeTypes.IncSecID
Where Income.IncomeDate = dbo.getdateparam(92, 999)
And IncomeTypes.IncTypeID In (20, 21, 22)
Group By Income.STID
) As misc
On s.STID = misc.STID
Order By s.STID;
COALESCE takes the first non-NULL value in the values provided. If misc.amt is NULL, it would default it to the second value, 0.
Needs some help on the following:
Table #Data contains the Opening and Closing Stock for a product over 5 days
Table #BackData contains some post dated transactions
How can i Update the table #Data with a Running Total including a carry forward
CREATE TABLE #Data (
Prod VARCHAR(20)
,SDate DATE
,OStock INT
,CStock INT
)
CREATE TABLE #BackData (
Prod VARCHAR(20)
,SDate DATE
,CStock INT
)
INSERT INTO #Data
SELECT 'p1', '2016-06-06', 10, 10
UNION ALL
SELECT 'p1', '2016-06-07', 10, 14
UNION ALL
SELECT 'p1', '2016-06-08', 14, 13
UNION ALL
SELECT 'p1', '2016-06-09', 13, 13
UNION ALL
SELECT 'p1', '2016-06-10', 13, 11
INSERT INTO #BackData
SELECT 'p1', '2016-06-06', 2
UNION ALL
SELECT 'p1', '2016-06-07', 4
UNION ALL
SELECT 'p1', '2016-06-09', -1
UNION ALL
SELECT 'p1', '2016-06-10', -2
DROP TABLE #Data
DROP TABLE #BackData
Desired Output :
Prod| SDate |OStock |CStock|
p1 |2016-06-06 |10 |12 |
p1 |2016-06-07 |12 |16 |
p1 |2016-06-08 |16 |16 |
p1 |2016-06-09 |16 |15 |
p1 |2016-06-10 |15 |13 |
EDIT
This is what i had managed to write before i got the answer, using two updates because the actual table had too many columns to use in a single query.
UPDATE D
SET D.CStock = FL.NewCStock
FROM #Data D
INNER JOIN (
SELECT DT.Prod
,DT.SDate
,SUM(IIF(RwNm = 1, DT.CStock, 0) + ISNULL(BD.CStock, 0)) OVER (
PARTITION BY DT.Prod ORDER BY DT.SDate ROWS UNBOUNDED PRECEDING
) NewCStock
FROM (
SELECT Prod
,SDate
,CStock
,ROW_NUMBER() OVER (
PARTITION BY Prod ORDER BY SDate
) AS RwNm
FROM #Data
) DT
LEFT JOIN #BackData BD ON DT.Prod = DT.Prod
AND BD.SDate = DT.SDate
) FL ON D.Prod = FL.Prod
AND D.SDate = FL.SDate
UPDATE D
SET D.OStock = PV.NewOStock
FROM #Data D
INNER JOIN (
SELECT Prod
,SDate
,ISNULL(LAG(CStock) OVER (
PARTITION BY Prod ORDER BY SDate
), CStock) AS NewOStock
FROM #Data
) PV ON D.Prod = PV.Prod
AND D.SDate = PV.SDate
You can use the following query to UPDATE:
;WITH ToUpdate AS (
SELECT d1.OStock, d1.CStock,
COALESCE(LAG(d2.CStock2) OVER (PARTITION BY d2.Prod
ORDER BY d2.SDate),
d1.OStock) AS OStock2,
d2.CStock2
FROM #Data AS d1
JOIN (
SELECT d.Prod, d.SDate, d.OStock, d.CStock,
COALESCE(t.newCStock,
LAG(t.newCStock) OVER (PARTITION BY d.Prod
ORDER BY d.SDate)) AS CStock2
FROM #Data AS d
LEFT JOIN (
SELECT bd.Prod, bd.SDate,
drn.CStock + SUM(bd.CStock) OVER (PARTITION BY bd.Prod
ORDER BY bd.SDate) AS newCStock
FROM #BackData AS bd
INNER JOIN (
SELECT Prod, CStock,
ROW_NUMBER() OVER (PARTITION BY Prod ORDER BY SDate) AS rn
FROM #Data
) AS drn ON bd.Prod = drn.Prod AND drn.rn = 1
) AS t ON t.Prod = d.Prod AND t.SDate = d.SDate
) AS d2 ON d1.Prod = d2.Prod AND d1.SDate= d2.SDate
)
UPDATE ToUpdate
SET OStock = OStock2,
CStock = CStock2
This looks awfully convoluted, but I couldn't think of anything simpler.
Demo here
You can rebuild values in #Data table with the help of recursive CTE:
;WITH cte AS (
SELECT top 1 d.Prod,
d.SDate,
d.OStock,
d.OStock + b.CStock as CStock
FROM #Data d
LEFT JOIN #BackData b
ON b.Prod = d.Prod and b.SDate = d.SDate
ORDER BY d.SDate ASC
UNION ALL
SELECT c.Prod,
DATEADD(day,1,c.SDate),
c.CStock,
c.CStock + ISNULL(b.CStock,0)
FROM cte c
INNER JOIN #Data d
ON d.Prod = c.Prod AND d.SDate = DATEADD(day,1,c.SDate)
OUTER APPLY (SELECT CStock FROM #BackData b WHERE b.Prod = d.Prod and b.SDate = d.SDate) as b
)
SELECT *
FROM cte
Output:
Prod SDate OStock CStock
-------------------- ---------- ----------- -----------
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13
To update #Data:
UPDATE d
SET OStock = c.OStock, CStock = c.CStock
FROM #Data d
INNER JOIN cte c
ON c.Prod = d.Prod AND c.SDate = d.SDate
Shouldn't the result be like :
Prod SDate OStock CStock
p1 2016-06-06 10 12
p1 2016-06-07 12 20 (#Data CStock 14 + #BakData 2 + 4)
p1 2016-06-08 20 19 (#Data CStock 13 + #BakData 2 + 4)
p1 2016-06-09 19 18 (#Data CStock 13 + #BakData 2 + 4 - 1)
p1 2016-06-10 18 14 (#Data CStock 11 + #BakData 2 + 4 - 1 -2)
This query will produce the above result
update d
set OStock = d.OStock + a.OAdj,
CStock = d.CStock + a.CAdj
from #Data d
cross apply
(
select OAdj = sum(case when Oflag = 1 then x.CStock else 0 end),
CAdj = sum(x.CStock)
from
(
select *, Oflag = case when x.SDate = d.SDate then 0 else 1 end
from #BackData x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) x
) a
Based on your expected output, it seems that you are re-calculating the daily Opening / Closing balance based on the figure from 2016-06-06
Here is a solution that will gives you your expected output.
; with
cte as
(
select Prod, SDate, OStock, CStock,
rn = row_number() over (partition by Prod order by SDate)
from #Data
),
adj as
(
select Prod, SDate, CStock
from cte
where rn = 1
union all
select Prod, SDate, CStock
from #BackData
)
update d
set OStock = coalesce(o.OStock, d.OStock),
CStock = c.CStock
from #Data d
cross apply
(
select OStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate < d.SDate
) o
cross apply
(
select CStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) c
Result :
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13
I have a table which has employee details
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 NULL
5 2 3 NULL
6 2 2 NULL
7 1 1 NULL
8 5 0 NULL
Now, I have to start wil Employee Id 2 and found all it's low level hirerachy (i.e. 2, 4, 5, 6, 8) and assign them value same as "2" (i.e. EMP).
Expected output :
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 EMP
5 2 3 EMP
6 2 2 EMP
7 1 1 NULL
8 5 0 EMP
What I am trying:
; WITH LevelHire AS
(
SELECT EmpId, ManagerId,Level
FROM EmployeeTable
WHERE EmpId =2
UNION ALL
SELECT Lh.EmpId, RC.ManagerId, Lh.Level
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.EmpId
)
SELECT * FROM LevelHire
option (maxrecursion 0)
How can I achieve the same?
you can try something like this
;WITH EmployeeTable AS
(
SELECT 1 EmpId,0 ManagerId , 5 Level ,'CEO' Value
UNION ALL SELECT 2,1, 4,'EMP'
UNION ALL SELECT 3,1, 4,'ORG'
UNION ALL SELECT 4,2, 3,NULL
UNION ALL SELECT 5,2, 3,NULL
UNION ALL SELECT 6,2, 2,NULL
UNION ALL SELECT 7,1, 1,NULL
UNION ALL SELECT 8,5, 0,NULL
),LevelHire AS
(
SELECT EmpId, ManagerId,Level,Value
FROM EmployeeTable
WHERE EmpId = 2
UNION ALL
SELECT RC.EmpId, RC.ManagerId, Lh.Level,LH.Value
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.ManagerId
)
SELECT E.EmpId, E.ManagerId,E.Level,ISNULL(E.Value ,LH.Value) Value
FROM EmployeeTable E
LEFT JOIN LevelHire LH
ON E.EmpId = LH.EmpId
Time to learn about hierarchyid. First, some code:
IF object_id('tempdb.dbo.#employees') IS NOT NULL
DROP TABLE #employees;
go
WITH Employees AS (
SELECT *
FROM ( VALUES
( 1, NULL, 5, 'CEO'),
( 2, 1, 4, 'EMP'),
( 3, 1, 4, 'ORG'),
( 4, 2, 3, NULL ),
( 5, 2, 3, NULL ),
( 6, 2, 2, NULL ),
( 7, 1, 1, NULL ),
( 8, 5, 0, NULL )
) AS x ( EmpId, ManagerId, Level, Value )
), rcte AS (
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value,
CAST('/' + CAST(e.EmpId AS VARCHAR) + '/' AS VARCHAR(MAX)) AS h
FROM Employees AS e
WHERE e.ManagerId IS NULL
UNION ALL
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
m.h + CAST(e.EmpId AS VARCHAR) + '/' AS h
FROM Employees AS e
JOIN rcte AS m
ON e.ManagerId = m.EmpId
)
SELECT rcte.EmpId ,
rcte.ManagerId ,
rcte.Level ,
rcte.Value ,
CAST(rcte.h AS HIERARCHYID) AS h
INTO #employees
FROM rcte;
GO
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
e.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE m.EmpId = 1
SELECT m.EmpId ,
m.ManagerId ,
m.Level ,
m.Value ,
m.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE e.EmpId = 8
While I needed a recursive CTE to actually establish the hierarchy, any of the actual queries of the form "who does this person report to?" and "who reports to this person?" are ultimately satisfied from the persisted hierarchy in the #employees table. The two queries at the end show how to traverse the hierarchy in either direction. This sort of thing is important if your hierarchy is large (wide, deep, or both). You do need to maintain it when the org chart changes, but that's a one-time operation. The querying of the data should be fast because the lineage is persisted with the employee record.
Incidentally, your Level column is a bit odd to me. Specifically, it seems backwards (i.e. CEO has the highest level). I say this because if/when you add another level to the org chart, you'll need to re-level everyone from the CEO down. If you have the CEO have the lowest level, you just tack that level onto the bottom and don't have to re-level anyone.
I'm having to re-write a project that was done using a combination of SQL queries and Query-of-Queries in ColdFusion. There were dozens of queries referencing the original SQL Query results set, but it wasn't abstracted to work for different events. So I would like to improve it by moving most of the counting into SQL. I got the first 6 counts they need working (not sure if in the most optimal way). But, in addition to those, I need to be able to do a break down not just on the overall date range, but also for each individual day in that date range for the unique counts.
So far the query is:
SELECT Count(CASE
WHEN type IN ( 1, 3, 4, 5, 9 ) THEN barcode
ELSE NULL
END) AS total_scans,
Count(CASE
WHEN type IN ( 2, 8 ) THEN barcode
ELSE NULL
END) AS total_creds,
Count(barcode) AS total_scans,
Count(DISTINCT CASE
WHEN type IN ( 1, 3, 4, 5, 9 ) THEN barcode
ELSE NULL
END) AS unique_scans,
Count(DISTINCT CASE
WHEN type IN ( 2, 8 ) THEN barcode
ELSE NULL
END) AS unique_creds,
Count(DISTINCT barcode) AS unique_scans
FROM (SELECT c.id,
a.barcode,
d.type,
c.location,
Datepart(mm, a.scan_time) AS scan_month,
Datepart(dd, a.scan_time) AS scan_day,
Datepart(hour, a.scan_time) AS scan_hour,
Datepart(minute, a.scan_time) AS scan_min
FROM [scan_11pc_gate_entries] AS a
INNER JOIN scan_units AS b
ON a.scanner = b.id
INNER JOIN scan_gates AS c
ON b.gate = c.id
INNER JOIN [scan_11pc_gate_allbarcodes] AS d
ON a.barcode = d.barcode
WHERE ( c.id IN (SELECT id
FROM scan_gates
WHERE ( event_id = 21 )) )
AND ( a.valid IN ( 1, 8 ) )
AND a.scan_time >= '20110808'
AND a.scan_time <= '20110814') data
Well I see plenty of other room for improvement / optimization here, but to satisfy the immediate requirement:
SELECT d, Count(CASE WHEN type IN ( 1, 3, 4, 5, 9 ) THEN barcode
ELSE NULL END) AS total_scans,
Count(CASE WHEN type IN ( 2, 8 ) THEN barcode
ELSE NULL END) AS total_creds,
Count(barcode) AS total_scans,
Count(DISTINCT CASE WHEN type IN ( 1, 3, 4, 5, 9 ) THEN barcode
ELSE NULL END) AS unique_scans,
Count(DISTINCT CASE WHEN type IN ( 2, 8 ) THEN barcode
ELSE NULL END) AS unique_creds,
Count(DISTINCT barcode) AS unique_scans
FROM (SELECT c.id, -- is this column necessary?
a.barcode,
d.type,
c.location, -- is this column necessary?
d = DATEADD(DAY, DATEDIFF(DAY, 0, a.scan_time), 0)
FROM [scan_11pc_gate_entries] AS a
INNER JOIN scan_units AS b
ON a.scanner = b.id
INNER JOIN scan_gates AS c
ON b.gate = c.id
AND c.event_id = 21 -- join criteria,
-- shouldn't be an extra IN clause
INNER JOIN [scan_11pc_gate_allbarcodes] AS d
ON a.barcode = d.barcode
WHERE ( a.valid IN ( 1, 8 ) )
AND a.scan_time >= '20110808'
AND a.scan_time <= '20110814') data
GROUP BY d
ORDER BY d;
Note that >= and <= is the same as BETWEEN, and unless scan_time is a DATE column (or guaranteed to always be at midnight), the approach you're using isn't safe. Better to say:
AND a.scan_time >= '20110808'
AND a.scan_time < '20110815'
More info here:
What do BETWEEN and the devil have in common?
EDIT
Understanding that this is based on the information I've pieced together from comments and questions, which is far from a complete picture of your environment and workload, an indexed view you may find useful would be:
CREATE VIEW dbo.myview
WITH SCHEMABINDING
AS
SELECT c.event_id,
a.barcode,
[type] = CASE WHEN d.[type] IN (2,8) THEN 'c' ELSE 's' END,
scan_date = DATEADD(DAY, DATEDIFF(DAY, 0, a.scan_time), 0),
total = COUNT_BIG(*)
FROM dbo.scan_11pc_gate_entries AS a
INNER JOIN dbo.can_units AS b
ON a.scanner = b.id
INNER JOIN dbo.scan_gates AS c
ON b.gate = c.id
INNER JOIN dbo.scan_11pc_gate_allbarcodes AS d
ON a.barcode = d.barcode
WHERE (a.valid IN (1,8))
AND d.[type] IN (2,3,4,5,8,9)
GROUP BY
c.event_id,
a.barcode,
CASE WHEN d.[type] IN (2,8) THEN 'c' ELSE 's' END,
DATEADD(DAY, DATEDIFF(DAY, 0, a.scan_time), 0);
GO
CREATE UNIQUE CLUSTERED INDEX x
ON dbo.myview(event_id, barcode, [type], scan_date);
Now you can write a query that does something like this:
SELECT [date] = CONVERT(CHAR(8), scan_date, 112),
SUM(CASE WHEN [type] = 's' THEN total ELSE 0 END) AS total_scans,
SUM(CASE WHEN [type] = 'c' THEN total ELSE 0 END) AS total_creds,
COUNT(CASE WHEN [type] = 's' THEN 1 END) AS unique_scans,
COUNT(CASE WHEN [type] = 'c' THEN 1 END) AS unique_creds
FROM dbo.myview WITH (NOEXPAND) -- in case STD Edition
WHERE event_id = 21
AND scan_date BETWEEN '20110808' AND '20110814'
GROUP BY scan_date
UNION ALL
SELECT [date] = 'weekly',
SUM(CASE WHEN [type] = 's' THEN total ELSE 0 END) AS total_scans,
SUM(CASE WHEN [type] = 'c' THEN total ELSE 0 END) AS total_creds,
COUNT(DISTINCT CASE WHEN [type] = 's' THEN barcode END) AS unique_scans,
COUNT(DISTINCT CASE WHEN [type] = 'c' THEN barcode END) AS unique_creds
FROM dbo.myview WITH (NOEXPAND) -- in case STD Edition
WHERE event_id = 21
AND scan_date BETWEEN '20110808' AND '20110814'
ORDER BY [date];
This is all completely untested as your schema is a little bit cumbersome to try and create a complete repro of your system, but hopefully this gives you a general idea to work from...