Getting the correct ROWCOUNT - sql

I have done a union of 2 query.When i individualy take the rowcounts of the 2 queries it shows me 1504 rows and 15 rows respectively.But when i take a total rowcount ,i still get 1504 rows.Am i missing something here ?
The query is:
SELECT DISTINCT T1.sys_tenant_id
FROM SO_CTRL T1, S_BU T2
WHERE T1.SYS_TENANT_ID = T2.ROW_ID AND T2.CUST_STATUS_CD = 'Active' AND
T1.OBJ_NAME = 'Opportunity' AND T1.CTRL_NAME != 'Primary Revenue Close Date'
UNION
SELECT DISTINCT T1.sys_tenant_id
FROM SO_CTRL T1, S_BU T2
WHERE T1.SYS_TENANT_ID = T2.ROW_ID AND T2.CUST_STATUS_CD = 'Active' AND
T1.OBJ_NAME = 'Opportunity' AND T1.CTRL_NAME = 'Primary Revenue Close Date' AND
(T1.default_value_expr IS NULL OR LTRIM(RTRIM(T1.default_value_expr)) = ''))

One possible explanation for what you are seeing is the 15 rows in the second queries already exist as duplicates in the 1504 rows from the first query.
The UNION operator will filter out duplicates, so if you want to end up with a row count of 1519, you can try using UNION ALL.

Related

Too many subselects in Oracle Query?

