DB2 SQL to count number of rows with respective to 2 columns - sql

I have data in DB2 table something like this
main_id
sub_id
key_id
type
100
1001
1
0
100
1001
2
0
101
1002
1
0
102
1003
1
1
102
1004
1
1
103
1005
2
2
103
1006
2
2
104
1007
2
0
105
1008
2
1
105
1009
2
1
Now I am trying to create output something like this where :
count1 - main_id count with multiple rows only with key id 1
count2 - main_id count with multiple rows only with key id 2
count3 - main_id count with multiple rows with combo 1 & 2 key id
Type
count_1
count_2
count_3
0
1
1
1
1
2
1
Overall, I need distinct main_id's count with multiple rows of sub_id and then need to segregate them based on type and key_id's
In the above case :
100 - has 2 rows w/ 1001 but key_id's are differnt -count_3
102 - has 2 rows w/ 1002 and 1003 but key_id's are from 1 - count_1
103 - has 2 rows w/ 1005 and 1006 but key_id's are from 2 - count_2
105 - has 2 rows w/ 1008 and 1009 but key_id's are from 2 -count_2
Ignored 101 and 104 as there are no multiple rows for that main_id
WITH main_ids AS (
SELECT main_id, key_id, type, COUNT(sub_id) sub_id_count
FROM your_table
GROUP BY main_id, key_id, type
),
grouped AS (
SELECT type, COUNT(main_id) main_id_count
FROM main_ids
WHERE sub_id_count > 1
GROUP BY type
)
SELECT type,
SUM(CASE WHEN key_id = 1 THEN sub_id_count END) count_1,
SUM(CASE WHEN key_id = 2 THEN sub_id_count END) count_2,
SUM(CASE WHEN sub_id_count > 1 THEN sub_id_count END) count_3
FROM main_ids
JOIN grouped ON grouped.type = main_ids.type
GROUP BY type
Need help with count_3 condition, is there any way where can get count of main_id having multiple rows with both key_id 1 and 2(in the above case only 100 main_id is satisfying that condition)
Appreciate any help

WITH
T (main_id, sub_id, key_id, type) AS
(
VALUES
(100, 1001, 1, 0)
, (100, 1001, 2, 0)
, (101, 1002, 1, 0)
, (102, 1003, 1, 1)
, (102, 1004, 1, 1)
, (103, 1005, 2, 2)
, (103, 1006, 2, 2)
, (104, 1007, 2, 0)
, (105, 1008, 2, 1)
, (105, 1009, 2, 1)
)
, G AS
(
SELECT TYPE, KEY_ID, COUNT (DISTINCT MAIN_ID) C
FROM T
GROUP BY TYPE, KEY_ID
)
SELECT
TYPE
, CASE WHEN C1 = 1 THEN 1 END AS COUNT_1
, CASE WHEN C2 = 1 THEN 1 END AS COUNT_2
, CASE WHEN C1 > 1 AND C2 > 1 THEN 1 END AS COUNT_3
-- Just for convenience
, C1
, C2
FROM
(
SELECT
M.TYPE
, COALESCE (G1.C, 0) AS C1
, COALESCE (G2.C, 0) AS C2
FROM (SELECT DISTINCT TYPE FROM G) M
LEFT JOIN G G1 ON G1.TYPE = M.TYPE AND G1.KEY_ID = 1
LEFT JOIN G G2 ON G2.TYPE = M.TYPE AND G2.KEY_ID = 2
)
TYPE
COUNT_1
COUNT_2
COUNT_3
C1
C2
0
1
2
2
1
1
1
1
1
2
1
0
1

Related

SQL Order By On two columns but same prority

I'm stuck on this simple select and don't know what to do.
I Have this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
5 | 200
6 | 200
7 | 100
8 | NULL
and want this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
7 | 100
5 | 200
6 | 200
8 | NULL
all group members keep together, but others order by ID.
I can not write this script because of that NULL records. NULL means that there is not any group for this record.
First you want to order your rows by the minimum ID of their group - or their own ID in case they belong to no group.Then you want to order by ID. That is:
order by min(id) over (partition by case when grp is null then id else grp end), id
If IDs and groups can overlap (i.e. the same number can be used for an ID and for a group, e.g. add a record for ID 9 / group 1 to your sample data) you should change the partition clause to something like
order by min(id) over (partition by case when grp is null
then 'ID' + cast(id as varchar)
else 'GRP' + cast(grp as varchar) end),
id;
Rextester demo: http://rextester.com/GPHBW5600
What about data after a null? In a comment you said don't sort the null.
declare #T table (ID int primary key, grp int);
insert into #T values
(1, NULL)
, (3, 100)
, (5, 200)
, (6, 200)
, (7, 100)
, (8, NULL)
, (9, 200)
, (10, 100)
, (11, NULL)
, (12, 150);
select ttt.*
from ( select tt.*
, sum(ff) over (order by tt.ID) as sGrp
from ( select t.*
, iif(grp is null or lag(grp) over (order by id) is null, 1, 0) as ff
from #T t
) tt
) ttt
order by ttt.sGrp, ttt.grp, ttt.id
ID grp ff sGrp
----------- ----------- ----------- -----------
1 NULL 1 1
3 100 1 2
7 100 0 2
5 200 0 2
6 200 0 2
8 NULL 1 3
10 100 0 4
9 200 1 4
11 NULL 1 5
12 150 1 6

