How to use Aggregate in Select Statement - sql

I have problem with my code and it seems it didn't accept aggregate function inside my select query. What I am trying to achieve with my DATEDIFF(MONTH,MIN(LoanDateStart),MAX(LoanPaymentDue)) is that I want to get the total number of months and then use the number of months to calculate with the rest of the query.
I got this error:
Msg 130, Level 15, State 1, Line 11
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Is there anyway that I could achieve the other way around?
Query
SELECT
ISNULL(SUM((CAST(((((lt.InterestRate/100) * lc.LoanAmount) +
lc.LoanAmount) / ((dbo.fnNumberOfYears(CONVERT(VARCHAR(15), LoanDateStart,
101), CONVERT(VARCHAR(15), LoanPaymentDue, 101)) * DATEDIFF(MONTH,
MIN(LoanDateStart), MAX(LoanPaymentDue))) * 2)) AS DECIMAL(18,2)))),0)
FROM LoanContract lc
INNER JOIN LoanType lt ON lt.LoanTypeID = lc.LoanTypeID
WHERE lc.LoanTypeID = 1 AND lc.EmployeeID = 5

Please try the following...
SELECT ISNULL( SUM( ( CAST( ( ( ( ( lt.InterestRate / 100 ) *
LoanContract.LoanAmount ) +
LoanContract.LoanAmount ) /
( ( dbo.fnNumberOfYears( CONVERT( VARCHAR( 15 ),
LoanDateStart,
101 ),
CONVERT( VARCHAR( 15 ),
LoanPaymentDue,
101 ) ) *
monthsDifference ) *
2 ) ) AS DECIMAL( 18,
2 ) ) ) ),
0 )
FROM ( SELECT LoanContractID AS LoanContractID,
DATEDIFF( MONTH,
MIN( LoanDateStart ),
MAX( LoanPaymentDue ) ) AS monthsDifference
FROM LoanContract
GROUP BY LoanContractID
) AS monthsDifferenceFinder
INNER JOIN LoanContract ON LoanContract.LoanContractID = monthsDifferenceFinder.LoanContractID
INNER JOIN LoanType lt ON lt.LoanTypeID = LoanContract.LoanTypeID
WHERE lc.LoanTypeID = 1
AND lc.EmployeeID = 5
Please note that I have used LoanContractID in the place of the primary key for LoanContract as you have not stated what that is.
The cause of your problem was that SUM() (an aggregate function) was operating on the results of MIN() and MAX(), themselves aggregate functions. This confused SQL-Server.
I have worked around this problem by writing a subquery that determines the difference between your two values for each LoanContract, based on the unique identifier for that LoanContract as opposed to the primary key for each record. (Is your data in 3NF (Third Normal Form)?)
The results of the subquery are then joined to LoanContract based on LoanContractID, effectively appending each LoanContract's monthsDifference value to each record for that LoanContract.
SUM() is then able to work on a retrieved value rather than having to try to aggregate correctly aggregated values.
If you have any questions or comments, then please feel free to post a Comment accordingly.

Anyway, I fixed the issue right now, I'm using LEFT OUTER JOIN.
Here is my code:
SELECT
ISNULL(SUM((CAST(((((lt.InterestRate/100) * lc.LoanAmount) + lc.LoanAmount) / ((dbo.fnNumberOfYears(CONVERT(VARCHAR(15), LoanDateStart, 101), CONVERT(VARCHAR(15), LoanPaymentDue, 101)) * x.NumberOfMonths) * 2)) AS DECIMAL(18,2)))),0)
FROM LoanContract lc
INNER JOIN LoanType lt ON lt.LoanTypeID = lc.LoanTypeID
LEFT OUTER JOIN(SELECT
lc.LoanTypeID AS 'LoanTypeID',
lc.EmployeeID AS 'EmployeeID',
(DATEDIFF(MONTH, MIN(LoanDateStart), MAX(LoanPaymentDue))) AS 'NumberOfMonths'
FROM LoanContract lc
INNER JOIN LoanType lt ON lt.LoanTypeID = lc.LoanTypeID
GROUP BY lc.EmployeeID, lc.LoanTypeID) x ON x.EmployeeID = lc.EmployeeID AND x.LoanTypeID = lc.LoanTypeID
WHERE lc.LoanTypeID = 1 AND lc.EmployeeID = 5

