Count Based on Age SQL - sql

I have the following T-SQL to calculate ages:
SELECT
Member_ID,
DATEDIFF(YY,DOB,GETDATE()) -
CASE
WHEN DATEADD(YY,DATEDIFF(YY,DOB,GETDATE()),DOB) > GETDATE() THEN 1
ELSE 0
END AS Age in Years
FROM MEMBER
WHERE YEAR(registration_date ) >= 2012
How do I count the number of member IDs for each age in years?

I would do this using a subquery or CTE. Much easier to follow:
SELECT AgeInYears, COUNT(*)
FROM (SELECT Member_ID,
(DATEDIFF(YY,DOB,GETDATE()) -
CASE WHEN DATEADD(YY,DATEDIFF(YY,DOB,GETDATE()),DOB) > GETDATE() THEN 1
ELSE 0
END) AS AgeinYears
FROM MEMBER
WHERE YEAR(registration_date ) >= 2012
) m
GROUP BY AgeInYears
ORDER BY 1;

Something like this:
SELECT
-- Member_ID, commented out
DATEDIFF(YY,DOB,GETDATE()) -
CASE
WHEN DATEADD(YY,DATEDIFF(YY,DOB,GETDATE()),DOB) > GETDATE() THEN 1
ELSE 0
END AS Age in Years
, count(member_id) membersWithThisAge
FROM MEMBER
WHERE YEAR(registration_date ) >= 2012
group by DATEDIFF(YY,DOB,GETDATE()) -
CASE
WHEN DATEADD(YY,DATEDIFF(YY,DOB,GETDATE()),DOB) > GETDATE() THEN 1
ELSE 0
END
The member_id can't be included in the select clause. If it were, then you would get a count of one age for each member.

Related

How to improve slow sql query with aggregate functions

