using count in a subquery and getting errors - sql

i have line by line data in a table and i need to net of cancellations from sales and produce a report grouping on a scheme identifier. i.e i need to find all the sales and subtract all the cancellatsion to prduce a net sales figure.
i am trying to use the query below but i'm getting errors.
select insscheme, ((select count(quote_id) where (sale = '1')) - (select count(quote_id) where cancellation = '1')) as sales from policys
group by insscheme
order by insscheme
and i'm getting the error
Column 'policys.Sale' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Can anyone help me out with this?

You don't need any sub queries here. Just use COUNT and CASE.
SELECT insscheme,
COUNT(CASE WHEN sale = '1' AND cancellation <> '1' THEN 1 END) AS sales
FROM policys
GROUP BY insscheme
ORDER BY insscheme
I have assumed above that cancellation is not nullable. If it is use
COUNT(CASE WHEN sale = '1' THEN 1 END) -
COUNT(CASE WHEN cancellation = '1' THEN 1 END) AS sales

Perhaps this might work.
select insscheme, SUM(sale) - SUM(cancellation) as NetSales
from policys
group by insscheme
I don't see what the quote_id column has to do with your query. Are you querying one table or several? It would help if you could show us what your schema looks like with a brief discussion of your table layout.

Related

Running total for grouped data SQL

I know there are a lot of questions about this and I have tried a lot of the answers but none have worked for me yet (see below for the most promising).
I want to get the running total in the column amount grouped by project and date divided into two groups (invoiced or costs). The group is dependent on the Cost Center as you can see in the query.
This topic has seemed to be the closest to what I'm trying to achieve but I cant get it to work.
SQL - running total when data already grouped
With the code below I get the error message "Column 'M3FDBPRD.MVXJDTA.FGLEDG.EGAIT2' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause." But I don't want to group by cost center in the result, I only want to use it to group the amount.
The reuslt im trying to achevie is this (if using the data above):
My current code is this:
SELECT
Project As Projektnr,
LastDayOfMonth,
-SUM(CASE WHEN CostCenter= '9000' THEN Amount
WHEN CostCenter = '2100' THEN Amount
WHEN CostCenter = '2200' THEN Amount
WHEN CostCenter = '2300' THEN Amount END) OVER (ORDER BY LastDayOfMonth) AS Invoiced,
-SUM(CASE WHEN CostCenter > '9000' and CostCenter < '9999' THEN Amount END) OVER (ORDER BY LastDayOfMonth) AS RunningTotalCosts
FROM [M3FDBPRD].[MVXJDTA].[FGLEDG]
WHERE Project = '150001'
GROUP BY LastDayOfMonth, Project
ORDER BY LastDayOfMonth
I have SQL Server version 13
Thank you
You are mixing window functions and aggregation. The logic looks like:
SUM(SUM(CASE WHEN CostCenter IN ('9000' '2100', '2200', '2300')
THEN Amount
END)
) OVER (ORDER BY LastDayOfMonth) AS Invoiced,
SUM(SUM(CASE WHEN CostCenter > '9000' and CostCenter < '9999'
THEN Amount
END)
) OVER (ORDER BY LastDayOfMonth) AS RunningTotalCosts
That is, the SUM()s need to nested. The inner SUM() is the aggregation. The outer SUM() is the window function.

SQL - Grouping by Last Day of Quarter