Different select criteria in odd and even events

I have a table which looks like this ( 10 billion rows)
AID BID CID
1 2 1
1 6 9
0 1 4
1 3 2
1 100 2
0 4 2
0 0 1
The AID could only be 0 or 1. BID and CID could be anything.
Now I want to select events first with AID=1 and then AID=0, and again AID=1 and then AID=0.
The idea is to select equal numbers of AID=1 and AID=0 event.
How can I achieve that?
The expected result is
AID BID CID
1 2 1
0 1 4
1 6 9
0 4 2
1 3 2
0 0 1
;WITH cte AS (
select *
FROM (VALUES
(1, 2, 1),
(1, 6, 9),
(0, 1, 4),
(1, 3, 2),
(1, 100, 2),
(0, 4, 2),
(0, 0, 1)
) as t(AID, BID, CID)
),
withrow AS (
SELECT ROW_NUMBER() OVER (PARTITION BY AID ORDER BY AID) as RN, *
FROM cte)
SELECT AID,BID,CID
FROM withrow
ORDER BY RN asc , aid desc
Output:
AID BID CID
----------- ----------- -----------
1 100 2
0 4 2
1 3 2
0 1 4
1 6 9
0 0 1
1 2 1
(7 row(s) affected)

Merging results from two subqueries Postgresql

I am using Postgresql, I created two subqueries that return results as follows:
firm_id type_1 fee_1
1 2 100
2 4 300
5 1 100
firm_id type_2 fee_2
1 3 200
2 3 200
3 2 150
4 5 300
I would like to yield a result as:
firm_id type_1 type_2 total_fee
1 2 3 300
2 4 3 500
3 0 2 150
4 0 5 300
5 1 0 100
Any helps appreciated!
Use FULL JOIN and coalesce():
with q1(firm_id, type_1, fee_1) as (
values
(1, 2, 100),
(2, 4, 300),
(5, 1, 100)),
q2 (firm_id, type_2, fee_2) as (
values
(1, 3, 200),
(2, 3, 200),
(3, 2, 150),
(4, 5, 300))
select
firm_id,
coalesce(type_1, 0) type_1,
coalesce(type_2, 0) type_2,
coalesce(fee_1, 0)+ coalesce(fee_2, 0) total_fee
from q1
full join q2
using (firm_id);
firm_id | type_1 | type_2 | total_fee
---------+--------+--------+-----------
1 | 2 | 3 | 300
2 | 4 | 3 | 500
3 | 0 | 2 | 150
4 | 0 | 5 | 300
5 | 1 | 0 | 100
(5 rows)
SELECT firm_id
,coalesce(t.type_1, 0) type_1
,coalesce(b.type_1, 0) type_2
,coalesce(t.fee_1, 0) + coalesce(b.fee_1, 0) total_fee
FROM (
SELECT * --Your first select query
FROM tablea
) t
FULL JOIN (
SELECT * --Your second select query
FROM tableb
) b using (firm_id)
FULL JOIN: combines the results of both left and right outer joins.
The joined table will contain all records from both tables, and fill in NULLs for missing matches on either side.
COALESCE function returns the first of its arguments that is not null. Null is returned only if all arguments are null. It is often used to substitute a default value for null values when data is retrieved for display
SELECT coalesce( t1."firm_id", t2."firm_id" ) as firm_id,
coalesce( t1."type_1", 0 ) as type_1,
coalesce( t2."type_2", 0 ) as type_2,
coalesce( t1."fee_1", 0 )
+
coalesce( t2."fee_2", 0 ) as total_fee
FROM table1 t1
FULL JOIN table2 t2
ON t1."firm_id" = t2."firm_id"
where table1 and table2 must be replaced by your subqueries
see a demo: http://sqlfiddle.com/#!15/6d391/2
select coalesce(sq1.firm_id, sq2.firm_id) as firm_id, coalesce(type_1, 0), coalesce(type_2, 0), coalesce(fee_1, 0)+coalesce(fee_2, 0) as total_fee
from <subquery1> as sq1
outer join <subquery1> as sq1
on sq1.firm_id=sq2.firm_id
If you have two subqueries, say a and b, that give you set A and set B, what you're looking for is a full join A x B where at least one firm_id in the full join is not null, with a calculated column total_fee = fee_1 + fee_2.
I'm not that familiar with postgresql syntax but it should be something like
select
-- get rid of the columns you don't want
a.firm_id, a.type_1, a.type_2, a.fee_1,
b.firm_id, b.type_1, b.type_2, b.fee_2,
a.fee_1 + b.fee_2 as total_fee
from ( subquery_1 here ) as a
full join ( subquery_2 here) as b
on
b.firm_id = a.firm_id and
b.type_1 <> a.type_1
where
a.firm_id is not null or b.firm_id is not null

