Performing a sub query alternatives - sql

I'm looking for advice on how to get around the
"Cannot perform an aggregate function on an expression containing an
aggregate or a subquery".
On the Select PlateID From #Instances code, in the example below. Does anyone have any ideas or suggestions?
DECLARE #Instances AS TABLE(PlateID INT);
INSERT INTO #Instances(PlateID)VALUES(11638),(11637),(11632),(11659)
DECLARE #NumberofPlates INT;
SELECT #NumberofPlates = COUNT(*) FROM #Instances;
SELECT Instance_Plate_Room_Instance_ID_LNK
from dbo.M_Instance_Plate
WHERE Instance_Plate_Deleted = 0
group by Instance_Plate_Room_Instance_ID_LNK
having sum(case
when Instance_Plate_Plate_ID_LNK not in (SELECT PlateID
FROM #Instances)
then 1 else 0 end) = 0 and
SUM(case
when Instance_Plate_Plate_ID_LNK in (SELECT PlateID
FROM #Instances)
then 1 else 0 end) = #NumberofPlates;

In the absence of the structure of the physical table you included in your query I mocked up some random data for this, and put together a query that seems to work?
DECLARE #Instances AS TABLE(PlateID INT);
INSERT INTO #Instances(PlateID) VALUES (11638),(11637),(11632),(11632);
DECLARE #M_Instance_Plate TABLE (Instance_Plate_Plate_ID_LNK INT, Instance_Plate_Deleted INT, Instance_Plate_Room_Instance_ID_LNK INT);
INSERT INTO #M_Instance_Plate SELECT 11638, 0, 100;
INSERT INTO #M_Instance_Plate SELECT 11637, 0, 100;
INSERT INTO #M_Instance_Plate SELECT 11632, 0, 100;
INSERT INTO #M_Instance_Plate SELECT 11632, 0, 200;
INSERT INTO #M_Instance_Plate SELECT 11632, 1, 300;
DECLARE #NumberofPlates INT;
SELECT #NumberofPlates = COUNT(*) FROM #Instances;
WITH x AS (
SELECT
Instance_Plate_Room_Instance_ID_LNK,
SUM(CASE WHEN Instance_Plate_Plate_ID_LNK IS NULL THEN 1 ELSE 0 END) AS test_1, --Any missing
SUM(CASE WHEN Instance_Plate_Plate_ID_LNK IS NOT NULL THEN 1 ELSE 0 END) AS test_2 --Has coverage
FROM
#M_Instance_Plate ip
LEFT JOIN #Instances i ON i.PlateID = ip.Instance_Plate_Plate_ID_LNK
WHERE
Instance_Plate_Deleted = 0
GROUP BY
Instance_Plate_Room_Instance_ID_LNK)
SELECT
Instance_Plate_Room_Instance_ID_LNK
FROM
x
WHERE
test_1 = 0
AND test_2 = #NumberofPlates;

INSERT INTO #Instances(PlateID)VALUES(11638),(11637),(11632),(11659)
--DECLARE #NumberofPlates INT;
--SELECT #NumberofPlates = COUNT(*) FROM #Instances;
SELECT Instance_Plate_Room_Instance_ID_LNK
FROM dbo.M_Instance_Plate p
WHERE Instance_Plate_Deleted = 0
AND NOT EXISTS (
SELECT 1
FROM #Instances i
LEFT JOIN M_Instance_Plate m ON i.PlateID = m.Instance_Plate_Plate_ID_LNK
WHERE m.Instance_Plate_Room_Instance_ID_LNK = p.Instance_Plate_Room_Instance_ID_LNK
AND m.Instance_Plate_Plate_ID_LNK IS NULL
UNION ALL
SELECT 1
FROM #Instances i
JOIN M_Instance_Plate m ON i.PlateID = m.Instance_Plate_Plate_ID_LNK
WHERE m.Instance_Plate_Room_Instance_ID_LNK = p.Instance_Plate_Room_Instance_ID_LNK
GROUP BY i.PlateID
HAVING COUNT(*) != 1
)

Try this instead. Is equivalent expression (checks if plateId exists on your table variable and then matches with your variable #numberofplates)
HAVING #NumberofPlates = (
SELECT COUNT(1) AS cc
FROM #Instances AS a
WHERE a.PlateID = Instance_Plate_Plate_ID_LNK
)

Related

Conditionally modify query based on parameter

I have this query (something like a case statement which I can use and fix it)
select *
from mytable
where 1=1
and (isNull(ID, 0) = 0 OR UtilityID IN (9,40))
I also want to add another statement
select *
from mytable
where 1=1
and UtilityID NOT IN (9,40)
Everything is happening in a procedure, so want to use a variable like declare #something so if that is passed as 1, use the first statement and the if 0 is passed, use the latter one.
While I appreciate the genius in Dale's answer I find this more readable:
IF #something = 0
BEGIN
select *
from mytable
where ID IS NULL OR ID = 0 OR UtilityID IN (9,40);
END
IF #something = 1
BEGIN
select *
from mytable
where UtilityID NOT IN (9,40);
END
It's procedure code, so use IF to direct the control flow. Also expanded and simplified your where clauses
I think I understand your logic, ignoring the 1=1 (which does nothing) you want to only allow id = 0 when #something = 1. This should do it:
declare #something bit = 0;
declare #mytable table (ID int, UtilityID int);
insert into #mytable (ID, UtilityID)
select 0, 1 union all
select 1, 2 union all
select 2, 9 union all
select 3, 40;
select *
from #mytable
where (
(#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
or (#something = 0 and (UtilityID not in (9,40)))
);
A more performant approach for a larger query could be:
select *
from #mytable
where (#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
union all
select *
from #mytable
where (#something = 0 and (UtilityID not in (9,40)));
PS: Hopefully your ID cannot ever by null - it should have a constraint on it.

A SQL query to count how many attributes a record has in common with another record?

I am trying to write a SQL query to take the count of columns with equal value in my schema for each row by comparison to a single record.
Example:
record1: 1, 0, 1, 0, 0
record2: 0, 0, 0, 0, 1
record3: 0, 0, 1, 0, 0
record1 has 2 attributes in common with record2, go through the entire table and order by number of attributes each record has in common with record1
Is there a way to write a SQL statement that will do this? I have only found ways to compare each row and specify which attributes must be of equal value.
You can do:
select t.*,
((case when t.col1 = t1.col1 then 1 else 0 end) +
(case when t.col2 = t1.col2 then 1 else 0 end) +
(case when t.col3 = t1.col3 then 1 else 0 end) +
. . .
) as num_in_common
from t cross join
t t1
where t1.id = 1; -- or however you define "record1"
order by num_in_common desc;
Here's a nice routine you can use in SQL Server that will do this if you'd like. Replace #temp with your table name:
declare #holding table (id int, col1 int, col2 int, col3 int, col4 int, col5 int, num_in_common int)
declare #iterator int = 1
declare #row1col1 int, #row1col2 int, #row1col3 int, #row1col4 int ,#row1col5 int
while #iterator<=(select max(id) from #temp)
begin
if #iterator=1
select #row1col1=col1, #row1col2=col2, #row1col3=col3, #row1col4=col4 ,#row1col5=col5
from #temp where id=#iterator
else
insert #holding
select *, case when col1-#row1col1 = 0 then 1 else 0 end +
case when col2-#row1col2 = 0 then 1 else 0 end +
case when col3-#row1col3 = 0 then 1 else 0 end +
case when col4-#row1col4 = 0 then 1 else 0 end +
case when col5-#row1col5 = 0 then 1 else 0 end
from #temp where id=#iterator
set #iterator=#iterator+1
end
select * from #holding

Select non-existing rows

Let say I have a table:
ColumnA ColumnB
---------------------------------
1 10.75
4 1234.30
6 2000.99
How can I write a SELECT query that will result in the following:
ColumnA ColumnB
---------------------------------
1 10.75
2 0.00
3 0.00
4 1234.30
5 0.00
6 2000.99
You can use a CTE to create a list of numbers from 1 to the maximum value in your table:
; with numbers as
(
select max(ColumnA) as nr
from YourTable
union all
select nr - 1
from numbers
where nr > 1
)
select nr.nr as ColumnA
, yt.ColumnB
from numbers nr
left join
YourTable yt
on nr.nr = yt.ColumnA
order by
nr.nr
option (maxrecursion 0)
See it working at SQL Fiddle.
Please try:
declare #min int, #max int
select #min=MIN(ColumnA), #max=MAX(ColumnA) from tbl
select
distinct number ColumnA,
isnull(b.ColumnB, 0) ColumnB
from
master.dbo.spt_values a left join tbl b on a.number=b.ColumnA
where number between #min and #max
Create a TallyTable (or NumbersTable) - see this question: What is the best way to create and populate a numbers table?
With that table create an insert statement:
INSERT INTO YourTable (ColumnA, ColumnB)
SELECT Number FROM NumberTable
WHERE
NOT EXISTS (SELECT 1 FROM YourTable WHERE NumberTable.Number = YourTable.ColumnA)
-- Adjust this value or calculate it with a query to the maximum of the source table
AND NumberTable.Number < 230130
DECLARE #t TABLE (ID INT,Val DECIMAL(10,2))
INSERT INTO #t (ID,Val) VALUES (1,10.75)
INSERT INTO #t (ID,Val) VALUES (4,6.75)
INSERT INTO #t (ID,Val) VALUES (7,4.75)
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 1
set #MaxNo = 10
set #IncrementStep = 1
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num,
CASE WHEN Val IS NOT NULL THEN Val ELSE 0.00 END AS NUMBER
from C
LEFT JOIN #t t
ON t.ID = c.Num
You could use a number-table or following trick to generate a sequence which you can LEFT OUTER JOIN with your table. I assume you want to determine the boundaries dynamically:
WITH Seq AS
(
SELECT TOP ((SELECT Max(ColumnA)FROM Table1) - (SELECT Min(ColumnA) FROM Table1) + 1)
Num = (SELECT Min(ColumnA) FROM Table1)+ Row_number() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects)
SELECT ColumnA = Seq.Num,
ColumnB = COALESCE(t.ColumnB ,0.00)
FROM Seq
LEFT OUTER JOIN Table1 t
ON Seq.Num = t.ColumnA
Demo with your sample.
Worth reading: http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I have my collect of table functions like these.
create function dbo.GetNumbers(#Start int, #End int)
returns #Items table
(
Item int
)
as
begin
while (#Start <= #End)
begin
insert into #Items
values (#Start)
set #Start = #Start + 1
end
return
end
Then I can use it to left join to my data table and every value will be there.
declare #min int, #max int
set #min = 10
set #max = 20
select gn.Item
from dbo.GetNumbers(#min, #max) gn
I have similar table functions for date ranges, times, timezones, etc.

How do I check whether data is there or not in more than one sql table in one script?

I have more than 3 sql tables.
now i'm trying to select count(*) from all tables but how can i do this?.
I want to know whether data is present in all tables or not
I need to check the row count from previous business day ~15% for any table and it sends an email alert
I tried like following please help me to complete
PROCEDURE [dbo].[SendEmail_WSOTableDataAlert]
AS
BEGIN
declare #err int
IF NOT EXISTS (select 1 from T1) OR
NOT EXISTS (select 1 from T2)
BEGIN
set #error=1
END
//here i need to show which table is having empty data how can i do this please help
SET #tableHTML = #tableHTML + +
'</TABLE>' + #EmailFooter;
#error =1
then
send mail
END
Select
case when count(*) = 0 then
'No rows'
else
'Has rows'
end
FROM
(
Select * from #table1
UNION ALL
Select * from #table2
UNION ALL
Select * from #table3
) t
UPDATE
This makes sure all of then have at least one row and fail if any of them does not have record
Select
case when count(*) = 0 then
'No rows'
else
'Has rows'
end
FROM
(
Select top 1 1 found from #table1
intersect
Select top 1 1 found from #table2
intersect
Select top 1 1 found from #table3
) t
You can try multiplying the flags indicating zero counts together. If any of them is zero, the result will be zero.
select (case when (select count(*) from table1)=0 then 0 else 1 end
*case when (select count(*) from table2)=0 then 0 else 1 end
*case when (select count(*) from table3)=0 then 0 else 1 end) as no_zeros
If you would like to know which table has all zeros, you could transform the query as follows:
select (case when (select count(*) from table1)=0 then 1 else 0 end
+case when (select count(*) from table2)=0 then 2 else 0 end
+case when (select count(*) from table3)=0 then 4 else 0 end
+case when (select count(*) from table4)=0 then 8 else 0 end) as no_zeros
Use powers of two (1, 2, 4, 8, 16, 32, and so on) as your flags. Ones 1 in the binary representation of the result will tell you which tables have no records.
(select count() from table1 )
union all
(select count() from table2 )
union all
(select count(*) from table3 )
And then loop through the rows of the result
declare #count1 int
select #count1 = count(*)
from table1
declare #count2 int
select #count2 = count(*)
from table2
declare #count3 int
select #count3 = count(*)
from table3
if (#count1 + #count2 + #count3 = 0)
--do something
else
--do something else
You can use the EXISTS keyword to efficiently check if there is any data in a table.
IF NOT EXISTS (SELECT 1 FROM Table1) OR NOT EXISTS (SELECT 1 FROM Table2) OR NOT EXISTS (SELECT 1 FROM Table3)
BEGIN
/* do something */
END

Getting average from 3 columns in SQL Server

I have a table with 3 columns (smallint) in SQL Server 2005.
Table Ratings
ratin1 smallint,
ratin2 smallint
ratin3 smallint
These columns can have values from 0 to 5.
How can I select the average value of these fields, but only compare fields where the value is greater then 0.
So if the column values are 1, 3 ,5 - the average has to be 3.
if the values are 0, 3, 5 - The average has to be 4.
This is kind of quick and dirty, but it will work...
SELECT (ratin1 + ratin2 + ratin3) /
((CASE WHEN ratin1 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin2 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin3 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin1 = 0 AND ratin2 = 0 AND ratin3 = 0 THEN 1 ELSE 0 END) AS Average
#mwigdahl - this breaks if any of the values are NULL. Use the NVL (value, default) to avoid this:
Sum columns with null values in oracle
Edit: This only works in Oracle. In TSQL, try encapsulating each field with an ISNULL() statement.
There should be an aggregate average function for sql server.
http://msdn.microsoft.com/en-us/library/ms177677.aspx
This is trickier than it looks, but you can do this:
SELECT dbo.MyAvg(ratin1, ratin2, ratin3) from TableRatings
If you create this function first:
CREATE FUNCTION [dbo].[MyAvg]
(
#a int,
#b int,
#c int
)
RETURNS int
AS
BEGIN
DECLARE #result int
DECLARE #divisor int
SELECT #divisor = 3
IF #a = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #b = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #c = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #divisor = 0
SELECT #result = 0
ELSE
SELECT #result = (#a + #b + #c) / #divisor
RETURN #Result
END
select
(
select avg(v)
from (values (Ratin1), (Ratin2), (Ratin3)) as value(v)
) as average
You can use the AVG() function. This will get the average for a column. So, you could nest a SELECT statement with the AVG() methods and then SELECT these values.
Pseudo:
SELECT col1, col2, col3
FROM (
SELECT AVG(col1) AS col1, AVG(col2) AS col2, AVG(col3) AS col3
FROM table
) as tbl
WHERE col1 IN (0, 3, 5)
etc.