SQL Server query : selecting a total figure, based on a sub-query - sql

I am trying to select total figures from my database table, using aggregate functions.
The trouble is: one of the columns I need requires that I run a sub-query within the aggregate. Which SQL does not allow.
Here is the error I am getting :
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Here is the initial query :
select
method,
sum(payment_id) as payment_id,
sum(status) as status,
sum(allowEmailContact) as allowEmailContact,
sum(allowPhoneContact) as allowPhoneContact,
sum(totalReservations) as totalReservations
from
(SELECT
RES.method, count(*) as payment_id,
'' as status, '' as complete_data,
'' as allowEmailContact, '' as allowPhoneContact,
'' as totalReservations
FROM
Customer CUS
INNER JOIN
Reservation RES ON CUS.id = RES.customerId
WHERE
(RES.created > '2015-05-31 23:59' and RES.created <= '2015-06-15
23:59')
AND RES.payment_id IS NOT NULL
AND scope_id = 1
GROUP BY
RES.method
UNION ALL
etc
etc
) AS results
GROUP BY
method
(I used : "etc, etc, etc" to replace a large part of the query; I assume there is no need to write the entire code, as it is very long. But, the gist is clear)
This query worked just fine.
However, I need an extra field -- a field for those customers whose data are "clean" --- meaning : trimmed, purged of garbage characters (like : */?"#%), etc.
I have a query that does that. But, the problem is: how to insert this query into my already existing query, so I can create that extra column?
This is the query I am using to "clean" customer data :
select *
from dbo.Customer
where
Len(LTRIM(RTRIM(streetAddress))) > 5 and
Len(LTRIM(RTRIM(streetAddress))) <> '' and
(Len(LTRIM(RTRIM(streetAddress))) is not null and
Len(LTRIM(RTRIM(postalCode))) = 5 and postalCode <> '00000' and
postalCode <> '' and Len(LTRIM(RTRIM(postalCode))) is not null and
Len(LTRIM(RTRIM(postalOffice))) > 2 and
phone <> '' and Len(LTRIM(RTRIM(email))) > 5 and
Len(LTRIM(RTRIM(email))) like '#' and
Len(LTRIM(RTRIM(firstName))) > 2 and Len(LTRIM(RTRIM(lastName))) > 2) and
Len(LTRIM(RTRIM(firstName))) <> '-' and Len(LTRIM(RTRIM(lastName))) <> '-' and
Len(LTRIM(RTRIM(firstName))) is not null and
Len(LTRIM(RTRIM(lastName))) is not null
etc, etc
This query works fine on its own.
But, how to INSERT it into the initial query, to create a separate field, where I can get the TOTAL of those customers who meet this "clean" criteria?
I tried it like this :
select
method,
sum(payment_id) as payment_id,
sum(status) as status,
SUM((select *
from dbo.Customer
where
Len(LTRIM(RTRIM(streetAddress))) > 5 and
Len(LTRIM(RTRIM(streetAddress))) <> '' and
(Len(LTRIM(RTRIM(streetAddress))) is not null and
Len(LTRIM(RTRIM(postalCode))) = 5 and
postalCode <> '00000' and postalCode <> '' and
Len(LTRIM(RTRIM(postalCode))) is not null and
Len(LTRIM(RTRIM(postalOffice))) > 2 and phone <> '' and
Len(LTRIM(RTRIM(email))) > 5 and
Len(LTRIM(RTRIM(email))) like '#' and
Len(LTRIM(RTRIM(firstName))) > 2 and
Len(LTRIM(RTRIM(lastName))) > 2) and
Len(LTRIM(RTRIM(firstName))) <> '-' and
Len(LTRIM(RTRIM(lastName))) <> '-' and
Len(LTRIM(RTRIM(firstName))) is not null and
Len(LTRIM(RTRIM(lastName))) is not null) ) as clean_data,
sum(allowEmailContact) as allowEmailContact, sum(allowPhoneContact) as allowPhoneContact,
sum(totalReservations) as totalReservations
from
(SELECT
RES.method, count(*) as payment_id, '' as status,
'' as complete_data, '' as allowEmailContact,
'' as allowPhoneContact, '' as totalReservations
FROM Customer CUS
INNER JOIN Reservation RES ON CUS.id = RES.customerId
WHERE (RES.created > '2015-05-31 23:59' and RES.created <= '2015-06-15
23:59')
AND RES.payment_id is not null and scope_id = 1
GROUP BY RES.method
UNION ALL
etc
etc
etc
and it gave me that "aggregate" error.

SELECT COUNT(*) instead of SUM(), also, the WHERE Clause to clean the data is awful. There has to be a better way. Maybe mark the rows as clean when they're updated or as a batch job?

Related

How to use an SQL Comparator in the base 'Case' selector in the 'When' logic without having to re-write conditions

I have an SQL query joined on multiple tables (all INNER JOINS).
The below is an example of the query I am trying to run (the ? is to illustrate the position in which I presume the answer to my question will be rectified).
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When ? > 0 then 'Bigger' else 'Equal or Smaller' End
)
The idea with the above is that instead of the ? the actual value I want to compare against would be Count(ID), if it's greater than 0 I want it to SELECT 'Bigger', otherwise it should SELECT 'Equal or Smaller'. So a more-accurate depiction of what I wish to run would be the below.
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When
Count(cpdCan.ID) > 0 then 'Bigger' else 'Equal or Smaller' End
)
Of course there is a syntax error above but I am enquiring as to whether it is possible to compare like in the above SQL query structure but replacing Count(cpdCan.ID) > 0 with some other means to achieve that value & logic?
If this is un-achievable in SQL Server 2016 what other means would be a better solution to this XY?
I think that you mean:
case when
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND (cpdCan.DateEnded >= GETDATE() OR coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900')
AND cpdCan.Deleted <> 1
) > 0
then 'Bigger'
else 'Equal or Smaller'
End

SQL Query - Get records with null values (but make sure they dont have any other records that match the key with a value)

So I am writing a query whereby I need to get all records within a table that have null or '' values for two fields...
File and Postcode.
My problem is I have duplicate records, all queries I have written so far will return me a record with a null or '' file and postcode field however one of the duplicates (based on email field) does have a file/postcode value.
I need to only get those records where all instances have a null file/postcode value
SELECT DISTINCT EMAIL FROM Results R
WHERE
( ISNULL(R.Postcode, '') = ''
AND
ISNULL(R.File, '') = ''
)
AND NOT EXISTS (
SELECT Id FROM Results RR
WHERE RR.Email = R.Email
AND (
ISNULL(R.Postcode, '') <> ''
AND
ISNULL(R.File, '') <> ''
)
)
ORDER BY R.Email
Bit of a blind stab in the dark here, but I suspect a HAVING clause with a conditional aggregate will resolve this one:
SELECT Email
FROM Results
GROUP BY Email
HAVING COUNT(CASE WHEN Postcode IS NOT NULL AND Postcode != '' THEN 1 END) = 0
AND COUNT(CASE WHEN [File] IS NOT NULL AND [File] != '' THEN 1 END) = 0;
Note, also, that I haven't used ISNULL (or COALESCE) in the logic, but instead used boolean logic. This is actually important as having functions like ISNULL wrapped around a column in your WHERE cause the query to be non-SARGable; meaning that the indexes on your table can't be used to aid the data engine filter to the correct rows and instead it has to perform a full scan of the data.
I would express the having clause as:
HAVING COUNT(NULLIF(Postcode, '')) = 0 AND
COUNT(NULLIF([File], '')) = 0 ;

SQL : finding which clause is making my query returning no answer

My query is basic and look like this :
SELECT ID FROM Table WHERE CRIT1='a' AND CRIT2='b' AND CRIT3='c'
However it sometimes return no value. This is normal because there is no match in the table.
To help my users to find which criteria is too restrictive, I would like to find another query which tell me if it is because of clause CRIT1, CRIT2 or CRIT3 that I have no answer.
Currently, I've done it this way (using pseudo code) :
If ( SELECT ID FROM Table WHERE CRIT1='a' returns EOF )
Then WrongCriteria="CRIT1"
Elseif ( SELECT ID FROM Table WHERE CRIT1='a' AND CRIT2='b' returns EOF )
Then WrongCriteria="CRIT2"
Elseif ( SELECT ID FROM Table WHERE CRIT1='a' AND CRIT2='b' AND CRIT3='c' returns EOF )
Then WrongCriteria="CRIT3"
It works ... but there are several queries and each of them is very slow due to the poor network response time.
My question is thus : It is possible to do the above pseudo-code in one single SQL query?
You can combine three queries into one by using SUM on a conditional:
SELECT
SUM(CASE WHEN CRIT1='a' THEN 1 ELSE 0 END) as CRIT1
, SUM(CASE WHEN CRIT1='a' AND CRIT2='b' THEN 1 ELSE 0 END) as CRIT2
, SUM(CASE WHEN CRIT1='a' AND CRIT2='b' AND CRIT3='c' THEN 1 ELSE 0 END) as CRIT3
FROM MyTable
Zero in a column corresponds to the criterion being to restrictive.
Note that this is only a different implementation of your three queries, which "prioritizes" the criteria in a specific way (crit1 then crit2 then crit3). In theory, with three criteria you want to test all individual ones, plus three combinations of pairs, i.e get six counts for these conditions:
CRIT1='a'
CRIT2='b'
CRIT3='c'
CRIT1='a' && CRIT2='b'
CRIT1='a' && CRIT3='c'
CRIT2='b' && CRIT3='c'
The above six counts would give you a full picture of which criteria are too restrictive.
Yes it's possible to do this check in a single query using 'OR' operator.
I'm assuming it's only one condition which can be wrong at a time:
SELECT CASE WHEN CRIT1 <> 'a' THEN 'CRIT1'
WHEN CRIT2 <> 'b' THEN 'CRIT2'
WHEN CRIT3 <> 'c' THEN 'CRIT3' END AS WrongCriteria
FROM Table WHERE CRIT1<>'a' OR CRIT2<>'b' OR CRIT3<>'c'
To show all combinations of restrictions:
SELECT
COALESCE( 'Conditions:'
+ NULLIF(
( CASE WHEN CRIT1 <> 'a' THEN ' CRIT1' ELSE '' END )
+ ( CASE WHEN CRIT2 <> 'b' THEN ' CRIT2' ELSE '' END )
+ ( CASE WHEN CRIT3 <> 'c' THEN ' CRIT3' ELSE '' END ),
'' ),
'None' ) AS Restrictions
FROM MyTable

Optimise the update query which exceeds 30 secs of execution time

I have written a procedure which adds the data to database in case is not present (duplicate). If the data is duplicated then the empty fields in the database will be updated.
eg: first time the entry is
companyname email_id contact_name designation mobile fax country
1 abc xyz#abc.com xyz pqr
Now if the entry comes second time with some extra data then
2 abc xyz#abc.com xyz pqr 0987765 087722 South Africa
Now the existing data will be updated only for empty field i.e. only mobile fax and country will be updated in the existing data.
Now my query for updating is as follows:
UPDATE dbo.companyinfo SET companyinfo.companyname=case when companyinfo.companyname='' or companyinfo.companyname=null then RESULT.companyname else companyinfo.companyname end ,
companyinfo.website= case when companyinfo.website='' OR companyinfo.website IS NULL then RESULT.website else companyinfo.website end ,
companyinfo.contactperson= case when companyinfo.contactperson='' OR companyinfo.contactperson IS NULL then RESULT.contactperson else companyinfo.contactperson end,companyinfo.country = case when companyinfo.country=1 OR companyinfo.country IS NULL then RESULT.country else companyinfo.country end,
companyinfo.telphone=case when companyinfo.telphone='' OR companyinfo.telphone IS NULL then RESULT.telphone else companyinfo.telphone end,companyinfo.mobile= case when companyinfo.mobile='' OR companyinfo.mobile IS NULL then RESULT.mobile else companyinfo.mobile end ,
companyinfo.fax= case when companyinfo.fax='' OR companyinfo.fax IS NULL then RESULT.fax else companyinfo.fax end, companyinfo.region= case when companyinfo.region=2 OR companyinfo.region IS NULL then RESULT.region else companyinfo.region end,companyinfo.urlorcatalog=RESULT.urlorcatalog,companyinfo.address= case when companyinfo.address='' OR companyinfo.address IS NULL then RESULT.address else companyinfo.address end,
companyinfo.lastupdatedby=RESULT.lastupdatedby
FROM
(
select TEMP1.companyname,TEMP1.website,TEMP1.contactperson,TEMP1.country, TEMP1.telphone , TEMP1.mobile, TEMP1.fax,TEMP1.region, TEMP1.urlorcatalog,TEMP1.address,TEMP1.lastupdatedby, TEMP1.DataID
from
(
SELECT tmp.companyname,tmp.website,tmp.contactperson,tmp.country,tmp.telphone,tmp.mobile,tmp.fax, tmp.region,tmp.urlorcatalog,tmp.address,tmp.lastupdatedby,Email.DataID,ROW_NUMBER() OVER (PARTITION BY tmp.email ORDER BY tmp.email ) AS 'RowNumber'
FROM #TempTable tmp
LEFT OUTER JOIN emailinfo Email ON tmp.email =Email.email
WHERE
tmp.email !=''
AND
EXISTS (SELECT emailinfo.email FROM dbo.emailinfo WHERE email=tmp.email)
)AS TEMP1
LEFT OUTER JOIN dbo.companyinfo COMPANY ON TEMP1.DataID =COMPANY.dataId
WHERE
TEMP1.RowNumber =1
) AS RESULT
WHERE companyinfo.dataId =RESULT.DataID
Sometimes i get an error saying "Unable to add Timeout expired. Timeout period elapsed prior to completion of the operation or the server is not responding" and through sql profiler i came to know the duration of the above query exceeds 30 secs.
The execution time of this query exceeds 30 seconds. How can i optimize the query so that the execution times becomes less then 30 seconds.
*Note the above query is the part of the procedure
Try using merge statement
MERGE INTO yourtable AS Target
USING (VALUES ('abc',
'xyz#abc.com',
'xyz',
'pqr',
0987765,
087722,
'South Africa')) AS Source (companyname, email_id, contact_name, designation, mobile, fax, country )
ON Target.companyname = Source.companyname
AND Target.email_id = Source.email_id
AND Target.contact_name = Source.contact_name
AND Target.designation = Source.designation
WHEN MATCHED THEN
UPDATE SET companyname = CASE WHEN target.companyname IS NULL OR target.companyname = '' THEN Source.companyname ELSE target.companyname END,
email_id = CASE WHEN target.email_id IS NULL OR target.email_id = '' THEN Source.email_id ELSE target.email_id END,
contact_name = CASE WHEN target.contact_name IS NULL OR target.contact_name = '' THEN Source.contact_name ELSE target.contact_name END,
designation = CASE WHEN target.designation IS NULL OR target.designation = '' THEN Source.designation ELSE target.designation END,
mobile = CASE WHEN target.mobile IS NULL OR target.mobile = '' THEN Source.designation ELSE target.mobile END,
fax = CASE WHEN target.fax IS NULL OR target.fax = '' THEN Source.fax ELSE target.fax END,
country = CASE WHEN target.country IS NULL OR target.country = '' THEN Source.country ELSE target.country END
WHEN NOT MATCHED BY TARGET THEN
INSERT (companyname,
email_id,
contact_name,
designation,
mobile,
fax,
country )
VALUES (companyname,
email_id,
contact_name,
designation,
mobile,
fax,
country );
Note : Modifiy the join ON condition based on your requirement
Well, there seems to be nothing inherently wrong with the query that would explain why it is taking so long. I would look at the query plan to see what it is doing.
I would also look into indenting (for readability) and improve the sub-query (as it seems overly complicated)

SQL Nested Select statements with COUNT()

I'll try to describe as best I can, but it's hard for me to wrap my whole head around this problem let alone describe it....
I am trying to select multiple results in one query to display the current status of a database. I have the first column as one type of record, and the second column as a sub-category of the first column. The subcategory is then linked to more records underneath that, distinguished by status, forming several more columns. I need to display every main-category/subcategory combination, and then the count of how many of each sub-status there are beneath that subcategory in the subsequent columns. I've got it so that I can display the unique combinations, but I'm not sure how to nest the select statements so that I can select the count of a completely different table from the main query. My problem lies in that to display the main category and sub category, I can pull from one table, but I need to count from a different table. Any ideas on the matter would be greatly appreciated
Here's what I have. The count statements would be replaced with the count of each status:
SELECT wave_num "WAVE NUMBER",
int_tasktype "INT / TaskType",
COUNT (1) total,
COUNT (1) "LOCKED/DISABLED",
COUNT (1) released,
COUNT (1) "PARTIALLY ASSEMBLED",
COUNT (1) assembled
FROM (SELECT DISTINCT
(t.invn_need_type || ' / ' || s.code_desc) int_tasktype,
t.task_genrtn_ref_nbr wave_num
FROM sys_code s, task_hdr t
WHERE t.task_genrtn_ref_nbr IN
(SELECT ship_wave_nbr
FROM ship_wave_parm
WHERE TRUNC (create_date_time) LIKE SYSDATE - 7)
AND s.code_type = '590'
AND s.rec_type = 'S'
AND s.code_id = t.task_type),
ship_wave_parm swp
GROUP BY wave_num, int_tasktype
ORDER BY wave_num
Image here: http://i.imgur.com/JX334.png
Guessing a bit,both regarding your problem and Oracle (which I've - unfortunately - never used), hopefully it will give you some ideas. Sorry for completely messing up the way you write SQL, SELECT ... FROM (SELECT ... WHERE ... IN (SELECT ...)) simply confuses me, so I have to restructure:
with tmp(int_tasktype, wave_num) as
(select distinct (t.invn_need_type || ' / ' || s.code_desc), t.task_genrtn_ref_nbr
from sys_code s
join task_hdr t
on s.code_id = t.task_type
where s.code_type = '590'
and s.rec_type = 'S'
and exists(select 1 from ship_wave_parm p
where t.task_genrtn_ref_nbr = p.ship_wave_nbr
and trunc(p.create_date_time) = sysdate - 7))
select t.wave_num "WAVE NUMBER", t.int_tasktype "INT / TaskType",
count(*) TOTAL,
sum(case when sst.sub_status = 'LOCKED' then 1 end) "LOCKED/DISABLED",
sum(case when sst.sub_status = 'RELEASED' then 1 end) RELEASED,
sum(case when sst.sub_status = 'PARTIAL' then 1 end) "PARTIALLY ASSEMBLED",
sum(case when sst.sub_status = 'ASSEMBLED' then 1 end) ASSEMBLED
from tmp t
join sub_status_table sst
on t.wave_num = sst.wave_num
group by t.wave_num, t.int_tasktype
order by t.wave_num
As you notice, I don't know anything about the table with the substatuses.
You can use inner join, grouping and count to get your result:
suppose tables are as follow :
cat (1)--->(n) subcat (1)----->(n) subcat_detail.
so the query would be :
select cat.title cat_title ,subcat.title subcat_title ,count(*) as cnt from
cat inner join sub_cat on cat.id=subcat.cat_id
inner join subcat_detail on subcat.ID=am.subcat_detail_id
group by cat.title,subcat.title
Generally when you need different counts, you need to use the CASE statment.
select count(*) as total
, case when field1 = "test' then 1 else 0 end as testcount
, case when field2 = 'yes' then 1 else 0 endas field2count
FROM table1