to frame cost effective query - sql-server-2005

Could you please help me out in building a Query. I Have a table as below
Id Info_Id Type
1 2 2
2 6 2
3 5 3
4 8 3
5 2 3
6 2 2
7 5 2
8 8 2
9 5 2
10 8 2
11 8 2
12 5 3
13 6 3
14 8 3
a query need to be framed so as to group by "Info_Id".
I need output as below for eg:
Info_Id CountOfRec Type2 Type3
2 3 2 1
5 4 2 2
6 2 1 1
8 5 3 2
I Tried as below but I m not able to get the efficient output
select Info_Id, count(Id)as CountOfRec,
(select count(Id)from tbl_TypeInfo where Info_Id = 5 AND Type = 2) as Type2,
(select count(Id)from tbl_ TypeInfo where Info_Id = 5 AND Type = 3) as Type3
from tbl_TypeInfo
where Info_Id = 5
group by Info_Id
output was this ,
Info_Id CountOfRec Type2 Type3
5 4 2 2
(I have to loop for each “Info_id” to get desired OP, there is thousand of records and its time consuming)
I wanted to get the highlighted output from the table. The query I have framed is not efficient and there would be good solution for this can you help me out.

You can use the CASE expression to only count the rows for a specific type:
SELECT Info_Id,
COUNT(*) AS CountOfRec,
COUNT(CASE WHEN Type = 2 THEN 1 ELSE NULL END) AS Type2
COUNT(CASE WHEN Type = 3 THEN 1 ELSE NULL END) AS Type3
FROM tbl_TypeInfo
GROUP BY Info_Id
Add a WHERE Info_Id = 5 to retrieve results for a specific ID only.
Update: as per comments, if you do not store a table of ID's, You need to change your IN(..) list to a virtual "table":
SELECT vt.id,
COUNT(*) AS CountOfRec,
COUNT(CASE WHEN Type = 2 THEN 1 ELSE NULL END) AS Type2,
COUNT(CASE WHEN Type = 3 THEN 1 ELSE NULL END) AS Type3
FROM (
SELECT 1 id
UNION SELECT 2
UNION SELECT 3
UNION SELECT 5
UNION SELECT 8
) AS vt LEFT JOIN tbl_TypeInfo ON vt.id = tbl_TypeInfo.Info_Id
GROUP BY vt.id

You can use SQL Server's PIVOT operator
SELECT Info_ID
, CountOfRec = [2] + [3]
, Type2 = [2]
, Type3 = [3]
FROM (
SELECT *
FROM (
SELECT *
FROM tbl_TypeInfo
) s
PIVOT (COUNT(Id) FOR Type IN ([2], [3])) pvt
) q
Test
;WITH tbl_TypeInfo AS (
SELECT [Id] = 1, [Info_Id] = 2, [Type] = 2
UNION ALL SELECT 2, 6, 2
UNION ALL SELECT 3, 5, 3
UNION ALL SELECT 4, 8, 3
UNION ALL SELECT 5, 2, 3
UNION ALL SELECT 6, 2, 2
UNION ALL SELECT 7, 5, 2
UNION ALL SELECT 8, 8, 2
UNION ALL SELECT 9, 5, 2
UNION ALL SELECT 1, 8, 2
UNION ALL SELECT 1, 8, 2
UNION ALL SELECT 1, 5, 3
UNION ALL SELECT 1, 6, 3
UNION ALL SELECT 1, 8, 3
)
SELECT Info_ID
, CountOfRec = [2] + [3]
, Type2 = [2]
, Type3 = [3]
FROM (
SELECT *
FROM (
SELECT *
FROM tbl_TypeInfo
) s
PIVOT (COUNT(Id) FOR Type IN ([2], [3])) pvt
) q

This is a bit crazy, but if the types are always, always 2 and 3, this could be threated as an equation, where count(type2)+count(type3)=count(*) and 2*count(type2)+3*count(type3)=sum(*) so you could to something like
SELECT 3*c-s as Type2Count, s-2*c as Type3Count
FROM (SELECT COUNT(*) as C, SUM(Type) as S
FROM tbl_TypeInfo
WHERE Info_Id = 5) SourceTable
This will be lightning fast, however, this is extemely breakable!!!!
If ever, the types are changed, or a type is added, this will not work.

Related

create sequence of numbers on grouped column in Oracle