SQL: Get multiple line entries linked to one item?

I have a table:
ID | ITEMID | STATUS | TYPE
1 | 123 | 5 | 1
2 | 123 | 4 | 2
3 | 123 | 5 | 3
4 | 125 | 3 | 1
5 | 125 | 5 | 3
Any item can have 0 to many entries in this table. I need a query that will tell me if an ITEM has all it's entries in either a state of 5 or 4. For example, in the above example, I would like to end up with the result:
ITEMID | REQUIREMENTS_MET
123 | TRUE --> true because all statuses are either 5 or 4
125 | FALSE --> false because it has a status of 3 and a status of 5.
If the 3 was a 4 or 5, then this would be true
What would be even better is something like this:
ITEMID | MET_REQUIREMENTS | NOT_MET_REQUIREMENTS
123 | 3 | 0
125 | 1 | 1
Any idea how to write a query for that?
Fast, short, simple:
SELECT itemid
,count(status = 4 OR status = 5 OR NULL) AS met_requirements
,count(status < 4 OR status > 5 OR NULL) AS not_met_requirements
FROM tbl
GROUP BY itemid
ORDER BY itemid;
Assuming all columns to be integer NOT NULL.
Builds on basic boolean logic:
TRUE OR NULL yields TRUE
FALSE OR NULL yields NULL
And NULL is not counted by count().
->SQLfiddle demo.
SELECT a.ID FROM (SELECT ID, MIN(STATUS) AS MINSTATUS, MAX(STATUS) AS MAXSTATUS FROM TABLE_NAME AS a GROUP BY ID)
WHERE a.MINSTATUS >= 4 AND a.MAXSTATUS <= 5
One way of doing this would be
SELECT t1.itemid, NOT EXISTS(SELECT 1
FROM mytable t2
WHERE itemid=t1.itemid
AND status NOT IN (4, 5)) AS requirements_met
FROM mytable t1
GROUP BY t1.itemid
UPDATE: for your updated requirement, you can have something like:
SELECT itemid,
sum(CASE WHEN status IN (4, 5) THEN 1 ELSE 0 END) as met_requirements,
sum(CASE WHEN status IN (4, 5) THEN 0 ELSE 1 END) as not_met_requirements
FROM mytable
GROUP BY itemid
simple one:
select
"ITEMID",
case
when min("STATUS") in (4, 5) and max("STATUS") in (4, 5) then 'True'
else 'False'
end as requirements_met
from table1
group by "ITEMID"
better one:
select
"ITEMID",
sum(case when "STATUS" in (4, 5) then 1 else 0 end) as MET_REQUIREMENTS,
sum(case when "STATUS" in (4, 5) then 0 else 1 end) as NOT_MET_REQUIREMENTS
from table1
group by "ITEMID";
sql fiddle demo
WITH dom AS (
SELECT DISTINCT item_id FROM items
)
, yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id
)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id
)
SELECT d.item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM dom d
LEFT JOIN yes y ON y.item_id = d.item_id
LEFT JOIN no n ON n.item_id = d.item_id
;
Can be done with an outer join, too:
WITH yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id)
SELECT COALESCE(y.item_id, n.item_id) AS item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM yes y
FULL JOIN no n ON n.item_id = y.item_id
;
Nevermind, it was actually easy to do:
select ITEM_ID ,
sum (case when STATUS >= 3 then 1 else 0 end ) as met_requirements,
sum (case when STATUS < 3 then 1 else 0 end ) as not_met_requirements
from TABLE as d
group by ITEM_ID