I currently have a query running to average survey scores for agents. We use the date range of the LastDayOfTheQuarter and 180 days back to calculate these scores. I ran into an issue for this current quarter.
One of my agents hasn't received any surveys in 2020 which is causing the query to not pull the current lastdayofquarter and 180 days back of results.
The code I am using:
SELECT
Agent,
U.Position,
U.BranchDescription,
(ADDDATE(LastDayOfQuarter, -180)) AS MinDate,
(LastDayOfQuarter) AS MaxDate,
COUNT(DISTINCT Response ID) as SurveyCount,
AVG(CASE WHEN Question ID = Q1_2 THEN Answer Value END) AS EngagedScore,
AVG(CASE WHEN Question ID = Q1_3 THEN Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Question ID = Q1_6 THEN Answer Value END) AS ValuedScore
FROM qualtrics_responses
LEFT JOIN date D
ON (D.`Date`) = (DATE(`End Date`))
LEFT JOIN `users` U
ON U.`UserID` = `Agent ID`
WHERE `Agent` IS NOT NULL
AND DATE(`End Date`) <= (`LastDayOfQuarter`)
AND DATE(`End Date`) >= (ADDDATE(`LastDayOfQuarter`, -180))
GROUP BY `Agent`, (ADDDATE(`LastDayOfQuarter`, -180))
i know the issue is due to the way I am joining the dates and since he doesn't have a result in this current year, the end date to date join isn't grabbing the desired date range. I can't seem to come up with any alternatives. Any help is appreciated.
I make the assumption that table date in your query is a calendar table, that stores the starts and ends of the quarters (most likely with one row per date in the quarter).
If so, you can solve this problem by rearranging the joins: first cross join the users and the calendar table to generate all possible combinations, then bring in the surveys table with a left join:
SELECT
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter - interval 180 day AS MinDate,
D.LastDayOfQuarter AS MaxDate,
COUNT(DISTINCT Q.ResponseID) as SurveyCount,
AVG(CASE WHEN Q.QuestionID = 'Q1_2' THEN Q.Answer Value END) AS EngagedScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_3' THEN Q.Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_6' THEN Q.Answer Value END) AS ValuedScore
FROM date D
CROSS JOIN users U
LEFT JOIN qualtrics_responses Q
ON Q.EndDate >= D.Date
AND Q.EndDate < D.Date + interval 1 day
AND U.UserID = Q.AgentID
AND Q.Agent IS NOT NULL
GROUP BY
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter
Notes:
I adapted the date arithmetics - this assumes that you are using MySQL, as the syntax of the query suggests
You should really qualify all the columns in the query, by prefixing them with the alias of the table they belong to; this makes the query so much easier to understand. I gave a tried at it, you might need to review that.
All non-aggregated columns should appear in the group by clause (also see the comment from Eric); this is a a requirement in most databaseses, and good practice anywhere

SQL adding one filter in outer query to apply to all subqueries

I am creating a query that contains multiple sub-queries that show number of incidents in different status/category/etc. A date filter will need to be applied to all sub-queries, in order to count number of incidents created within the date range.
Because the report will be moved to Business Objects, I cannot specify the dates multiple times in the sub-queries. Hence I joined the incident table (inc) in the sub-queries with another incident table (inc_filter) in the outer query, and hoping to apply one date filter to all sub queries.
But the result returned was incorrect, I got multiple rows that have the value either 0 or 1.
Could anyone please point me to the right direction?
SELECT
(SELECT COUNT(*)
FROM Incident inc
WHERE inc.id = inc_filter.id
AND inc.status = 'Open')
"Total # of Open Inc",
(SELECT COUNT(*)
FROM Incident inc
WHERE inc.id = inc_filter.id
AND inc.status = 'Closed')
"Total # of Closed Inc"
--more sub-queries here...
FROM Incident inc_filter
AND inc_filter.CREATED > '10-Apr-2017'
AND inc_filter.CREATED < '13-Apr-2017'
You are probably simply looking for conditional aggregation:
SELECT
COUNT(CASE WHEN status = 'Open' THEN 1 END) AS "Total # of Open Inc",
COUNT(CASE WHEN status = 'Closed' THEN 1 END) AS "Total # of Closed Inc"
-- more counts here...
FROM Incident
WHERE created >= DATE '2017-04-10' AND created < DATE '2017-04-13';
First you should use case and you dont need use many subquery,
second if i understood your question you should use sum() like this
SELECT sum(case when inc_filter.status = 'Open' then 1 else 0 end) as open,
sum(case when inc_filter.status = 'Closed' then 1 else 0 end) as closed
FROM Incident inc_filter
AND inc_filter.CREATED > '10-Apr-2017'
AND inc_filter.CREATED < '13-Apr-2017'
Dear you need one more select above this with using sum of your count. actually you are using group function but that is apply on row level. you need you sum all your subquery columns to see the required result for example your query will be
select sum("Total # of Open Inc") ,sum("Total # of Closed Inc")
from(
SELECT
(SELECT COUNT(*)
FROM Incident inc
WHERE inc.id = inc_filter.id
AND inc.status = 'Open')
"Total # of Open Inc",
(SELECT COUNT(*)
FROM Incident inc
WHERE inc.id = inc_filter.id
AND inc.status = 'Closed')
"Total # of Closed Inc"
--more sub-queries here...
FROM Incident inc_filter
AND inc_filter.CREATED > '10-Apr-2017'
AND inc_filter.CREATED < '13-Apr-2017');
But it is better for you to use joins