Related

SQL group by in Subquery

I'm trying to get monthly production using group by after converting the unix column into regular timestamp. Can you please tell how to use group by here in the code.
'''
With production(SystemId, dayof, monthof, yearof, powerwatts, productionwattshours) as
(
Select SystemId,
[dayof] = DAY(hrdtc),
[monthof] = MONTH(hrdtc),
[yearof] = YEAR(hrdtc),
powerwatts, productionwatthours
from (
Select * , dateadd(s, UnixTime, '19700101') as hrdtc from meterreading ) ds
)
Select * from production
where systemId = 2368252
'''
I think you're looking for this (technically you don't need a subquery but it allows you to avoid repeating the DATEADD() expression):
SELECT SystemId = 2368252,
[Month] = DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1),
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
(
SELECT powerwatts, productionwatthours,
DATEADD(SECOND, UnixTime, '19700101') as hrdtc
FROM dbo.enphasemeterreading
WHERE systemId = 2368252
) AS ds
GROUP BY DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1);
If you want to also avoid repeating the GROUP BY expression:
SELECT SystemId = 2368252,
[Month],
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
(
SELECT [Month] = DATEFROMPARTS(YEAR(hrdtc), MONTH(hrdtc), 1),
powerwatts, productionwatthours
FROM
(
SELECT powerwatts, productionwatthours,
DATEADD(SECOND, UnixTime, '19700101') as hrdtc
FROM dbo.enphasemeterreading
WHERE systemId = 2368252
) AS ds1
) AS ds2
GROUP BY [Month];
Personally I don't think that's any prettier or clearer. A couple of other tips:
Spell it out; shorthand is lazy and problematic
Always qualify tables and other objects with schema
Updated requirement (please state these up front): How would I join this query to another table?
SELECT * FROM dbo.SomeOtherTable AS sot
INNER JOIN
(
SELECT SystemId = 2368252,
[Month],
powerwatts = SUM(powerwatts),
productionwatthours = SUM(productionwatthours)
FROM
...
GROUP BY [Month]
) AS agg
ON sot.SystemId = agg.SystemId;

Showing list of all 24 hours in sql server if there is no data also