Grouping Hierarchical data (parentID+ID) and running sum?

I have the following data:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
I am trying to group this data with Reporting Services 2008 and have a sum of the price per group of level 1 (Flowers/Electro) and for level 0 (Root).
I have a table grouped on [ID] with a recursive parent of [parendID] and I am able to calculate the sum for the level 0 (just one more row in the table outside the group), but somehow I am not able to create sum's per group as SRSS does "create" groups per level. My desired result looks like so:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
|-5 Violet 5
| |-6 Red Rose 12
| Group Sum-->27
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
Group Sum-->260
----------------------
Total 287
(indentation of ID just added for level clarification)
With my current approach I cannot get the group sums, so I figured out I would need the following data structure:
ID parentID Text Price level0 level1 level2 level3
1 Root 1
2 1 Flowers 1 1
3 1 Electro 1 2
4 2 Rose 10 1 1 1
5 2 Violet 5 1 1 2
6 4 Red Rose 12 1 1 1 1
7 3 Television 100 1 2 1
8 3 Radio 70 1 2 2
9 8 Webradio 90 1 2 2 1
When having the above structure I can create an outer grouping of level0, with child groupings level1, level2, level3 accordingly . When now having a "group sum" on level1, and the total sum outside the group I have EXACTLY what I want.
My question is the following:
How do I either achieve my desired result with my current data structure, or how do I convert my current data structure (outer left joins?) into the "new data structure" temporarily - so I can run my report off of the temp table?
Thanks for taking your time,
Dennis
WITH q AS
(
SELECT id, parentId, price
FROM mytable
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN mytable p
ON p.id = q.parentID
)
SELECT id, SUM(price)
FROM q
GROUP BY
id
Update:
A test script to check:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, parentID INT, txt VARCHAR(200) NOT NULL, price MONEY)
INSERT
INTO #table
SELECT 1, NULL, 'Root', NULL
UNION ALL
SELECT 2, 1, 'Flowers', NULL
UNION ALL
SELECT 3, 1, 'Electro', NULL
UNION ALL
SELECT 4, 2, 'Rose', 10
UNION ALL
SELECT 5, 2, 'Violet', 5
UNION ALL
SELECT 6, 4, 'Red Rose', 12
UNION ALL
SELECT 7, 3, 'Television', 100
UNION ALL
SELECT 8, 3, 'Radio', 70
UNION ALL
SELECT 9, 8, 'Webradio', 90;
WITH q AS
(
SELECT id, parentId, price
FROM #table
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN #table p
ON p.id = q.parentID
)
SELECT t.*, psum
FROM (
SELECT id, SUM(price) AS psum
FROM q
GROUP BY
id
) qo
JOIN #table t
ON t.id = qo.id
Here's the result:
1 NULL Root NULL 287,00
2 1 Flowers NULL 27,00
3 1 Electro NULL 260,00
4 2 Rose 10,00 22,00
5 2 Violet 5,00 5,00
6 4 Red Rose 12,00 12,00
7 3 Television 100,00 100,00
8 3 Radio 70,00 160,00
9 8 Webradio 90,00 90,00
I found a really ugly way to do what I want - maybe there is something better?
SELECT A.Text, A.Price,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
A.ID
ELSE B.ID
END
ELSE C.ID
END
ELSE D.ID
END
AS LEV0,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
ELSE C.ID
END
AS LEV1,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
AS LEV2,
CASE
WHEN D.Text IS NULL
THEN NULL
ELSE A.ID
END
AS LEV3
FROM dbo.testOld AS A LEFT OUTER JOIN
dbo.testOld AS B ON A.parentID = B.ID LEFT OUTER JOIN
dbo.testOld AS C ON B.parentID = C.ID LEFT OUTER JOIN
dbo.testOld AS D ON C.parentID = D.ID
Output of this is:
Text Price LEV0 LEV1 LEV2 LEV3
---------- ----------- ----------- ----------- ----------- -----------
Root NULL 1 NULL NULL NULL
Flowers NULL 1 3 NULL NULL
Electro NULL 1 4 NULL NULL
Television 100 1 4 5 NULL
Radio 70 1 4 6 NULL
Rose 10 1 3 7 NULL
Violet 5 1 3 8 NULL
Webradio 90 1 4 5 14
Red Rose 12 1 3 7 15
With this structure I can go ahead and create 4 nested groups on the LEV0-3 columns including subtotals per group (as shown above in my desired result).