MS SQL Server 2012 query speed / optimization

First, I apologize if this has been answered elsewhere, but I was unable to find anything today. If it has been answered, the lack is me, not the search system.
I am having an issue where in a stored procedure works fairly quickly until I get to a specific point.
I have a database of POS sales data for a restaurant chain.
Among the various tables we have, the ones I need for this query are:
Item (the definitions of the various items; each row includes the SALES category the item belongs to; see caveats below)
Category (the definitions of the various categories items can be in)
CategoryItem (the mapping between the above)
HstItem (the historical sales of the items)
Caveats: There are 2 types of categories each item can be in:
sales categories: each item can be in one sales category at a time.
reporting categories: each item can be in a arbitrary number of these categories simultaneously.
I am needing to get sales totals for 6 date ranges for a specific REPORTING category (week to date, wtd ly, period to date, ptd ly, year to date, ytd ly).
All of that said, for all of my code the query / procedure runs in a decent amount of time, until to the section for the reporting category.
My select statement currently includes the following WHERE clause:
where hstitem.itemid in (select fkitemid from categoryitem where categoryitemid = ##)
I am looking for a more efficient / faster way to execute this.
Any assistance is greatly appreciated. Thanks in advance.
EDIT:
The full original query is as follows:
insert into #GnG (GStoreID, CurWkGNG, CurWkGNGLY,CurYTDGNG,CurYTDGNGLY,CurrPTDGNG,CurrPTDGNGLY)
select
hgi.FKStoreId,
CurWkGnG = sum(case when hgi.DateOfBusiness between #SDate and #EDate then hgi.price else 0 end),
CurWkGnGLY = sum(case when hgi.DateOfBusiness between #SDateLY and #EDateLY then hgi.price else 0 end),
CurYTDGnG =
case
when convert(varchar(10),opendate,126) between convert(varchar(10),#LYTDStart,126) and convert(varchar(10),#LYTDEnd,126) then sum(case when hgi.DateOfBusiness between DATEADD(day, (DATEPART(week, opendate) * 7 + DATEPART(weekday, opendate)) - (DATEPART(week, DATEADD(year, 1, opendate)) * 7 + DATEPART(weekday, DATEADD(year, 1, opendate))), DATEADD(year, 1, opendate)) and #CYTDEnd then hgi.price else 0 end)
else sum(case when hgi.DateOfBusiness between #CYTDStart and #CYTDEnd then hgi.price else 0 end)
end,
CurYTDGnGLY = sum(case when hgi.DateOfBusiness between #LYTDStart and #LYTDEnd then hgi.price else 0 end),
CurrPTDGnG = sum(case when hgi.DateOfBusiness between #CurrPtDStart and #CurrPtDEnd then hgi.price else 0 end),
CurrPTDGnGLY = sum(case when hgi.DateOfBusiness between #CurrPtDLYStart and #CurrPtDlyEnd then hgi.price else 0 end)
from hstGndItem hgi
join #StoresIncluded si
on hgi.FKStoreID = si.StoreID
where hgi.fkitemid in
(select fkitemid from categoryitem where categoryitemid = 25)
group by hgi.fkstoreid, opendate, comping
order by hgi.fkstoreid
Try converting the "IN" to a inner join like so :
FROM hstitem h inner join categoryitem c on c.fkitemid = h.itemid
where c.categoryitemid = ##
You can use WHERE EXISTS instead of IN to check if the ID exists in the table:
WHERE EXISTS
(
SELECT ci.fkitemid
FROM categoryitem ci
WHERE ci.categoryitemid = ## AND ci.fkitemid = hstitem.itemid
)
The difference between the IN clause and using EXISTS is that the sub-query inside the WHERE EXISTS will exit prematurely after a match have been found while the IN clause would wait until the sub-query is finished.
I apologize for the lag in my response here.
I found AN answer. Dont know if it is the right one or not, but it works for us.
I removed the code to use a sub select from the where, and am now generating a new table to hold the values that should pull. The new code to populate the table runs each morning around 0600. I then am having the main code simply join to that table to pull the single answer, rather than perform math based on the sub-query.
Thank you all for the suggestions.

SQL count records depending on criteria

I have a table which has account information for each individual working day. Each record shows the balance on the account for that particular day. I need to create a field that has a running count on how many days it has been since the balance on that particular account was zero.
For example: I have an account which is number 00000001. It was opened a week ago. The database has created a record for the account for last Tuesday, Wednesday, Thursday, Friday and Monday. The account did not have a balance until Friday and the balance was the same for Monday. I want the field to show '2' as a result. Further to this if the balance drops to '0' today, I need the count to reset on the next record and start again if the account has a balance the following day. There are several accounts in this table so I need this to work for each one individually.
Below is as far as I have got:
SELECT
pos.EffDate,
cus.CNA1,
pos.ACNO,
pos.CCY,
pos.LDBL,
pos.LDBLUSD,
MIN(pos3.effdate) AS 'First Post Date',
pos3.Balcount
FROM[dbo].[Account] AS pos
JOIN [dbo].[Customer] AS cus ON ((pos.CNUM=cus.CUST_NO) AND (cus.effdate=pos.effdate))
LEFT JOIN (SELECT pos2.effdate, pos2.ACNO, SUM(CASE pos2.LDBL WHEN 0 THEN 0 ELSE 1 END) AS 'Balcount' FROM [dbo].[Account] AS pos2 GROUP BY pos2.ACNO, pos2.Effdate HAVING pos2.effdate BETWEEN pos2.effdate AND MIN(pos2.effdate)) pos3 ON pos3.ACNO = pos.ACNO
WHERE pos.effdate >='1 Dec 2015' AND pos.Effdate <'30 Dec 2015'
AND pos.srcsys = 'MP_UK'
AND pos.LDBL <= 0
AND pos.CNUM <> '000020'
AND pos.intbear <> 'N'
AND pos.blockdeb IS null
GROUP BY pos.EffDate,cus.CNA1,pos.ACNO,pos.CCY,pos.LDBL,pos.LDBLUSD, pos3.Balcount
ORDER BY 1,2,3
If you need me to clarify anything please let me know. All help is greatly appreciated.
Thanks,
Ben
Basically, you want to group the data, based on the number of time the account is zero before a given row. Then, within each group, you want to enumerate the records.
In SQL Server 2012+, you can do these things with window functions. I am not sure exactly what your sample query has to do with the question, but here is the basic idea:
select a.*, row_number() over (partition by cust_no, grp order by eff_date) as seqnum
from (select a.*,
sum(case when balance = 0 then 1 else 0 end) over
(partition by cust_no order by eff_date) as grp
from Account a
) a;
In earlier versions of SQL Server, you can do something very similar using apply.