Optimizing sql, existing query using two full table scans CTE's - sql

I am looking for an improvement to the below query, any input gratefully received
with cteA as (
select name, count(1) as "A"
from mytable
where y="A"
group by name
),
cteB as (
select name, count(1) as "B"
from mytable
where y="B"
group by name
)
SELECT cteA.name as 'name',
cteA.A as 'count x when A',
isnull(cteB.B as 'count x when B',0)
FROM
cteOne
LEFT OUTER JOIN
cteTwo
on cteA.Name = cteB.Name
order by 1

select name,
sum(case when y='A' then 1 else 0 end) as [count x when A],
sum(case when y='B' then 1 else 0 end) as [count x when B]
from mytable
where y in ('A','B')
group by name
order by name

The simplest answer is:
select name, y, count(*)
from mytable
where y in ('A','B')
group by name, y
You can use a PIVOT to move Y row values into columns, if you need them in columns.

Related

SQL Function for updating column with values

Those who have helped me before, i tend to use SAS9.4 a lot for my day to day work, however there are times when i need to use SQL Server
There is a output table i have with 2 variables (attached output.csv)
output table
ID, GROUP, DATE
The table has 830 rows:
330 have a "C" group
150 have a "A" group
50 have a "B" group
the remaining 300 have group as "TEMP"
within SQL i do not now how to programatically work out the total volume of A+B+C. The aim is to update "TEMP" column to ensure there is an Equal amount of "A" and "B" totalling 250 of each (the remainder of the total count)
so the table totals
330 have a "C" group
250 have a "A" group
250 have a "B" group
You want to proportion the "temp" to get equal amounts of "A" and "B".
So, the idea is to count up everything in A, B, and Temp and divide by 2. That is the final group size. Then you can use arithmetic to allocate the rows in Temp to the two groups:
select t.*,
(case when seqnum + a_cnt <= final_group_size then 'A' else 'B' end) as allocated_group
from (select t.*, row_number() over (order by newid()) as seqnum
from t
where group = 'Temp'
) t cross join
(select (cnt_a + cnt_b + cnt_temp) / 2 as final_group_size,
g.*
from (select sum(case when group = 'A' then 1 else 0 end) as cnt_a,
sum(case when group = 'B' then 1 else 0 end) as cnt_b,
sum(case when group = 'Temp' then 1 else 0 end) as cnt_temp
from t
) g
) g
SQL Server makes it easy to put this into an update:
with toupdate as (
select t.*,
(case when seqnum + a_cnt <= final_group_size then 'A' else 'B' end) as allocated_group
from (select t.*, row_number() over (order by newid()) as seqnum
from t
where group = 'Temp'
) t cross join
(select (cnt_a + cnt_b + cnt_temp) / 2 as final_group_size,
g.*
from (select sum(case when group = 'A' then 1 else 0 end) as cnt_a,
sum(case when group = 'B' then 1 else 0 end) as cnt_b,
sum(case when group = 'Temp' then 1 else 0 end) as cnt_temp
from t
) g
) g
)
update toupdate
set group = allocated_group;
I'd go with a top 250 update style approach
update top (250) [TableName] set Group = 'A' where exists (Select * from [TableName] t2 where t2.id = [TableName].id order by newid()) and Group = 'Temp'
update top (250) [TableName] set Group = 'B' where exists (Select * from [TableName] t2 where t2.id = [TableName].id order by newid()) and Group = 'Temp'

how to write aggregate conditional SQL query to get individual column counts and total counts by passing date arguments

