I have two sql statements,
select * from UserTable where role='HOD'
select * from UserTable where role='Supervisor'
I want the results to be in a way such that if the first statement returns nothing, I want the second statement to run and if first statement returns something, second statement do not need to run. Is there a way to do it, be it in a stored procedure or a SQLQuery?
Executing two queries is more expensive than executing a single one that returns both result sets. It would be cheaper to filter the results on the client.
Even if you return all results in a single query, you can differentiate the two cases. For example, HOD always comes before Supervisor. You could use a ranking function like ROW_NUMBER() to assign a value to each row, depending on whether it matches HOD or Supervisor:
with a as (
select * ,row_number() over (partition by Role order by Role) as rn
from UserTable
where Role in ('HOD','Supervisor')
)
select *
from a
where rn=1
Another option is to combine a query that returns HOD with a query that returns Supervisor if HOD doesn't exist:
select *
from UserTable
where Role ='HOD'
UNION ALL
select *
from UserTable
where Role ='Supervisor'
AND NOT EXISTS (SELECT 1
from UserTable
where Role ='Supervisor')
The performance of both queries can be improved if Role is part of an index. The first query becomes equivalent to a simple index seek if the table has an index that covers all the returned fields. If the query returns only eg, ID, UserName, Role :
with a as (
select * ,row_number() over (partition by Role order by Role) as rn
from UserTable
where Role in ('HOD','Supervisor')
)
select ID,UserName, Role
from a
where rn=1
and the table has a covering index:
CREATE INDEX IX_UserTable_Roles ON UserTable (Role,ID,UserName)
The resulting execution plan is a single INDEX SEEK on the index
Try This
IF EXISTS (
SELECT 1 FROM UserTable WHERE ROLE = 'HOD'
)
BEGIN
SELECT * FROM UserTable WHERE ROLE = 'HOD'
END
ELSE BEGIN
SELECT * FROM UserTable WHERE ROLE = 'Supervisor'
END
This should do :
IF EXISTS(SELECT 1 FRom UserTable where role='HOD')
Begin
select * from UserTable where role='HOD'
END
ELSE BEGIN
select * from UserTable where role='Supervisor'
END
If you are using Transact SQL then a good page to read about this is https://learn.microsoft.com/en-us/sql/t-sql/language-elements/if-else-transact-sql
In the Transact SQL instance, the general version of what you are looking for is;
IF Boolean_expression
{ sql_statement | statement_block }
[ ELSE
{ sql_statement | statement_block } ]
Related
I'm using VS 2012 and SQL Server / SSIS.
I originally had a SQL task to check for duplicate values in a table:
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1);
The ? is because I am inserting a parameter value, and the result of the query is being assigned to a variable. It works fine if there is a duplicate.
When there are no duplicates, I get this message:
Single Row result set is specified, but no rows were returned
So, to get round this I now use an IF EXISTS, like the below:
IF EXISTS (SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1))
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1)
ELSE
SELECT 0 AS DupNI;
However, now I get the error:
No value given for one or more required parameters.
It appears because I am wrapping the statement in the IF EXISTS, I can no longer inject the parameter values via the ?
Why is this? How do I get around this issue?
Your current query will return multiple rows if there are duplicates, one for each duplicate (XMLFileID, NINumber) pair. If you only want to return a value which indicates whether there are any duplicates in the table, you could use your EXISTS clause as an expression:
SELECT CASE WHEN EXISTS(
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1)
) THEN 1 ELSE 0 END AS [Duplicates Exist]
Demo on dbfiddle
I am trying to run a basic select statement to return a list of job numbers. I want to then take that list of job numbers and crank them through 4 different select statement to see if I get any results. The point of this is to create a watchdog list to notify me if any parts from any jobs have not been created. Here is my code thus far:
--Search for all Jobs
--Use select statement here to return a list of job numbers
--Then crank through both of the following queries to see if I get any results
--Laser 1
SELECT * FROM db1.dbo.table1
WHERE PARTNAME NOT IN (SELECT PARTNAME FROM db2.Laser1.dbo.Part WHERE ORDERNO = #JobNumber)
AND DISTRICT = 1 AND ORDERNO = #JobNumber
--Laser 2
SELECT * FROM db1.dbo.table1
WHERE PARTNAME NOT IN (SELECT PARTNAME FROM db2.Laser2.dbo.Part WHERE ORDERNO = #JobNumber)
AND DISTRICT = 3 AND ORDERNO = #JobNumber
In short I want to use a select statement to return all jobs that are currently in proccess. Then replace the #JobNumber with that job and see if it returns anything.
Pretty sparse on details here but you definitely don't need a cursor or a loop for this. Something like this perhaps?
select *
from Jobs j
where j.PARTNAME not in
(
SELECT p1.PARTNAME
from db1.dbo.table1
left join db2.Laser1.dbo.Part p1 on p1.ORDERNO = #JobNumber AND DISTRICT = 1
UNION ALL
SELECT p2.PARTNAME
from db1.dbo.table1
left join db2.Laser2.dbo.Part p2 on p2.ORDERNO = #JobNumber AND DISTRICT = 3
)
I need some support with array type, because it is a new thing for me, so
I have a function:
create or replace type num_array as table of number;
create or replace function functionname(arr_in num_array)
return num_array is
tab num_array;
begin
select id_acc bulk collect into tab from (
SELECT a.id_acc
FROM (SELECT id_acc, parent_acc FROM account) a
connect by nocycle prior a.id_acc=a.parent_acc
start with id_acc in
(
select distinct ID_ACC
from (SELECT id_acc, parent_acc FROM account
) a
where parent_acc = id_acc
connect by nocycle prior a.parent_acc = a.id_acc or parent_acc is null
start with id_acc in (select parent_acc from table_name t,account a where t.id=a.id_acc)));
return tab;
end;
As an input I want to have an array of numbers (id). I want to connect that number (from input) with account table. It is in line:
start with id_acc in (select parent_acc from table_name t,account a where t.id=a.id_acc)));
I would like to join somehow table account with numbers from input,
I was trying to use table(tab()),account a but I got an error.
As output I would like to have result of select query so (return tab).
I'm not sure I understood what you want to achieve. Just to help you with the syntax, look at this:
start with id_acc in (select * from table(arr_in));
Below i have provided a small snippet which basically illustrates your issue with joining Nested Table type with Table.
CREATE OR REPLACE FUNCTION test_ntt_join
RETURN NUMBER_NTT
AS
lv_tab_num NUMBER_NTT;
lv_tab2 NUMBER_NTT;
BEGIN
SELECT LEVEL BULK COLLECT INTO lv_tab_num FROM DUAL CONNECT BY LEVEL < 10;
SELECT COLUMN_VALUE
BULK COLLECT INTO
lv_tab2
FROM TABLE(lv_tab_num) t,
EMP
WHERE emp.empno= t.column_value;
RETURN lv_tab2;
END;
------------------------------------------OUTPUT------------------------------------
select * from table(test_ntt_join);
COLUMN_VALUE
1
1
3
------------------------------------------OUTPUT------------------------------------
When I execute my "select union select", I get the correct number or rows (156)
Executed independently, select #1 returns 65 rows and select #2 returns 138 rows.
When I use this "select union select" with an Insert into, I get 203 rows (65+138) with duplicates.
I would like to know if it is my code structure that is causing this issue ?
INSERT INTO dpapm_MediaObjectValidation (mediaobject_id, username, checked_date, expiration_date, notified)
(SELECT FKMediaObjectId, CreatedBy,#checkdate,dateadd(ww,2,#checkdate),0
FROM dbo.gs_MediaObjectMetadata
LEFT JOIN gs_MediaObject mo
ON gs_MediaObjectMetadata.FKMediaObjectId = mo.MediaObjectId
WHERE UPPER([Description]) IN ('CAPTION','TITLE','AUTHOR','DATE PHOTO TAKEN','KEYWORDS')
AND FKMediaObjectId >=
(SELECT TOP 1 MediaObjectId
FROM dbo.gs_MediaObject
WHERE DateAdded > #lastcheck
ORDER BY MediaObjectId)
GROUP BY FKMediaObjectId, CreatedBy
HAVING count(*) < 5
UNION
SELECT FKMediaObjectId, CreatedBy,getdate(),dateadd(ww,2,getdate()),0
FROM gs_MediaObjectMetadata yt
LEFT JOIN gs_MediaObject mo
ON yt.FKMediaObjectId = mo.MediaObjectId
WHERE UPPER([Description]) = 'KEYWORDS'
AND FKMediaObjectId >=
(SELECT TOP 1 MediaObjectId
FROM dbo.gs_MediaObject
WHERE DateAdded > #lastcheck
ORDER BY MediaObjectId)
AND NOT EXISTS
(
SELECT *
FROM dbo.fnSplit(Replace(yt.Value, '''', ''''''), ',') split
WHERE split.item in (SELECT KeywordEn FROM gs_Keywords) or split.item in (SELECT KeywordFr FROM gs_Keywords)
)
)
I would appreciate any clues into resolving this problem ...
Thank you !
The UNION keyword should only return distinct records between the two queries. However, if I recall correctly, this is only true if the datatypes are the same. The date variables might be throwing that off. Depending on the collation type, whitespace might be handled differently as well. You might want to do a SELECT DISTINCT on the dpapm_MediaObjectValidation table after doing your insert, but be sure to trim whitespace from both sides in your comparison. Another approach is to do your first insert, then on your second insert, forgo the UNION altogether and do a manual EXISTS check to see if the items to be inserted already exist.
I have this query:
SELECT (SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)) AS TenantBalance, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
But there's a problem with it; there are other TenantID's that don't have transactions and I want to get those too.
For example, the transaction table has 3 rows for bob, 2 row for john and none for jane. I want it to return the sum for bob and john AND return 0 for jane. (or possibly null if there's no other way)
How can I do this?
Tables are like this:
Tenants
ID
Other Data
Transactions
ID
TenantID (fk to Tenants)
Other Data
(You didn't state your sql engine, so I'm going to link to the MySQL documentation).
This is pretty much exactly what the COALESCE() function is meant for. You can feed it a list, and it'll return the first non-null value in the list. You would use this in your query as follows:
SELECT COALESCE((SUM(tr.AmountPaid) - SUM(tr.AmountCharged)), 0) AS TenantBalance, te.ID
FROM tblTenant AS te
LEFT JOIN tblTransaction AS tr ON (tr.TenantID = te.ID)
GROUP BY te.ID;
That way, if the SUM() result would be NULL, it's replaced with zero.
Edited: I rewrote the query using a LEFT JOIN as well as the COALESCE(), I think this is the key of what you were missing originally. If you only select from the Transactions table, there is no way to get information about things not in the table. However, by using a left join from the Tenants table, you should get a row for every existing tenant.
Below is a full walkthrough of the problem. The function isnull has also been included to ensure that a balance of zero (rather than null) is returned for Tenants with no transactions.
create table tblTenant
(
ID int identity(1,1) primary key not null,
Name varchar(100)
);
create table tblTransaction
(
ID int identity(1,1) primary key not null,
tblTenantID int,
AmountPaid money,
AmountCharged money
);
insert into tblTenant(Name)
select 'bob' union all select 'Jane' union all select 'john';
insert into tblTransaction(tblTenantID,AmountPaid, AmountCharged)
select 1,5.00,10.00
union all
select 1,10.00,10.00
union all
select 1,10.00,10.00
union all
select 2,10.00,15.00
union all
select 2,15.00,15.00
select * from tblTenant
select * from tblTransaction
SELECT
tenant.ID,
tenant.Name,
isnull(SUM(Trans.AmountPaid) - SUM(Trans.AmountCharged),0) AS Balance
FROM tblTenant tenant
LEFT JOIN tblTransaction Trans ON
tenant.ID = Trans.tblTenantID
GROUP BY tenant.ID, tenant.Name;
drop table tblTenant;
drop table tblTransaction;
Select Tenants.ID, ISNULL((SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)), 0) AS TenantBalance
From Tenants
Left Outer Join Transactions Tenants.ID = Transactions.TenantID
Group By Tenents.ID
I didn't syntax check it but it is close enough.
SELECT (SUM(ISNULL(tblTransaction.AmountPaid, 0))
- SUM(ISNULL(tblTransaction.AmountCharged, 0))) AS TenantBalance
, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
I only added this because if you're intention is to take into account for one of the parts being null you'll need to do the ISNULL separately
Actually, I found an answer:
SELECT tenant.ID, ISNULL(SUM(trans.AmountPaid) - SUM(trans.AmountCharged),0) AS Balance FROM tblTenant tenant
LEFT JOIN tblTransaction trans
ON tenant.ID = trans.TenantID
GROUP BY tenant.ID