I want to show top ten customers,sales,margin where customers is registred during this accounting year. The query takes about 65seconds to run and it is not accepted :-(
As you may see i am not good at sql and will be very happy for help to improve the query.
SELECT Top 10
AcTr.R3, Actor.Nm,
SUM(CASE WHEN AcTr.AcNo<='3999' THEN AcAm*-1 ELSE 0 END) AS Sales ,
SUM(AcAm*-1) AS TB
FROM AcTr, Actor
WHERE (Actor.CustNo = AcTr.R3) AND
(Actor.CustNo <> '0') AND
(Actor.CreDt >= '20180901') AND
(Actor.CreDt <= '20190430') AND
AcTr.AcYr = '2018' AND
AcTr.AcPr <= '8' AND
AcTr.AcNo>='3000' AND
AcTr.AcNo <= '4999'
GROUP BY AcTr.R3, Actor.Nm
ORDER BY Sales DESC
Welcome to the community. You have a good start, but future, it is more helpful if you can provide (as commented), the CREATE table declarations so users know the actual data types. Not always required, but helps.
As for your query layout, it is more common to show the JOIN syntax instead of WHERE showing relations between tables, but that comes in time and practice.
Indexes help and should be based on a combination of both WHERE/JOIN criteria AND Grouping fields. Also, if fields are numeric, then do not 'quote' them, just leave as numbers. For example, your AcYr, AcPr, AcNo. I would think that an account number really would be a string value vs number for accounting purposes.
I would suggest the following indexes on your tables
Table Index
Actr ( AcYr, AcPr, AcNo, R3 )
Actor ( CustNo, CreDt )
The Actr table I have the filtering criteria first and the R3 last to help optimize the GROUP BY. The Actor table by the customer number, then the CreDt (Create date??), and is it really a string, or is it a date field? If so, the date criteria would be something like '2018-09-01' and '2019-04-30'
select TOP 10
Actor.Nm,
PreSum.Sales,
PreSm.TB
from
( select
R3,
SUM(CASE WHEN AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales,
SUM( AcAm * -1) AS TB
from
Actr
where
AcTr.AcYr = 2018
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC
Per latest inquiry / comment, wanting by year comparison and getting rid of the top 10 performers per a given time period.
select
Actor.Nm,
PreSum.Sales2018,
PreSum.Sales2019,
PreSum.TB2018,
PreSum.TB2019
from
( select
AcTr.R3,
SUM(CASE WHEN AcTr.AcYr = 2018
AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2018,
SUM(CASE WHEN AcTr.AcYr = 2019 AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2019,
SUM( CASE WHEN AcTr.AcYr = 2018
THEN AcAm * -1 else 0 end ) AS TB2018
SUM( CASE WHEN AcTr.AcYr = 2019
THEN AcAm * -1 else 0 end ) AS TB2019
from
Actr
where
AcTr.AcYr IN ( 2018, 2019 )
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC

How to I create a new column in sql using conditional clause?

This code worked until I introduced case clause. All I want to do it to make a new column whose value is 1 is prediciton >0 else O.
SELECT p.cohort_name, DATEPART(Month, date)as mm, AVG(prediction)
CASE WHEN ((p.prediction) < 0 THEN 1 else 0 END) as Z
FROM
rates.rates_cohort_predictions p
Group by 1,2
order by p.cohort_name, DATEPART(Month, date)
As mvp stated, you are missing a select statement delimiter
This is the correct SQL statement
SELECT p.cohort_name,
DATEPART(Month, date) AS mm,
AVG(prediction),
CASE WHEN (
(p.prediction) < 0
THEN 1
ELSE 0
END) AS Z
FROM rates.rates_cohort_predictions AS p
GROUP BY 1,2
ORDER BY p.cohort_name, DATEPART(Month, date)

How to return count in 2 columns?

I have this query. It should return Count for both AWARDED (1) and NOT AWARDED(0) works from works table.
Select Count(w.WorkID)as Total, w.IsAwarded, org.OrganizationName
From Works w
Inner Join MC_MemberShip.Membership.Organization org
ON org.OrganizationID= w.Organization_ID
where Convert(varchar(11), w.OpeningDate) >= Convert(varchar(11), #FromDate)
and Convert(varchar(11), w.OpeningDate) < DATEADD(day, 1, Convert(varchar(11), #ToDate))
and w.IsActive=1 and
ISNULL(w.IsAwarded,0)= 0 and w.Organization_ID= case when #OrgID= -1 then w.Organization_ID else #OrgID end
group by org.OrganizationName, w.IsAwarded
Now this query returns Total count for NOT AWARDED i.e. 0 only but i want to return count for AWARDED too in same query.
Organization TotalAwardedWorks TotalNotAwardedWorks
Town 1 1 2
Town 2 44 33
Your query should look something like this:
select org.OrganizationName,
Count(*) as Total,
sum(case when w.IsAwarded = 0 or w.IsAwarded is null then 1 else 0 end) as TotalNotAward,
sum(case when w.IsAwarded = 1 then 0 else 1 end) as TotalAward
from Works w Inner Join
MC_MemberShip.Membership.Organization org
on org.OrganizationID = w.Organization_ID
where w.OpeningDate >= #FromDate and
w.OpeningDate < dateadd(day, 1, #ToDate) and
w.IsActive = 1 and
(w.Organization_ID = #OrgId or #OrgID= -1)
group by org.OrganizationName;
Notes:
Do not convert dates to strings to perform comparisons. That is just perverse.
Generally, the use of case in the where clause is discouraged. The logic is more clearly represented using or.
You can get what you want by using case to put conditions in the aggregation functions.

SQL Efficiency on Date Range or Separate Tables

I'm calculating historical amount from a table in years(ex. 2015-2016, 2014-2015, etc.) I would like to seek expertise if its more efficient to do it in one batch or repeat the query multiple times filtered by the date required.
Thanks in advance!
OPTION 1:
select
id,
sum(case when year(getdate()) - year(txndate) between 5 and 6 then amt else 0 end) as amt_6_5,
...
sum(case when year(getdate()) - year(txndate) between 0 and 1 then amt else 0 end) as amt_1_0,
from
mytable
group by
id
OPTION 2:
select
id, sum(amt) as amt_6_5
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 5 and 6
...
select
id, sum(amt) as amt_1_0
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 0 and 1
1.
Unless you have resources issues I would go with the CASE version.
Although it has no impact on the results, filtering on the requested period in the WHERE clause might have a significant performance advantage.
2. Your period definition creates overlapping.
select id
,sum(case when year(getdate()) - year(txndate) = 6 then amt else 0 end) as amt_6
-- ...
,sum(case when year(getdate()) - year(txndate) = 0 then amt else 0 end) as amt_0
where txndate >= dateadd(year, datediff(year,0, getDate())-6, 0)
from mytable
group by id
This may be help you,
WITH CTE
AS
(
SELECT id,
(CASE WHEN year(getdate()) - year(txndate) BETWEEN 5 AND 6 THEN 'year_5-6'
WHEN year(getdate()) - year(txndate) BETWEEN 4 AND 5 THEN 'year_4-5'
...
END) AS my_year,
amt
FROM mytable
)
SELECT id,my_year,sum(amt)
FROM CTE
GROUP BY id,my_year
Here, inside the CTE, just assigned a proper year_tag for each records (based on your conditions), after that select a summary for the CTE grouped by that year_tag.

counting events over flexible ranges

I am trying to count events (which are rows in the event_table) in the year before and the year after a particular target date for each person. For example, say I have a person 100 and target date is 10/01/2012. I would like to count events in 9/30/2011-9/30/2012 and in 10/02/2012-9/30/2013.
My query looks like:
select *
from (
select id, target_date
from subsample_table
) as i
left join (
select id, event_date, count(*) as N
, case when event_date between target_date-365 and target_date-1 then 0
when event_date between target_date+1 and target_date+365 then 1
else 2 end as after
from event_table
group by id, target_date, period
) as h
on i.id = h.id
and i.target_date = h.event_date
The output should look something like:
id target_date after N
100 10/01/2012 0 1000
100 10/01/2012 1 0
It's possible that some people do not have any events in the before or after periods (or both), and it would be nice to have zeros in that case. I don't care about the events outside the 730 days.
Any suggestions would be greatly appreciated.
I think the following may approach what you are trying to accomplish.
select id
, target_date
, event_date
, count(*) as N
, SUM(case when event_date between target_date-365 and target_date-1
then 1
else 0
end) AS Prior_
, SUM(case when event_date between target_date+1 and target_date+365
then 1
else 0
end) as After_
from subsample_table i
left join
event_table h
on i.id = h.id
and i.target_date = h.event_date
group by id, target_date, period
This is a generic answer. I don't know what date functions teradata has, so I will use sql server syntax.
select id, target_date, sum(before) before, sum(after) after, sum(righton) righton
from yourtable t
join (
select id, target_date td
, case when yourdate >= dateadd(year, -1, target_date)
and yourdate < target_date then 1 else 0 end before
, case when yourdate <= dateadd(year, 1, target_date)
and yourdate > target_date then 1 else 0 end after
, case when yourdate = target_date then 1 else 0 end righton
from yourtable
where whatever
group by id, target_date) sq on t.id = sq.id and target_date = dt
where whatever
group by id, target_date
This answer assumes that an id can have more than one target date.