Consider below table with column a,b,c.
a b c
3 4 5
3 4 5
6 4 1
1 1 8
1 1 8
1 1 0
1 1 0
I need a select statement to get below output. i.e. increment column 'rn' based on group of column a,b,c.
a b c rn
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
You can use the DENSE_RANK analytic function to get a unique ID for each combination of A, B, and C. Just note that if a new value is inserted into the table, the IDs of each combination of A, B, and C will shift and may not be the same.
Query
WITH
my_table (a, b, c)
AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
SELECT t.*, DENSE_RANK () OVER (ORDER BY b desc, c desc, a) as rn
FROM my_table t;
Result
A B C RN
____ ____ ____ _____
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
As a starter: for your answer to make sense at all, you need a column that defines the ordering of the rows. Let me assume that you have such column, called id.
Then, you can use window functions:
select a, b, c,
sum(case when a = lag_a and b = lag_b and c = lag_c then 0 else 1 end) over(order by id) rn
from (
select t.*,
lag(a) over(order by id) lag_a,
lag(b) over(order by id) lag_b,
lag(c) over(order by id) lag_c
from mytable t
) t
Assuming you have some way of ordering your rows, then you can use MATCH_RECOGNIZE:
SELECT a, b, c, rn
FROM table_name
MATCH_RECOGNIZE (
ORDER BY id
MEASURES MATCH_NUMBER() AS rn
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW EQUAL_ROWS* )
DEFINE EQUAL_ROWS AS (
EQUAL_ROWS.a = PREV( EQUAL_ROWS.a )
AND EQUAL_ROWS.b = PREV( EQUAL_ROWS.b )
AND EQUAL_ROWS.c = PREV( EQUAL_ROWS.c )
)
)
So, for your test data:
CREATE TABLE table_name ( id, a, b, c ) AS
SELECT 1, 3, 4, 5 FROM DUAL UNION ALL
SELECT 2, 3, 4, 5 FROM DUAL UNION ALL
SELECT 3, 6, 4, 1 FROM DUAL UNION ALL
SELECT 4, 1, 1, 8 FROM DUAL UNION ALL
SELECT 5, 1, 1, 8 FROM DUAL UNION ALL
SELECT 6, 1, 1, 0 FROM DUAL UNION ALL
SELECT 7, 1, 1, 0 FROM DUAL;
Outputs:
A | B | C | RN
-: | -: | -: | -:
3 | 4 | 5 | 1
3 | 4 | 5 | 1
6 | 4 | 1 | 2
1 | 1 | 8 | 3
1 | 1 | 8 | 3
1 | 1 | 0 | 4
1 | 1 | 0 | 4
db<>fiddle here
It can also be done without any ordering, by getting the distinct groups and numbering each group. Borrowing the first part from EJ Egjed:
WITH my_table (a, b, c) AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
, groups as (select distinct a, b, c
from my_table)
, groupnums as (select rownum as num, a, b, c
from groups)
select a, b, c, num
from my_table join groupnums using(a,b,c);

Filtering SQL using a oracle database