I'd like to know how I could improve the performance of the below query, since it is taking way too long to run, after all, it returns millions of rows... I'm a dummy when it comes to SQL...
SELECT CIAM.EXTERNAL_ID,
(SELECT NEW_CHARGES / 100
FROM BI_OWNER.CMF_BALANCE
WHERE ( ACCOUNT_NO, BILL_REF_NO ) = (SELECT ACCOUNT_NO,
MAX(BILL_REF_NO)
FROM BI_OWNER.CMF_BALANCE
WHERE
ACCOUNT_NO = CIAM.ACCOUNT_NO
GROUP BY ACCOUNT_NO))
"AMOUNT LAST BILL",
(SELECT 'ACTIVE DISCOUNT'
|| ' '
|| CCK.AVAIL_PERIODS
|| '/'
|| CC.TOTAL_PERIODS
FROM BI_OWNER.CUSTOMER_CONTRACT_KEY CCK,
BI_OWNER.CUSTOMER_CONTRACT CC
WHERE CC.PARENT_ACCOUNT_NO = CIAM.ACCOUNT_NO
AND CC.END_DT IS NULL
AND EXISTS (SELECT 1
FROM CONTRACT_TYPES
WHERE CONTRACT_TYPE = CC.CONTRACT_TYPE
AND PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3)
AND ROWNUM = 1
AND CCK.TRACKING_ID = CC.TRACKING_ID
AND CCK.TRACKING_ID_SERV = CC.TRACKING_ID_SERV) "DISCOUNT",
(SELECT CC.TOTAL_PERIODS
FROM BI_OWNER.CUSTOMER_CONTRACT_KEY CCK,
BI_OWNER.CUSTOMER_CONTRACT CC
WHERE CC.PARENT_ACCOUNT_NO = CIAM.ACCOUNT_NO
AND CC.END_DT IS NULL
AND EXISTS (SELECT 1
FROM CONTRACT_TYPES
WHERE CONTRACT_TYPE = CC.CONTRACT_TYPE
AND PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3)
AND ROWNUM = 1
AND CCK.TRACKING_ID = CC.TRACKING_ID
AND CCK.TRACKING_ID_SERV = CC.TRACKING_ID_SERV) "CYCLE"
,
(SELECT SUM(BALANCE_DUE)
FROM BI_OWNER.CMF_BALANCE
WHERE ACCOUNT_NO = CIAM.ACCOUNT_NO
AND PPDD_DATE < TRUNC(SYSDATE))
"DEBT"
FROM BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
WHERE EXTERNAL_ID_TYPE = 1
AND EXISTS (SELECT 1
FROM BI_OWNER.CMF
WHERE ACCOUNT_NO = CIAM.ACCOUNT_NO
AND PREV_CUTOFF_DATE > SYSDATE - 30)
I would recommend identifying the SQL id for the query then using the SQL Monitor Report as it will tell you exactly what the execution plan is and where the SQL is spending most of it's time.
A simple way to get the SQL Monitor Report from SQL*Plus follows:
spool c:\temp\SQL_Monitor_rpt.html
SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
alter session set "_with_subquery" = optimizer;
SELECT DBMS_SQLTUNE.report_sql_monitor(
sql_id => '&SQLID' ,
type => 'HTML',
report_level => 'ALL') AS report
FROM dual;
spool off
Basically, you need to know your table sizes and how to get the large tables to have data access via an index (e.g. index on columns found in the where clause).
Here is an initial stab and may provide significant improvement. Many of your queries were correlated subqueries being executed for every record. Instead, I tried to build pre-query aggregates per account number in the select from/join section. Query first, then I'll explain logic after.
SELECT
CIAM.EXTERNAL_ID,
CMF_BALANCE.New_Charges / 100.0 "AMOUNT LAST BILL",
CCKs.Discount,
CCKs.Cycle,
AcntLast30.SumBalance "DEBT"
FROM
(SELECT
CMF.Account_No,
max( Bal.Bill_Ref_No ) MaxBillRef,
sum( case when Bal.PPDD_Date < TRUNC(SYSDATE )
then Bal.Balance_Due else 0 end ) SumBalance
from
BI_OWNER.CMF
JOIN BI_OWNER.CMF_BALANCE BAL
on CMF.Account_No = Bal.Account_No
where
CMF.PREV_CUTOFF_DATE > SYSDATE - 30
group by
CMF.Account_No ) AcntLast30
JOIN BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
on AcntLast30.Account_No = CIAM.Account_No
AND CIAM.EXTERNAL_ID_TYPE = 1
JOIN BI_OWNER.CMF_BALANCE
on AcntLast30.Account_No = CMFBalance.Account_No
AND AcntLast30.MaxBillRef = CMFBalance.Bill_Ref_No
JOIN
(select
CC.Parent_Account_No,
CC.TOTAL_PERIODS "CYCLE",
'ACTIVE DISCOUNT' || ' ' || CCK.AVAIL_PERIODS || '/' || CC.TOTAL_PERIODS "DISCOUNT"
FROM
BI_OWNER.CUSTOMER_CONTRACT CC
JOIN BI_OWNER.CUSTOMER_CONTRACT_KEY CCK
ON CC.TRACKING_ID = CCK.TRACKING_ID
AND CC.TRACKING_ID_SERV = CCK.TRACKING_ID_SERV
AND ROWNUM = 1
JOIN ( select distinct Contract_Type
FROM CONTRACT_TYPES
WHERE PLAN_ID_DISCOUNT IS NOT NULL
AND DURATION_UNITS = -3) CT
on CC.Contract_Type = CT.Contract_Type
WHERE
CC.END_DT IS NULL ) CCKs
on AcntLast30.Account_No = CCKs.Parent_Account_No
The initial "FROM" clause, I have a subquery because you appear to be only interested in accounts within the last 30 days. So, while I'm there, I am joining to your CMF_Balance table and getting the maximum Bill_Ref_No per account AND the sum of the balance when the PPDD_Date is less than the TRUNC(sysdate) which is your "DEBT" result column. So now we have the finite list of accounts you are interested in with the account, max bill on file and the balance due summed up.
(SELECT
CMF.Account_No,
max( Bal.Bill_Ref_No ) MaxBillRef,
sum( case when Bal.PPDD_Date < TRUNC(SYSDATE )
then Bal.Balance_Due else 0 end ) SumBalance
from
BI_OWNER.CMF
JOIN BI_OWNER.CMF_BALANCE BAL
on CMF.Account_No = Bal.Account_No
where
CMF.PREV_CUTOFF_DATE > SYSDATE - 30
group by
CMF.Account_No ) AcntLast30
Next, simple join to the CIAM table to only get accounts for External_ID_Type = 1. This too could have been merged into the query above for "AcntLast30" alias result.
JOIN BI_OWNER.CUSTOMER_ID_ACCT_MAP CIAM
on AcntLast30.Account_No = CIAM.Account_No
AND CIAM.EXTERNAL_ID_TYPE = 1
Now, since the "AcntLast30" query has the account and max bill reference we then join back to the CMF_Balance on the account and bill reference # once thus giving us the CMF_BALANCE.New_Charges / 100.0 "AMOUNT LAST BILL"
JOIN BI_OWNER.CMF_BALANCE
on AcntLast30.Account_No = CMFBalance.Account_No
AND AcntLast30.MaxBillRef = CMFBalance.Bill_Ref_No
Finally the subquery alias result "CCKs". Since the Discount and Cycle use the same query/subquery/exists, I just ran it once that qualified on the discounts types and pulled the Account_No for the JOIN condition. Now we have the Discount and Cycle values per account.
If you are returning so many rows, I believe the performance gained by grabbing these pre-query aggregates once up-front and joining to by the account will be much faster than that of each time individually subquerying at every row.
There was a reference to ROWNUM without any table/alias reference so I am not sure the impact of that one within the query.
Final note. For things like the discount that may not be applicable, you may need to change it to a LEFT JOIN, of which those values would show as NULL. But without knowing the extent of data, Cartesian products of 1:many entries in given tables, I think this will work well for you. For the most part it looked like everything was resulting in only one record qualified per account where higher importance on join (such as the max bill reference).

