Counts on Date Range and Individual Days - sql-server

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...

Related

Output of teams with more than a half wins (Example: 3/5)

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

TSQL group by generate duplicate row

I'm trying to extract all prices and taxes by dates range (not necessary the same date) in 2 column and group by ID.
Because I need to group by 2 others columns because T-SQL need that:
Column '...' is invalid in the select list because
it is not contained in either an aggregate function or the GROUP BY clause.
I have a duplicate user/ID sometimes. ( don't know why by the way..)
I have this SQL:
WITH myQuery AS
(
Select
c.name, c.id,
CASE
WHEN g.dateCreated BETWEEN CAST ('2016-06-01 00:00:00.000' AS DATETIME)
AND CAST ('2017-05-31 23:59:59.000' AS DATETIME)
THEN SUM(CAST(g.price AS decimal(20,2) ))
ELSE 0
END AS TOTAL_PRICE,
CASE
WHEN g.dateCreated BETWEEN CAST ('2016-01-01 00:00:00.000' AS DATETIME)
AND CAST ('2016-12-31 23:59:59.000' AS DATETIME)
THEN SUM(CAST(g.tax AS decimal(20,2) ))
ELSE 0
END AS TOTAL_TAX
FROM customers c
inner join goodies g
ON c.id = g.customer_id
GROUP BY c.name, c.id, g.dateCreated
)
SELECT count(*) FROM myQuery
I got 5203 rows. I have only 5031 users.
When I Analyse my data, I have some duplicate data.
Example:
Alex, 12, 0.00, 0.00
Alex, 12, 100.00, 14.55
Nancy, 4, 0.00, 0.00
Arthur, 97, 48.14, 09.17
I tried to group by only by id but it seem that I can't do that.
Why I have a duplicate data and How to prevent that and ensure that I have 1 row by USER even if they don't buy goodies?
Correcting your conditional aggregation and removing dateCreated from the group by:
with myQuery as (
select
c.name
, c.id
, total_price = sum(case
when g.dateCreated >= '20160601' and g.dateCreated < '20170601'
then cast(g.price as decimal(20,2))
else 0
end)
, total_tax = sum(case
when g.dateCreated >= '20160101' and g.dateCreated < '20170101'
then cast(g.tax as decimal(20,2))
else 0
end)
from customers c
left join goodies g
on c.id = g.customer_id
group by
c.name
, c.id
--, g.dateCreated
)
select count(*) from myQuery;
Changing the inner join to a left join will return customers even if they have no corresponding row in goodies.
I also changed your date range code to be more explicit about what is included.
Reference:
Bad habits to kick : mis-handling date / range queries - Aaron Bertrand
What do between and the devil have in common? - Aaron Bertrand

how to join table group by count

I have query_1:
select id,
count(case when no01 ='B' then 1 END) +
count(case when no02='B' then 1 END) +
count(case when no03='B' then 1 END) as Count_All
From tabel_a
where date ='20150201'
group by id
and also I have query_2:
select id, COUNT (*) from tabel_b
where ids <> 'T' and idt ='C'
group by id
How can I join query_1 with query_2 via id?
You could try using sub selects. Something like
SELECT *
FROM (
select id,
count(case when no01 ='B' then 1 END) + count(case when no02='B' then 1 END) + count(case when no03='B' then 1 END) as Count_All
From tabel_a
where date ='20150201'
group by id
) a INNER JOIN
(
select id,
COUNT (*) cnt
from tabel_b
where ids <> 'T'
and idt ='C'
group by id
) b ON a.ID = b.ID
Seeing as you specified SQL Server, you could also make use of a Common Table Expression.
Something like
;WITH a AS (
select id,
count(case when no01 ='B' then 1 END) + count(case when no02='B' then 1 END) + count(case when no03='B' then 1 END) as Count_All
From tabel_a
where date ='20150201'
group by id
)
, b as (
select id,
COUNT (*) cnt
from tabel_b
where ids <> 'T'
and idt ='C'
group by id
)
SELECT *
FROm a INNER JOIN
b ON a.ID = b.ID

ORA-00937: Not a single-group group function - Query error

Error: ORA-00937: Not a single-group group function
Query:
select count(*) todas,
sum(case when i.prioridade = 1 then 1 else 0 end) urgente,
sum(case when i.prioridade = 2 then 1 else 0 end) alta,
sum(case when i.prioridade = 3 then 1 else 0 end) normal,
sum(case when i.prioridade = 4 then 1 else 0 end) baixa,
(select count(*)
from GMITEMOS i
inner join GMCTLSLA c on c.os = i.cd_numero_os and c.item = i.item
where i.situacao in ('A', 'I', 'P')
and c.ordem = 99999
) naoAvaliados,
sum(case when i.situacao = 'P' then 1 else 0 end) pendentes,
sum(case when i.situacao = 'A' or i.situacao = 'I' then 1 else 0 end) iniciados
from GMITEMOS i
where i.situacao in ('A', 'I', 'P')
and exists (select 1
from GMCTLSLA c
where c.os = i.cd_numero_os
and c.item = i.item)
The error is ocurring here:
(select count(*)
from GMITEMOS i
inner join GMCTLSLA c on c.os = i.cd_numero_os and c.item = i.item
where i.situacao in ('A', 'I', 'P')
and c.ordem = 99999
) naoAvaliados
Can someone tell why is it happening?
You may have fixed it with max but that's not why it's happening and is a little bit hacky. Your problem is that your sub-query, which translates into a single column is not an aggregate query, min, max, sum etc and so needs to be included in a group by clause. You fixed this by wrapping it in max as the maximum of a single value will always be constant.
However, as your sub-query is, itself, an analytic query and will only ever return one row the obvious thing to do is to use a cartesian join to add it to your query. In the explicit join syntax this is known as the cross join.
select count(*) todas
, sum(case when i.prioridade = 1 then 1 else 0 end) urgente
, sum(case when i.prioridade = 2 then 1 else 0 end) alta
, sum(case when i.prioridade = 3 then 1 else 0 end) normal
, sum(case when i.prioridade = 4 then 1 else 0 end) baixa
, naoAvaliados
, sum(case when i.situacao = 'P' then 1 else 0 end) pendentes
, sum(case when i.situacao = 'A' or i.situacao = 'I' then 1 else 0 end) iniciados
from GMITEMOS i
cross join (select count(*) as naoAvaliados
from GMITEMOS j
inner join GMCTLSLA k
on k.os = j.cd_numero_os
and k.item = j.item
where j.situacao in ('A', 'I', 'P')
and k.ordem = 99999
)
where i.situacao in ('A', 'I', 'P')
and exists (select 1
from GMCTLSLA c
where c.os = i.cd_numero_os
and c.item = i.item
)
The cartesian join has a bad reputation as it multiples the number of rows on one side of the join by the number of rows on the other. It does, however, have it's uses, especially in this sort of case.
It's happening because the subquery itself is a scalar result, not a group function. As you have apparently found, you can fix it by substituting a group function that yields an equivalent result to your subquery.
In merge statement, if you are getting this error than simple use the group by and it will resolve the issue.
merge into table1 tb1
using
(select a.id,a.ac_no,sum(a.qy) as qyt,sum(a.amt) as sum_amt from
table2 a, table1 b
where a.id=b.id
and a.id = '1234'
and a.date = '08Oct2014'
and a.ac_no in (123, 234, 345)
and a.ac_no = b.ac_no
group by a.ac_no,a.id
)qry
on (qry.id=tb1.id and qry.ac_no=tb1.ac_no )
when matched then
update set qy=qry.qy,amt = qry.sum_amt;

SQL Server - How to display master details data in columns

I have two tables, to be concise let’s call them TableA and TableB. This is the schema:
TableA
ID – int
Name varchar(50)
TableB
ID – int
TableA_Fk – int
Value varchar(50)
Each record in table A can have at most 9 records in table B. I want to be able to retrieve the data in a columnar form:
TableA-Name, TableB-Value1, … TableB-Value9
Is this possible using queries? Thanks!
You could do something like:
SELECT rank() OVER (ORDER BY tableA_FK) as rank, tableA_fk, value
INTO #temp
FROM TableB b
ORDER BY rank
SELECT a.Name,
CASE WHEN t.rank = 1 THEN t.Value ELSE NULL END AS TableB-Value1,
CASE WHEN t.rank = 2 THEN t.Value ELSE NULL END AS TableB-Value2,
CASE WHEN t.rank = 3 THEN t.Value ELSE NULL END AS TableB-Value3,
.... (etc.)
FROM TableA a
INNER JOIN #temp t ON a.Id = t.tableA_fk
You need Sql Server 2005 or up.
Sorry, but I don't have Sql Server (or the time) to test this well. Hope this gives you an idea and helps.
You will require a LEFT JOIN and a PIVOT table
This should do it, in addition to be DBRM independant.
SELECT A.Name
, SUM(CASE WHEN B.Value = 1 THEN 1 ELSE NULL END) AS B_Value_1
, SUM(CASE WHEN B.Value = 2 THEN 2 ELSE NULL END) AS B_Value_2
, SUM(CASE WHEN B.Value = 3 THEN 3 ELSE NULL END) AS B_Value_3
, SUM(CASE WHEN B.Value = 4 THEN 4 ELSE NULL END) AS B_Value_4
, SUM(CASE WHEN B.Value = 5 THEN 5 ELSE NULL END) AS B_Value_5
, SUM(CASE WHEN B.Value = 6 THEN 6 ELSE NULL END) AS B_Value_6
, SUM(CASE WHEN B.Value = 7 THEN 7 ELSE NULL END) AS B_Value_7
, SUM(CASE WHEN B.Value = 8 THEN 8 ELSE NULL END) AS B_Value_8
, SUM(CASE WHEN B.Value = 9 THEN 9 ELSE NULL END) AS B_Value_9
FROM A
INNER JOIN B ON B.TableA_FK = A.ID
GROUP BY A.Name
ORDER BY A.Name

Resources