I would like to know if the following is possible
For example I have a shoe factory. In this factory I have a production line, Every step in this production line is recorded into the oracle database.
if the shoe has completed a production step the result is = 1
example table
Shoe_nr production step result
1 1 1
1 2 1
1 3
2 1 1
2 2 1
2 3
3 1
3 2
3 3
Now the question, is it possible to filter out production step 3 where only the shoes have passed production step 2 which is equal to 1 in result.
I know if it can be done it's probably very easy but if you dont know i found out it's a little bit tricky.
Thanks,
Chris
Yes, you can do it with IN and a Subselect
select *
from shoes
where shoe.id in (
select shoe.id
from shoes
where production_step = 2
and result = 1
)
and production_step = 3
This might be one option; see comments within code (lines #1 - 12 represent sample data; you already have that and don't type it. Query you might be interested in begins at line #13).
SQL> with shoes (shoe_nr, production_step, result) as
2 -- sample data
3 (select 1, 1, 1 from dual union all
4 select 1, 2, 1 from dual union all
5 select 1, 3, null from dual union all
6 select 2, 1, 1 from dual union all
7 select 2, 2, 1 from dual union all
8 select 2, 3, null from dual union all
9 select 3, 1, null from dual union all
10 select 3, 2, null from dual union all
11 select 3, 3, null from dual
12 ),
13 -- which shoes' production step #3 should be skipped?
14 skip as
15 (select shoe_nr
16 from shoes
17 where production_step = 2
18 and result = 1
19 )
20 -- finally:
21 select a.shoe_nr, a.production_step, a.result
22 from shoes a
23 where (a.shoe_nr, a.production_step) not in (select b.shoe_nr, 3
24 from skip b
25 )
26 order by a.shoe_nr, a.production_step;
SHOE_NR PRODUCTION_STEP RESULT
---------- --------------- ----------
1 1 1
1 2 1
2 1 1
2 2 1
3 1
3 2
3 3
7 rows selected.
SQL>
If you just want the shoe_nr that satisfy the condition, you can use aggregation and a having clause:
select shoe_nr
from mytable
group by shoe_nr
having
max(case when production_step = 2 then result end) = 0
and max(case when production_step = 3 then 1 end) = 1
If you want the entire row corresponding to this shoe_nr at step 3, use window functions instead:
select 1
from (
select
t.*,
max(case when production_step = 2 then result end)
over(partition by shoe_nr) as has_completed_step_2
from mytable t
) t
where production_step = 3 and has_completed_step_2 = 0

Group the column value based on selective rows for an id

I have a table which have 4 dimensions for a foreignid.
I want to find unique combination based on 2 dimensions.
TABLE1
-----------------------------
ID NAME VALUE TABLE2ID
-----------------------------
1 TYPE 10 1
2 DIR IN 1
3 STATE MA 1
4 COUNT 100 1
5 TYPE 10 2
6 DIR IN 2
7 STATE SA 2
8 COUNT 200 2
9 TYPE 20 3
10 DIR OUT 3
11 STATE MA 3
12 COUNT 300 3
-----------------------------
Here, I want the TABLE2IDs based on the combination of TYPE and DIR rows which is unique.
So, here if you aggregate the row values based on TYPE and DIR you will get
-----------------------------
TYPE DIR TABLE2ID
-----------------------------
10 IN 1
10 IN 2
20 OUT 3
-----------------------------
Note:
The above question is answered
Additional Question related to this.
I have another table which have the count for table2 id based on hour.
I want to group all the count for all hours in a day for unique combination in table1(Don't worry about table 2 structure).
TABLE3
-----------------------------
ID TIME COUNT TABLE2ID
-----------------------------
1 2016101601 10 1
2 2016101602 20 1
3 2016101603 30 1
4 2016101604 40 1
5 2016101601 10 2
6 2016101602 20 2
7 2016101603 30 2
8 2016101604 40 2
9 2016101601 10 3
10 2016101602 20 3
11 2016101603 30 3
12 2016101604 40 3
-----------------------------
Here, I want the output be grouped based on unique value of table 1 according to type and name(regardless of table2id)
----------------------------------
TYPE DIR DATE COUNT
----------------------------------
10 IN 20161016 200
20 OUT 20161016 100
---------------------------------
Use a PIVOT:
Oracle Setup:
CREATE TABLE table1 ( id, name, value, table2id ) AS
SELECT 1, 'TYPE', '10', 1 FROM DUAL UNION ALL
SELECT 2, 'DIR', 'IN', 1 FROM DUAL UNION ALL
SELECT 3, 'STATE', 'MA', 1 FROM DUAL UNION ALL
SELECT 4, 'COUNT', '100', 1 FROM DUAL UNION ALL
SELECT 5, 'TYPE', '10', 2 FROM DUAL UNION ALL
SELECT 6, 'DIR', 'IN', 2 FROM DUAL UNION ALL
SELECT 7, 'STATE', 'SA', 2 FROM DUAL UNION ALL
SELECT 8, 'COUNT', '200', 2 FROM DUAL UNION ALL
SELECT 9, 'TYPE', '20', 3 FROM DUAL UNION ALL
SELECT 10, 'DIR', 'OUT', 3 FROM DUAL UNION ALL
SELECT 11, 'STATE', 'MA', 3 FROM DUAL UNION ALL
SELECT 12, 'COUNT', '300', 3 FROM DUAL;
Query:
SELECT *
FROM ( SELECT name, value, table2id FROM table1 )
PIVOT ( MAX(value) FOR name IN ( 'TYPE' AS type, 'DIR' AS DIR ) );
Output:
TABLE2ID TYPE DIR
-------- ---- ---
1 10 IN
2 10 IN
3 20 OUT
Or as an alternative:
SELECT table2id,
MAX( CASE WHEN name = 'TYPE' THEN value END ) AS type,
MAX( CASE WHEN name = 'DIR' THEN value END ) AS dir
FROM table1
GROUP BY table2id;
You could join two subqueries, one that selects the types and one that selects the dirs for the same id:
SELECT type, dir, a.table2id
FROM (SELECT value AS type, table2id
FROM table1
WHERE name = 'TYPE') a
JOIN (SELECT value AS dir, table2id
FROM table1
WHERE name = 'DIR') b ON a.table2id = b.table2id

SQL pad query result for missing groups

Assume the following table:
TableA:
ID GroupName SomeValue
1 C 1
2 C 1
2 B 1
2 A 1
I need to construct a query that selects the following result:
ID GroupName SomeValue
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
The GroupName is actually derived from TableA column's CASE expression and can take only 3 values: A, B, C.
Are the analytic functions the way to go?
EDIT
Sorry, for not mentioning it, but the ID could consist of multiple columns. Consider this example:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 2 C 1
2 2 C 1
2 2 B 1
2 2 A 1
I need to pad SomeValue with 0 for each unique combination ID1+ID2. So the result should be like this:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
EDIT2
Seems like solution, proposed by #Laurence should work even for multiple-column 'ID'. I couldn't rewrite the query proposed by #Nicholas Krasnov to conform to this requirement. But could somebody compare these solutions performance-wise? Will the analytic function work faster than 'cross join + left outer join'?
To fill in gaps, you could write a similar query using partition by clause of outer join:
SQL> with t1(ID,GroupName,SomeValue) as
2 (
3 select 1, 'C', 1 from dual union all
4 select 2, 'C', 1 from dual union all
5 select 2, 'B', 1 from dual union all
6 select 2, 'A', 1 from dual
7 ),
8 groups(group_name) as(
9 select 'A' from dual union all
10 select 'B' from dual union all
11 select 'C' from dual
12 )
13 select t1.ID
14 , g.group_name
15 , nvl(SomeValue, 0) SomeValue
16 from t1
17 partition by (t1.Id)
18 right outer join groups g
19 on (t1.GroupName = g.group_name)
20 order by t1.ID asc, g.group_name desc
21 ;
ID GROUP_NAME SOMEVALUE
---------- ---------- ----------
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
6 rows selected
UPDATE: Response to the comment.
Specify ID2 column in the partition by clause as well:
SQL> with t1(ID1, ID2, GroupName,SomeValue) as
2 (
3 select 1, 1, 'C', 1 from dual union all
4 select 1, 2, 'C', 1 from dual union all
5 select 2, 2, 'C', 1 from dual union all
6 select 2, 2, 'B', 1 from dual union all
7 select 2, 2, 'A', 1 from dual
8 ),
9 groups(group_name) as(
10 select 'A' from dual union all
11 select 'B' from dual union all
12 select 'C' from dual
13 )
14 select t1.ID1
15 , t1.ID2
16 , g.group_name
17 , nvl(SomeValue, 0) SomeValue
18 from t1
19 partition by (t1.Id1, t1.Id2)
20 right outer join groups g
21 on (t1.GroupName = g.group_name)
22 order by t1.ID1, t1.ID2 asc , g.group_name desc
23 ;
ID1 ID2 GROUP_NAME SOMEVALUE
---------- ---------- ---------- ----------
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
9 rows selected
Select
i.Id1,
i.Id2,
g.GroupName,
Coalesce(a.SomeValue, 0) As SomeValue
From
(select distinct ID1, ID2 from TableA) as i
cross join
(select distinct GroupName from TableA) as g
left outer join
tableA a
on i.ID = a.ID and g.GroupName = a.GroupName
Order By
1,
2,
3 Desc

Need sql query for matching with three values

I have a table like below
CAccountID CID NetworkID
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
9 4 1
10 4 2
I need a query to select all CID having all 3 NetworkID(1,2,3) and don't need to display only 1 and 2 NetworkID.
Output should be like below,
CAccountID CID NetworkID
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
You can use GROUP BY with JOIN :
select t.*
from table t inner join
( select cid
from table
where NetworkID in (1,2,3)
group by cid
having count(distinct NetworkID) = 3
) tt
on tt.cid = t.cid;
Try this:
select * from my_table t
where exists(select 1 from my_table
where CID = t.CID and NetworkID in (1,2,3)
group by CID
having count(*) = 3)
Try this:
select * from <<tablename>> where cid in(select cid from <<tablename>> group by cid having count(*)=3).
Here the subquery will return you all thouse cid which have 3 rows in your table.
Or if you have more network ids then use of INTERSECT operator can be helpful:
select * from <<tablename>> where cid in (
select cid from <<tablename>> where NetworkID=1
INTERSECT
select cid from <<tablename>> where NetworkID=2
INTERSECT
select cid from <<tablename>> where NetworkID=3
);
INTERSECT operator basically returns all the rows common in the queries. Thus, your data unpredicatbility can be handled in this way
Try xml path.
SELECT *
FROM Table_Name B
WHERE (SELECT [text()] = A.Network FROM Table_Name A WHERE A.CID = B.CID
ORDER BY CID, CAAccount FOR XML PATH('')) = 123
CTE Demo:
; WITH CTE(CAAccount, CID, Network) AS
(
SELECT 1 , 1, 1 UNION ALL
SELECT 2 , 1, 2 UNION ALL
SELECT 3 , 2, 1 UNION ALL
SELECT 4 , 2, 2 UNION ALL
SELECT 5 , 2, 3 UNION ALL
SELECT 6 , 3, 1 UNION ALL
SELECT 7 , 3, 2 UNION ALL
SELECT 8 , 3, 3 UNION ALL
SELECT 9 , 4, 1 UNION ALL
SELECT 10, 4, 2
) SELECT *
FROM CTE B
WHERE (SELECT [text()] = A.Network FROM CTE A WHERE A.CID = B.CID ORDER BY CID, CAAccount FOR XML PATH('')) = 123
Output:
CAAccount CID Network
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3