I have a query where I need to show 24 hour calls for each day.
But I am getting the hours which I have calls only.
My requirement is I need to get all the hours split and 0 if there are no calls.
Please suggest
Below is my code.
select #TrendStartDate
,isd.Name
,isd.Call_ID
,isd.callType
,DATEPART(HOUR,isd.ArrivalTime)
from [PHONE_CALLS] ISD WITH (NOLOCK)
WHERE CallType = 'Incoming'
and Name not in ('DefaultQueue')
and CAST(ArrivalTime as DATe) between #TrendStartDate and #TrendEndDate
The basic idea is that you use a table containing numbers from 0 to 23, and left join that to your data table:
WITH CTE AS
(
SELECT TOP 24 ROW_NUMBER() OVER(ORDER BY ##SPID) - 1 As TheHour
FROM sys.objects
)
SELECT #TrendStartDate
,isd.Name
,isd.Call_ID
,isd.callType
,TheHour
FROM CTE
LEFT JOIN [PHONE_CALLS] ISD WITH (NOLOCK)
ON DATEPART(HOUR,isd.ArrivalTime) = TheHour
AND CallType = 'Incoming'
AND Name NOT IN ('DefaultQueue')
AND CAST(ArrivalTime as DATe) BETWEEN #TrendStartDate AND #TrendEndDate
If you have a tally table, you should use that. If not, the cte will provide you with numbers from 0 to 23.
If you have a numbers table you can use a query like the following:
SELECT d.Date,
h.Hour,
Calls = COUNT(pc.Call_ID)
FROM ( SELECT [Hour] = Number
FROM dbo.Numbers
WHERE Number >= 0
AND Number < 24
) AS h
CROSS JOIN
( SELECT Date = DATEADD(DAY, Number, #TrendStartDate)
FROM dbo.Numbers
WHERE Number <= DATEDIFF(DAY, #TrendStartDate, #TrendEndDate)
) AS d
LEFT JOIN [PHONE_CALLS] AS pc
ON pc.CallType = 'Incoming'
AND pc.Name NOT IN ('DefaultQueue')
AND CAST(pc.ArrivalTime AS DATE) = d.Date
AND DATEPART(HOUR, pc.ArrivalTime) = h.Hour
GROUP BY d.Date, h.Hour
ORDER BY d.Date, h.Hour;
The key is to get all the hours you need:
SELECT [Hour] = Number
FROM dbo.Numbers
WHERE Number >= 0
AND Number < 24
And all the days that you need in your range:
SELECT Date = DATEADD(DAY, Number, #TrendStartDate)
FROM dbo.Numbers
WHERE Number < DATEDIFF(DAY, #TrendStartDate, #TrendEndDate)
Then cross join the two, so that you are guaranteed to have all 24 hours for each day you want. Finally, you can left join to your call table to get the count of calls.
Example on DB<>Fiddle
You can use SQL SERVER recursivity with CTE to generate the hours between 0 and 23 and then a left outer join with the call table
You also use any other Method mentioned in this link to generate numbers from 0 to 23
Link to SQLFiddle
set dateformat ymd
declare #calls as table(date date,hour int,calls int)
insert into #calls values('2020-01-02',0,66),('2020-01-02',1,888),
('2020-01-02',2,5),('2020-01-02',3,8),
('2020-01-02',4,9),('2020-01-02',5,55),('2020-01-02',6,44),('2020-01-02',7,87),('2020-01-02',8,90),
('2020-01-02',9,34),('2020-01-02',10,22),('2020-01-02',11,65),('2020-01-02',12,54),('2020-01-02',13,78),
('2020-01-02',23,99);
with cte as (select 0 n,date from #calls union all select 1+n,date from cte where 1+n <24)
select distinct(cte.date),cte.n [Hour],isnull(ca.calls,0) calls from cte left outer join #calls ca on cte.n=ca.hour and cte.date=ca.date

CTE doesn't work in SSAS Cube. I want to find solution or convert it ti subquery

I write this CTE query and the explanation is:
WITH TP AS
(select
c.ID, c.PeriodCId, c.PeriodName, c.Status, c.StatusChangeDate, CAST(c.StartDate AS DATE) AS StartDate, c.EndDate,c.PeriodCode,
c.PeriodType, c.ParentCId, c.MarketId, c.ParentId, c.WD, LEFT(CONVERT(varchar, c.StartDate, 112), 6) AS YEARMONTH,
(select count(*) from dTimePeriod c2 where c2.ParentId = c.ID and c2.Status='actv') as #children
from dTimePeriod c
where (MarketId = 7) ),
TP2 AS
( SELECT *
FROM TP
WHERE #children='12' ),
TP3 AS
(SELECT TP.*, CASE WHEN (TP.WD IS NOT NULL) AND (TP.StartDate <= getdate()) AND TP2.ID=TP.ParentId THEN 18 ELSE NULL END AS WorkingDays
FROM TP LEFT JOIN TP2 ON TP2.ID=TP.ParentId)
select * from TP3
order by ID
and this is the result
CTE Image
I have recursive table called [dTimePeriod] this table contains different cycles and each cycle contains different number of periods,EX: one cycle has 8 periods another cycle has 12 periods and so on, I want if cycles contains 12 periods put to each period value = 18 and for others cycle periods null
and there are some another conditions but it's not the issue.
And when I put it in the SSAS cube doesn't work because the cube doesn't understand the CTE so I tried to find a solution but it doesn't work,
one of them to put this CTE in a view and call this view in the cube but the view doesn't work as well.
so I start to write it as subquery to make the cube able to understand it.
but I am stuck, I can't write this CTE in subquery statement
and this is the subquery where I stuck
SELECT c.ID, c.PeriodCId, c.PeriodName, c.Status, c.StatusChangeDate, CAST(c.StartDate AS DATE) AS StartDate, c.EndDate,
c.PeriodCode, c.PeriodType, c.ParentCId, c.MarketId, c.ParentId, c.WD,
LEFT(CONVERT(varchar, c.StartDate, 112), 6) AS YEARMONTH,
CASE WHEN (c.WD IS NOT NULL) AND (c.StartDate <= getdate()) THEN 18
WHEN (c.WD IS NOT NULL) AND (c.StartDate > getdate()) THEN NULL ELSE c.WD END AS WorkingDays,
case when (select sub.* from
(select count(*) as children from dTimePeriod c2 where c2.ParentId = c.ID and c2.Status='actv' ) sub ) = 12
then 18 else null end as WWW
FROM dTimePeriod c
WHERE (c.MarketId = 7)
Instead of using the SQL command directly in the data source for your cube, you can turn this query into a stored procedure and use that result set in the cube. For the SQL statement in the cube, just do an EXEC command as below. The database name isn't necessary if this database is already the initial catalog in the connection string of the data source.
EXEC YourDatabase.YourSchema.YourSP

Month Aggregation with 0 for NULL results

I have seen something similar but I can't get this to work:
SELECT
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE
, CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_ACC
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR
, SUM (CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY
, CNTRSINTDATA.DIM_CNTRS_TSI_COA
WHERE
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.QB_TRANS_DATE = CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_DATE
AND CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR LIKE '2017'
AND CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE = CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_CODE
AND CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE = '6598'
GROUP BY
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI.TSI_NOMINAL_CODE
, CNTRSINTDATA.DIM_CNTRS_TSI_COA.TSI_NOMINAL_ACC
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR;`
The above query returns results for:
Feb-17 250
Jul-17 400
Jun-17 654
May-17 654
Oct-17 150
Nov-17 250
Aug-17 250
Sep-17
I need the rest of the months to also come back with zero's as there no transactions on the account that month.
Jan-17 0
Feb-17 250
Mar-17 0
Apr-17 0
Jul-17 400
Jun-17 654
May-17 654
Oct-17 150
Nov-17 250
Aug-17 250
Sep-17 0
Dec-17 0
There is a date table that has all the months as VARCHAR2 against date. Just cant get the right syntax. Can anyone help please?
Let's break that down and make it more manageable.
Firstly, translate that to SQL-92 join syntax and add some aliases
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, SUM (fqtt.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
INNER JOIN CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
INNER JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Next, you said that there exists a date record (presumably in CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY), so swap that around
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, SUM (fqtt.QB_TRANS_AMOUNT) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
INNER JOIN CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
INNER JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Finally, you don't actually want inner joins because that eliminates tuples where there is no match. In this case, you want a left join because you want to have the left value even if no value exists on the right. You also need to coalesce your sum expression to 0 because (for reasons I cannot fathom), the SQL standard defines the sum of a bunch records only containing null as null.
SELECT
dcde.CALENDAR_MONTH
, fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CNTRS_FIN_YEAR
, COALESCE(SUM (fqtt.QB_TRANS_AMOUNT), 0) AS CNTRS_ACC_BUDGET
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY dcde
LEFT JOIN CNTRSINTDATA.FACT_QUICKBOOKS_TRANS_TSI fqtt
ON fqtt.QB_TRANS_DATE = dcde.CALENDAR_DATE
LEFT JOIN CNTRSINTDATA.DIM_CNTRS_TSI_COA dctc
ON AND fqtt.TSI_NOMINAL_CODE = dctc.TSI_NOMINAL_CODE
WHERE
dcde.CNTRS_FIN_YEAR LIKE '2017'
AND
fqtt.TSI_NOMINAL_CODE = '6598'
GROUP BY
fqtt.TSI_NOMINAL_CODE
, dctc.TSI_NOMINAL_ACC
, dcde.CALENDAR_MONTH
, dcde.CNTRS_FIN_YEAR;
Give that a try and see if it is what you want
#Adam G,
Thank you for your suggestion. I actually got an idea from your commentry and instead created a select statement to get all the months I needed first. After that I joined to a select statement picking up the transactions where months matched and it worked. See below (come table names have changed)
SELECT DISTINCT
CNTRS_DATE_ENTITY.CALENDAR_MONTH
, CNTRS_DATE_ENTITY.CNTRS_FIN_MONTH_POS
, COALESCE (TRANS_DATA.TRANS_AMOUNT,0) AS TRANS_AMOUNT
FROM
CNTRSINTDATA.DIM_CNTRS_DATE_ENTITY CNTRS_DATE_ENTITY
LEFT JOIN
(
SELECT
QBS_TRANS_TSI.QB_NOMINAL_CODE
, TO_CHAR(QBS_TRANS_TSI.QB_TRANS_DATE, 'Mon-YY') AS TRANS_MONTH
, COALESCE (SUM(QBS_TRANS_TSI.QB_TRANS_AMOUNT),0) AS TRANS _AMOUNT
FROM
CNTRSINTDATA.FACT_QBS_TRANS_TSI QBS_TRANS_TSI
WHERE
QBS_TRANS_TSI.TSI_NOMINAL_CODE = '6598'
GROUP BY
QBS_TRANS_TSI.QB_NOMINAL_CODE
, TO_CHAR(QBS_TRANS_TSI.QB_TRANS_DATE, 'Mon-YY')
) TRANS_DATA
ON TRANS_DATA.TRANS_MONTH = CNTRS_DATE_ENTITY.CALENDAR_MONTH
WHERE
CNTRS_DATE_ENTITY.CNTRS_FIN_YEAR LIKE '2017'
ORDER BY
CNTRS_DATE_ENTITY.CNTRS_FIN_MONTH_POS
However the results have all the months but have blanks in the entity columns where the sum is zero. Any ideas on a remedy for this?
Here is one possible solution:
Generate fake empty transactions for the target period
for example using CTE:
declare #StartDate datetime = '20170101'
declare #EndDate datetime = '20171231'
;
with dt as
(
select #StartDate As 'thedate'
union all
select dateadd(month, 1, thedate) from dt where thedate < dateadd(month, -1, #EndDate)
)
select
dt.thedate 'row date',
datename(month,dt.thedate) 'Month',
YEAR(dt.thedate) 'Year',
0 'Amount '
from dt
Now you can add them to the transaction table (like union all) or you can do a left join with filtering on the result of your query.
I hope it helps! 🙂

Complex LEFT JOIN not working as expected

DBMS is intersystems-cache!
Here is my full query:
SELECT m.Name AS MessageType, COUNT(l.name) AS MessageCount, CAST(AVG(ResponseTime) AS DECIMAL(5, 2)) AS AvgResponseTime
FROM
(SELECT DISTINCT(name) FROM ENSLIB_HL7.Message) m LEFT JOIN
(
SELECT CAST(li.SessionId AS Bigint) AS session_id, li.name, MIN(li.TimeCreated) AS SessionStart, MAX(lo.TimeCreated) AS SessionEnd, CAST(DATEDIFF(s, MIN(li.TimeCreated), MAX(lo.TimeCreated)) AS DECIMAL(5, 2)) AS ResponseTime
FROM (SELECT h1.SessionId, h1.TimeCreated, $PIECE(RawContent, '|', 4), m1.name FROM ens.messageheader h1, ENSLIB_HL7.Message m1 WHERE h1.MessageBodyId = m1.id AND h1.TimeCreated > DATEADD(mi, -30, GETUTCDATE())) li
JOIN (SELECT h2.SessionId, h2.TimeCreated FROM ens.messageheader h2, ENSLIB_HL7.Message m2 WHERE h2.MessageBodyId = m2.id AND h2.TimeCreated > DATEADD(mi, -30, GETUTCDATE())) lo
ON li.SessionId = lo.SessionId
GROUP BY li.SessionId
) l on m.name = l.name
GROUP BY l.Name
This gives me 4 results:
VXU_V04 0 (null)
ADT_A03 3 0.01
ADT_A04 3 0.01
ADT_A08 143 0.01
Given that there is one result with 0 records, it seems like it is working. However, if I run SELECT DISTINCT(name) FROM ENSLIB_HL7.Message I get 10 results:
VXU_V04
ADT_A08
ACK_A08
ADT_A03
ACK_A03
ADT_A04
ACK_A04
ACK_V04
ADT_A01
ACK_A01
Why don't I get ten rows with my full query?
Change the GROUP BY to:
GROUP BY m.Name
You are aggregating by the column in the second table, so you only get one row for all the NULL values.
Most databases would reject this syntax, but apparently Intersystems Cache allows it.