Union a table with a synthetic table to get result even if query result is empty

I have a data table which i want to select some fields filtering by date.
If the result is empty, based on the sysdate I need to decide if it is ok or not.
To be able to do that I am creating a synthetic table with a flag field which I expect to be populated in result set even if there is no data in my actual table at that date.
WITH const AS (
SELECT
'NAME 1' AS name,
(CASE WHEN TO_TIMESTAMP(TO_CHAR(CURRENT_TIMESTAMP, 'HH24:MI:SS'), 'HH24:MI:SS') < TO_TIMESTAMP('01:00:00', 'HH24:MI:SS') THEN 1 ELSE 0 END) AS flag
FROM
Data_Table
UNION
SELECT
'ANY NAME' AS name,
(CASE WHEN TO_TIMESTAMP(TO_CHAR(CURRENT_TIMESTAMP, 'HH24:MI:SS'), 'HH24:MI:SS') < TO_TIMESTAMP('01:00:00', 'HH24:MI:SS') THEN 1 ELSE 0 END) AS flag )
SELECT Data_Table.sysname, const.flag FROM const LEFT OUTER JOIN Data_Table ON Data_Table.sysname = const.name WHERE Data_Table.date=TO_CHAR(sysdate, 'DD-MM-YYYY')
I expect to get results like below:
sysname flag
Name1 1
(null) 1
But getting empty result if there is no data with that date.
Here's general example. If I understand correctly you need to be able to return a value even if query returns no rows:
SELECT table_name FROM all_tables
WHERE table_name = 'YOUR_TABLE'
UNION ALL
SELECT '1' table_name FROM dual
WHERE NOT EXISTS
(
SELECT table_name FROM all_tables
WHERE table_name = 'YOUR_TABLE'
)
/
The result is 1 as there is no table as 'YOUR_TABLE' in my database. But if I put a valid table name then i will get results from the top query. If not then always get 1 or any other value from second query. The table names in both queries must be the same. The second one is a copy of the top one.
I see the problem in your last statement that is
SELECT Data_Table.sysname, const.flag
FROM const
LEFT OUTER JOIN Data_Table
ON Data_Table.sysname = const.name
WHERE Data_Table.date=TO_CHAR(sysdate, 'DD-MM-YYYY')-- Here is the problem
You are doing a left outer join and then filtering the data using where condition, the where condition will never be true in this case. You need to push that condition into the join so that it does not affect the overall result:
SELECT Data_Table.sysname, const.flag
FROM const
LEFT OUTER JOIN Data_Table
ON Data_Table.sysname = const.name
AND Data_Table.date=TO_CHAR(sysdate, 'DD-MM-YYYY')