I did some thing like this:
with q1 as
(
select x, count(*) as A from T1 where .. condition group by x
),
q2 as ( select x, count(*) AS B from T2 where .. condition group by x),
q3 as ( select x, count(*) AS C from T3 where .. condition group by x),
Q4 AS ( select x, count(*) AS D from T4 where .. condition group by x)
SELECT Q1.X, Q1.A, Q2.B, Q3.C, Q4.D FROM Q1,Q2,Q3,Q4
I only get blank results.. also i'm passing date as input arguments for each select.
column x exists in 4 different tables. All i have to do is show the count of x from each table for a particular date range.
You can use union all and then conditional aggregation as following:
Select x,
Coalesce(sum(case when tab ='Q1' then cnt end),0) As a,
Coalesce(Sum(case when tab ='Q2' then cnt end),0) As b,
Coalesce(Sum(case when tab ='Q3' then cnt end),0) As c,
Coalesce(Sum(case when tab ='Q4' then cnt end),0) As d
From
(Select 'Q1' as tab, x, count(*) cnt from t1 Where ... group by x
Union all
Select 'Q2' as tab, x, count(*) from t2 Where ... group by x
Union all
Select 'Q3' as tab, x, count(*) from t3 Where ... group by x
Union all
Select 'Q4' as tab, x, count(*) from t4 Where ... group by x)
Group by x;
Cheers!!
Conditional Aggregation can be used through JOIN Clauses by x columns which are common in name :
select t1.x,
count(case when t1.y=1 then 1 end ) as A,
count(case when t2.z=0 then 1 end ) as B,
count(case when t3.a=0 then 1 end ) as C,
count(case when t4.b=0 then 1 end ) as D
from t1
join t2 on t2.x = t1.x
join t3 on t3.x = t1.x
join t4 on t4.x = t1.x
group by t1.x
Conditions in WHERE Clauses are assumed to be t1.y=1, t2.z, t3.a, t4.b respectively.

Need query for following scenario