SQL when one column has duplicate rows, then select row where other column is the min value

I have this table
mt.id, mt.otherId, mt.name, mt.myChar, mt.type
1 10 stack U "question"
2 10 stack D
3 30 stack U "question"
4 10 stack U "whydownvotes"
And I want only
rows with id 2 and 3 returned (without using the id, otherid as parameter) and ensuring name and type are matching against parameters. And when there is a duplicate otherId = then return the row with min myChar value. So far I have this :
select mt.* from myTable mt
where (mt.myChar = 'U' AND (mt.name = 'stack' AND mt.type LIKE '%question%'))
or (mt.myChar = 'D' and mt.name = 'stack')
So where otherID is 10, I want the row with min char value 'D'. I am going to need a subquery or group using min(myChar) ... ?
How do i remove the first row from the sql fiddle (without using the id):
http://sqlfiddle.com/#!9/c579a/1
edit
Jeepers, whats with the downvotes, its clear question isn't it ? There is even a sql fiddle.
If this is SQL Server, then you can do it in two steps like this:
WITH filtered AS (
SELECT
mt.*,
minType = MIN(mt.type) OVER (PARTITION BY mt.otherId)
FROM
dbo.myTable AS mt
WHERE (mt.myChar = 'U' AND mt.name = 'stack' AND mt.type LIKE '%question%')
OR (mt.myChar = 'D' AND mt.name = 'stack')
)
SELECT
id,
otherId,
name,
myChar,
type
FROM
filtered
WHERE
type = minType
;
The filtered subquery is basically your current query but with an additional column that holds minimum type values per otherId. The main query filters the filtered set further based on the type = minType condition.
I am assuming you want is a groupwise maximum, one of the most commonly-asked SQL questions You can try as , This query should work on any DBMS. But If you are using the SQL SERVER then you can use the Row_Number() which is very easy to use.Here myTable is your table.
SELECT t0.*
FROM myTable AS t0
LEFT JOIN myTable AS t1 ON t0.otherId = t1.otherId AND t1.myChar < t0.myChar
WHERE t1.myChar IS NULL;
Here is sql fiddle

Query minus problems?

My query is not returning the expected amount of records
the first part returns 50.000 records
the second part (below the minus) returns 30.000
so as conclusion my minus should return 20.000 records.
only this is not happening more records are removed 21.000.
-- edit --
the count returns more rows that expected, these returned records will in a later step be removed.
Anyone suggestions?
-- select count(*) from (
SELECT
loc.ITEM,
loc.loc
FROM ITEM_LOC loc
WHERE LOC NOT IN(101,104,107,115,116,117)
and loc.status = 'A'
and primary_supp in (select supplier from item_supplier where supp_discontinue_date >= sysdate)
-- );
minus
--; select count(*) from (
select distinct item, store from (
SELECT
siv.ITEM,
sto.store
FROM DC_CCN190_SID_VTB siv
JOIN DC_STORE_RANGING str ON siv.dpac = str.dpac
join store sto on sto.store_name_secondary = cast(str.loc as varchar2(150 byte))
where sto.store_close_date >= sysdate
union
SELECT
pim.ITEM,
sto.store
FROM dc_pim_export_vert PIM
JOIN DC_STORE_RANGING str ON PIM.dpac = str.dpac
join store sto on sto.store_name_secondary = cast(str.loc as varchar2(150 byte))
where PIM.artikel_type_lms = 'D1'
and sto.store_close_date >= sysdate
)
-------------------------------------------------------------------------------------
count returns 50.000
select count(*) from (
SELECT
loc.ITEM,
loc.loc
FROM ITEM_LOC loc
WHERE LOC NOT IN(101,104,107,115,116,117)
and loc.status = 'A'
and primary_supp in (select supplier from item_supplier where supp_discontinue_date >= sysdate));
count returns 30.000
select count(*) from (
select distinct item, store from (
SELECT
siv.ITEM,
sto.store
FROM DC_CCN190_SID_VTB siv
JOIN DC_STORE_RANGING str ON siv.dpac = str.dpac
join store sto on sto.store_name_secondary = cast(str.loc as varchar2(150 byte))
where sto.store_close_date >= sysdate
union
SELECT
pim.ITEM,
sto.store
FROM dc_pim_export_vert PIM
JOIN DC_STORE_RANGING str ON PIM.dpac = str.dpac
join store sto on sto.store_name_secondary = cast(str.loc as varchar2(150 byte))
where PIM.artikel_type_lms = 'D1'
and sto.store_close_date >= sysdate
);
So the minus should return 20.000 right?
A = 50000. B = 30000. A - B = 21000. What is B - A? I'm expecting 1000. That is, there are 1000 records in B that are not included in A. In a simple case,
A returns 4 records
Jim
Bob
Mary
Samantha
B returns 3 records
Bob
Mary
Josephine
A - B returns 2 records:
Jim
Samantha
B - A returns 1 record:
Josephine
Use the
select count(*)
in both queries and compare the result with
select distinct count(*)
There should be a difference!
From HERE, "Additionally, if there are two identical rows in table_A, and that same row exists in table_B, BOTH rows from table_A will be removed from the result set."
If there's no difference, then only one explanation:
MINUS is a SQL set operation that selects elements from the first
table and then removes rows that are also returned by the second
SELECT statement.
Not all rows from the second second are present in the first one.

Fetch unique combinations of two field values

Probably it has been asked before but I cannot find an answer.
Table Data has two columns:
Source Dest
1 2
1 2
2 1
3 1
I trying to come up with a MS Access 2003 SQL query that will return:
1 2
3 1
But all to no avail. Please help!
UPDATE: exactly, I'm trying to exclude 2,1 because 1,2 already included. I need only unique combinations where sequence doesn't matter.
For Ms Access you can try
SELECT DISTINCT
*
FROM Table1 tM
WHERE NOT EXISTS(SELECT 1 FROM Table1 t WHERE tM.Source = t.Dest AND tM.Dest = t.Source AND tm.Source > t.Source)
EDIT:
Example with table Data, which is the same...
SELECT DISTINCT
*
FROM Data tM
WHERE NOT EXISTS(SELECT 1 FROM Data t WHERE tM.Source = t.Dest AND tM.Dest = t.Source AND tm.Source > t.Source)
or (Nice and Access Formatted...)
SELECT DISTINCT *
FROM Data AS tM
WHERE (((Exists (SELECT 1 FROM Data t WHERE tM.Source = t.Dest AND tM.Dest = t.Source AND tm.Source > t.Source))=False));
your question is asked incorrectly. "unique combinations" are all of your records. but i think you mean one line per each Source. so it is:
SELECT *
FROM tab t1
WHERE t1.Dest IN
(
SELECT TOP 1 DISTINCT t2.Dest
FROM tab t2
WHERE t1.Source = t2.Source
)
SELECT t1.* FROM
(SELECT
LEAST(Source, Dest) AS min_val,
GREATEST(Source, Dest) AS max_val
FROM table_name) AS t1
GROUP BY t1.min_val, t1.max_val
Will return
1, 2
1, 3
in MySQL.
To eliminate duplicates, "select distinct" is easier than "group by":
select distinct source,dest from data;
EDIT: I see now that you're trying to get unique combinations (don't include both 1,2 and 2,1). You can do that like:
select distinct source,dest from data
minus
select dest,source from data where source < dest
The "minus" flips the order around and eliminates cases where you already have a match; the "where source < dest" keeps you from removing both (1,2) and (2,1)
Use this query :
SELECT distinct * from tabval ;