I have a result like followed by
ID Name Status
1 A Y
2 A N
3 B Y
4 B Y
5 C N
in this case if status of Name A have two status then I need a select query for following outout
ID Name Status
1 A N
2 A N
3 B Y
4 B Y
5 C N
And sorry, I dont know how ask question for this scenario..
please provide the solution thanks in advance
This following script will select data as per your requirement-
SELECT yt.ID,
yt.Name,
CASE WHEN A.N>1 THEN 'N' ELSE Status END as Status
FROM your_table yt
LEFT JOIN (
SELECT Name,
COUNT(DISTINCT Status) as N
FROM your_table
GROUP BY Name
HAVING COUNT(DISTINCT Status) >1
) A on yt.Name = A.Name
Using LEFT JOIN with COALESCE in the SELECT will work in this case.
Demo with sample data:
DECLARE #TestTable TABLE (ID INT, [Name] VARCHAR (1), [Status] VARCHAR (1));
INSERT INTO #TestTable(ID, [Name], [Status]) VALUES
(1, 'A', 'Y'),
(2, 'A', 'N'),
(3, 'B', 'Y'),
(4, 'B', 'Y'),
(5, 'C', 'N');
SELECT T.ID,
COALESCE(Q.[Name], T.[Name]) AS [Name],
COALESCE(Q.[Status], T.[Status]) AS [Status]
FROM #TestTable T
LEFT JOIN (
SELECT DISTINCT [Name], 'N' AS [Status]
FROM #TestTable
WHERE [Status] = 'N'
) AS Q ON Q.[Name] = T.[Name]
Output:
ID Name Status
1 A N
2 A N
3 B Y
4 B Y
5 C N
Use a RANK in separate query to get the status for the latest id and left join on name against that query to use latest status for all rows for a name
SELECT a.id, a.name, b.status
FROM dbo.Table_3 a
LEFT JOIN (SELECT id, name, status, RANK() OVER (Partition BY name ORDER BY id desc) AS rnk
FROM dbo.table_3) b ON a.name = b.name AND b.rnk = 1
You can use a Windowed function so that you don't need to scan the table twice:
SELECT ID,
[Name],
CASE COUNT(CASE WHEN [Status] = 'N' THEN 1 END) OVER (PARTITION BY [Name]) WHEN 0 THEN [Status] ELSE 'N' END AS [Status]
FROM (VALUES(1,'A','Y'),
(2,'A','N'),
(3,'B','Y'),
(4,'B','Y'),
(5,'C','N')) V(ID, [Name], [Status]);
In below query the derived table a pulls the distinct record that has 'N' . Then joined it with main table and using case statement pulled the status.
Using Derived Table
select *,
case when a.name is not null then 'N' else #temp.status end [status]
from #temp
Left join (select distinct name from #temp where status ='N' )a on a.name = #temp.name
Using Case Statement
select *,
case (select count(*) from #temp t where status='N' and t.Name = #temp.Name)
when 1 then 'N'
else status
end [status]
from #temp
OR
select *,
case when (select count(*) from #temp t where status='N' and t.Name = #temp.Name) > 0 then 'N'
else status
end [status]
from #temp
Output
ID Name Status name status
1 A Y A N
2 A N A N
3 B Y NULL Y
4 B Y NULL Y
5 C N C N
For your particular example, you can just use a window function:
select ID, Name,
min(Status) over (partition by name) as status
from t;
This works because 'N' is less than 'Y', so the MIN() will return 'N' if any values are 'N'.

Joining two different queries under one answer

I have two different queries that have produced the correct result, but I would like to have them produce the answer out in one table. How do I do that?
Here is my code:
SELECT count(distinct ID) as NoOfEmployees
FROM Table_Name
WHERE date<= '2012-05-31';
select count(subA.ID) as EmployeesChanged from (
SELECT A.ID
FROM Table_Name A
WHERE A.date < '2012-06-01'
GROUP BY 1
HAVING COUNT(A.Service_type) > 1 ) subA
Currently I have the following output:
Number of Employees
x
Employees Changed
x
How do I make it
Number of Employees | Employees Changed | (Number of employees - number changed)
x | x | x
I don't know what database do you use. But for some databases you can try:
select q1.Value, q2.Value, q1.Value - q2.Value from
(SELECT count(distinct ID) as Value FROM Table_Name
WHERE date<= '2012-05-31') q1,
(select count(subA.ID) as Value from
( SELECT A.ID FROM Table_Name A
WHERE A.date < '2012-06-01' GROUP BY 1
HAVING COUNT(A.Service_type) > 1 ) subA) q2
If date<= '2012-05-31' is the same as A.date < '2012-06-01' ?
SELECT COUNT(1) AS NoOfEmployees,
SUM(CASE WHEN STCount > 0 then 1 else 0 end) as HasChange,
SUM(CASE WHEN STCount = 0 then 1 else 0 end) as NoChange
FROM
(SELECT ID,
COUNT(A.Service_type) STCount
FROM Table_Name
WHERE date<= '2012-05-31'
GROUP BY ID) AS Data
You can use CROSS JOIN:
SELECT a.*, b.*, a.NoOfEmployees - b.EmployeesChanged
FROM
(
SELECT count(distinct ID) as NoOfEmployees
FROM Table_Name
WHERE date<= '2012-05-31'
) a
CROSS JOIN
(
SELECT count(subA.ID) as EmployeesChanged
FROM
(
SELECT A.ID
FROM Table_Name A
WHERE A.date < '2012-06-01'
GROUP BY 1
HAVING COUNT(A.Service_type) > 1
) subA
) b
Edit:
You might be able to greatly optimize your query by using conditional aggregation instead of executing two separate queries:
SELECT a.NoOfEmployees, a.EmployeesChanged, a.NoOfEmployees - a.EmployeesChanged
FROM
(
SELECT
COUNT(DISTINCT CASE WHEN date <= '2012-05-31' THEN ID END) as NoOfEmployees,
COUNT(DISTINCT CASE WHEN date < '2012-06-01' AND COUNT(Service_type) > 1 THEN ID END) AS EmployeesChanged
FROM Table_Name
GROUP BY ID
) a

Multiple where clauses in one row sql

I want to take the below statement and fuse it into one query.
SELECT COUNT(*) AS count1 WHERE Month='11' AND Flag = 1
SELECT COUNT(*) AS count2 WHERE Month='11' AND Flag = 2
SELECT COUNT(*) AS count1 WHERE Month='12' AND Flag = 1
SELECT COUNT(*) AS count2 WHERE Month='12' AND Flag = 2
I want this to display as one query with columns count1 and count2 and rows month 11 and month 12.
Is there a syntax for this?
You can combine SUM and CASE to get various counts in one go:
SELECT
Month,
SUM(CASE WHEN Flag=1 THEN 1 ELSE 0 END) as count1,
SUM(CASE WHEN Flag=2 THEN 1 ELSE 0 END) as count2
from
...
WHERE Month in ('11','12')
GROUP BY
Month /* Other columns? */
With two columns only, it can be something like this:
select
(SELECT COUNT(*) FROM tablename WHERE Month='11' AND Flag = 1) as 'count1'
(SELECT COUNT(*) FROM tablename WHERE Month='11' AND Flag = 2) as 'count2'
UNION ALL
select
(SELECT COUNT(*) FROM tablename WHERE Month='12' AND Flag = 1),
(SELECT COUNT(*) FROM tablename WHERE Month='12' AND Flag = 2)
Replace tablename with the name of your table.
How about this:
select
month, flag, count(*)
from
table
where
month in ('11', '12') and
flag in (1, 2)
group by
month, flag;