Max rows by group - sql

Current SQL:
select t1.*
from table t1
where t1.id in ('2', '3', '4')
Current results:
id | seq
---+----
3 | 5
2 | 7
2 | 5
3 | 7
4 | 3
Attempt to select maxes:
select t1.*
from table t1
where t1.id in ('2', '3', '4')
and t1.seq = (select max(t2.seq)
from table2 t2
where t2.id = t1.id)
This obviously does not work since I'm using an in list. How can I adjust my SQL to get these expected results:
id | seq
---+----
2 | 7
3 | 7
4 | 3

Group By is your friend:
SELECT
id,
MAX(seq) seq
FROM TABLE
GROUP BY id
EDIT: Response to comment. To get the rest of the data from the table matching the max seq and id just join back to the table:
SELECT t1.*
FROM TABLE t1
INNER JOIN (
SELECT
id
MAX(seq) as seq
FROM TABLE
GROUP BY id
) as t2
on t1.id = t2.id
and t1.seq = t2.seq
EDIT: Gordon and Jean-Francois are correct you can also use the ROW_NUMBER() analytic function to get the same result. You need to check the performance difference for your application (I did not check). Here is an example of that:
SELECT *
FROM (
SELECT ROW_NUMBER() OVER (
PARTITION BY id
ORDER BY seq DESC) as row_num
,*
FROM TABLE
) as TMP
WHERE row_num = 1

This SQL Query will give you max seq from individaul ID.
SELECT t1.*
FROM t1
WHERE t1.id in ('2', '3', '4')
AND NOT EXISTS (
SELECT *
FROM t1 t2
WHERE t2.id = t1.id
AND t2.seq > t1.seq

select *
from table
where (id,seq) in
(
select id,max(seq)
from table
group by id
having id in ('2','3','4')
);
That is if id and/or seq are completely part of the PK of that table.

Here's another example, using the first/last method I mentioned earlier in the comments:
with sd as (select 3 id, 5 seq, 1 dummy from dual union all
select 2 id, 7 seq, 2 dummy from dual union all
select 2 id, 5 seq, 3 dummy from dual union all
select 3 id, 7 seq, 4 dummy from dual union all
select 3 id, 7 seq, 5 dummy from dual union all
select 4 id, 3 seq, 6 dummy from dual)
select id,
max(seq) max_seq,
max(dummy) keep (dense_rank first order by seq desc) max_rows_dummy
from sd
group by id;
ID MAX_SEQ MAX_ROWS_DUMMY
---------- ---------- --------------
2 7 2
3 7 5
4 3 6
The keep (dense_rank first order by ...) bit is requesting to keep the values associated with the rank of 1 in the order list of rows. The max(...) bit is there in case more then one row has a rank of 1; it's just a way of breaking ties.

Related

Create a duplicate row on top of Select statement

table TEST
id
Name
1
abc
2
xyz
In general i used to get records from below query
Select id,name from TEST.
id
Name
1
abc
2
xyz
but now i want to create a duplicate for each row on top my select query
expected output: please suggest how can i achieve result like below
id
Name
1
abc
1
abc
2
xyz
2
xyz
You may cross join your table with a sequence table containing how ever many copies you want. Here is an example using an inline sequence table:
SELECT t1.id, t1.Name
FROM yourTable t1
CROSS JOIN (
SELECT 1 AS seq FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
) t2
WHERE t2.seq <= 2
ORDER BY t1.id;
To me, UNION (ALL) set operator seems to be quite simple.
Sample data:
SQL> select * from test;
ID NAME
---------- ----
1 abc
2 xyz
UNION ALL:
SQL> select * from test
2 union all
3 select * from test;
ID NAME
---------- ----
1 abc
2 xyz
1 abc
2 xyz
SQL>
CREATE table test(
id integer,
name VARCHAR2(4)
);
INSERT into test (id, name) VALUES (1,'ABC');
INSERT into test (id, name) VALUES (2,'XYZ');
with data as (select level l from dual connect by level <= 2)
select *
from test, data
order by id, l
/
One more option is LATERAL
SELECT t.*
FROM test
, LATERAL (
SELECT id, name FROM DUAL
union all
SELECT id, name FROM DUAL
) t
One option is using a self-join along with ROW_NUMBER analytic function such as
WITH t AS
(
SELECT t1.id, t1.name, ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY 0) AS rn
FROM test t1,
test t2
)
SELECT id, name
FROM t
WHERE rn <= 2
Demo

SQL Grouping by first digit from sets of record

I need your help in SQL
I have a set of records of Cost center ID below.
what I want to do is to segregate/group them by inserting column to distinguish the category.
as you can see all digits start in 7 is belong to the bold digits.
my expected out is on below image also.
You can as the below:
DECLARE #Tbl TABLE (ID INT)
INSERT INTO #Tbl
VALUES
(735121201),
(735120001),
(5442244),
(735141094),
(735141097),
(4008060),
(735117603),
(40100000),
(735142902),
(735151199),
(4010070)
;WITH TableWithRowId
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY(SELECT NULL)) RowId,
ID
FROM
#Tbl
), TempTable
AS
(
SELECT T.RowId + 1 AS RowId FROM TableWithRowId T
WHERE
LEFT(T.ID, 1) != 7
), ResultTable
AS
(
SELECT
T.RowId ,
T.ID,
DENSE_RANK() OVER (ORDER BY (SELECT TOP 1 A.RowId FROM TempTable A WHERE A.RowId > T.RowId ORDER BY A.RowId)) AS Flag
FROM TableWithRowId T
)
SELECT * FROM ResultTable
Result:
RowId ID Flag
----------- ----------- ----------
1 735121201 1
2 735120001 1
3 5442244 1
4 735141094 2
5 735141097 2
6 4008060 2
7 735117603 3
8 40100000 3
9 735142902 4
10 735151199 4
11 4010070 4
The following query is similer with NEER's
;WITH test_table(CenterID)AS(
SELECT '735121201' UNION ALL
SELECT '735120001' UNION ALL
SELECT '5442244' UNION ALL
SELECT '735141094' UNION ALL
SELECT '735141097' UNION ALL
SELECT '4008060' UNION ALL
SELECT '735117603' UNION ALL
SELECT '40100000' UNION ALL
SELECT '735142902' UNION ALL
SELECT '735151199' UNION ALL
SELECT '4010070'
),t1 AS (
SELECT *,ROW_NUMBER()OVER(ORDER BY(SELECT 1)) AS rn,CASE WHEN LEFT(t.CenterID,1)='7' THEN 1 ELSE 0 END AS isSeven
FROM test_table AS t
),t2 AS(
SELECT t1.*,ROW_NUMBER()OVER(ORDER BY t1.rn) AS toFilter
FROM t1 LEFT JOIN t1 AS pt ON pt.rn=t1.rn-1
WHERE pt.CenterID IS NULL OR (t1.isSeven=1 AND pt.isSeven=0)
)
SELECT t1.CenterID,x.toFilter FROM t1
CROSS APPLY(SELECT TOP 1 t2.toFilter FROM t2 WHERE t2.rn<=t1.rn ORDER BY rn desc) x
CenterID toFilter
--------- --------------------
735121201 1
735120001 1
5442244 1
735141094 2
735141097 2
4008060 2
735117603 3
40100000 3
735142902 4
735151199 4
4010070 4

SQL query to return data only if ALL necessary columns are present and not NULL

ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
2 Purchase null
2 Return 5
2 Exchange 1
3 Purchase 34
3 Return 4
3 Exchange 2
4 Purchase 12
4 Exchange 2
Above is sample data. What I want to return is:
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
3 Purchase 34
3 Return 4
3 Exchange 2
So if a field is null in total or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. How can I go about doing this?
You can use exists. I think you intend:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Purchase' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Exchange' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Return' and t2.total is not null
);
There are ways to "simplify" this:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.total is not null
);
I would write this as a join, without subqueries:
SELECT pur.id, pur.total AS Purchase, exc.total AS Exchange, ret.total AS Return
FROM MyTable as pur
INNER JOIN MyTable AS exc ON exc.id=pur.id AND exc.type='Exchange'
INNER JOIN MyTable AS ret ON ret.id=pur.id AND ret.type='Return'
WHERE pur.type='Purchase'
The inner join means that if any of the three rows with different values are not found for a given id, then no row is included in the result.
Analytic functions are a good way to solve this kind of problems. The base table is read just once, and no joins (explicit or implicit, as in EXISTS conditions or correlated subqueries) are needed.
In the solution below, we count distinct values of 'Purchase', 'Exchange' and 'Return' for each id while ignoring other values (assuming that is indeed the requirement), and separately count total nulls in the total column for each id. Then it becomes a trivial matter to select just the "desired" rows in an outer query.
with
test_data ( id, type, total ) as (
select 1, 'Purchase', 12 from dual union all
select 1, 'Return' , 2 from dual union all
select 1, 'Exchange', 5 from dual union all
select 2, 'Purchase', null from dual union all
select 2, 'Return' , 5 from dual union all
select 2, 'Exchange', 1 from dual union all
select 3, 'Purchase', 34 from dual union all
select 3, 'Return' , 4 from dual union all
select 3, 'Exchange', 2 from dual union all
select 4, 'Purchase', 12 from dual union all
select 4, 'Exchange', 2 from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, type, total
from ( select id, type, total,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when total is null then 1 end
) over (partition by id) as ct_total
from test_data
)
where ct_type = 3 and ct_total = 0
;
Output:
ID TYPE TOTAL
-- -------- -----
1 Exchange 5
1 Purchase 12
1 Return 2
3 Exchange 2
3 Purchase 34
3 Return 4
This also should work fine even if new values are added to type column
select * from t where
ID not in(select ID from t where
t.total is null or t.[Type] is null)

Add values from count that have come from a UNION

I am attempting to add together the values_occurrence for the id's that match. For example ID number 1 has 3 and another record shows ID number 1 having 4. I want to ADD 3 + 4 based on the ID matching.
I am trying to see which ID has the most entries in each table and then add them together.
ID value_occurrence
--------------------
1 3
1 4
so far this is what I have.
SELECT ID, COUNT(ID) AS value_occurrence
FROM TABLE1
GROUP BY ID
UNION
SELECT ID, COUNT(ID) AS value_occurrence
FROM TABLE2
GROUP BY ID
ORDER BY ID ASC;
Any help would be appreciated.
Do a union all and then aggregate:
select id, count(*) as total_cnt, sum(t1) as t1_cnt, sum(t2) as t2_cnt
from ((select id, 1 as t1, 0 as t2 from table1) union all
(select id, 0, 1 from table2)
) t
group by id
order by id;

In Oracle, how do I get a page of distinct values from sorted results?

I have 2 columns in a one-to-many relationship. I want to sort on the "many" and return the first occurrence of the "one". I need to page through the data so, for example, I need to be able to get the 3rd group of 10 unique "one" values.
I have a query like this:
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id;
There can be multiple rows in table2 for each row in table1.
The results of my query look like this:
id | name
----------------
2 | apple
23 | banana
77 | cranberry
23 | dark chocolate
8 | egg
2 | yak
19 | zebra
I need to page through the result set with each page containing n unique ids. For example, if start=1 and n=4 I want to get back
2
23
77
8
in the order they were sorted on (i.e., name), where id is returned in the position of its first occurrence. Likewise if start=3 and n=4 and order = desc I want
8
23
77
2
I tried this:
SELECT * FROM (
SELECT id, ROWNUM rnum FROM (
SELECT DISTINCT id FROM (
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id)
WHERE ROWNUM <= 4)
WHERE rnum >=1)
which gave me the ids in numerical order, instead of being ordered as the names would be.
I also tried:
SELECT * FROM (
SELECT DISTINCT id, ROWNUM rnum FROM (
SELECT id FROM (
SELECT id, name
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
ORDER BY name, id)
WHERE ROWNUM <= 4)
WHERE rnum >=1)
but that gave me duplicate values.
How can I page through the results of this data? I just need the ids, nothing from the "many" table.
update
I suppose I'm getting closer with changing my inner query to
SELECT id, name, rank() over (order by name, id)
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
...but I'm still getting duplicate ids.
You may need to debug it a little, but but it will be something like this:
SELECT * FROM (
SELECT * FROM (
SELECT id FROM (
SELECT id, name, row_number() over (partition by id order by name) rn
FROM table1
INNER JOIN table2 ON table2.fkid = table1.id
)
) WHERE rn=1 ORDER BY name, id
) WHERE rownum>=1 and rownum<=4;
It's a bit convoluted (and I would tend to suspect that it could be simplified) but it should work. You'd can put whatever start and end position you want in the WHERE clause-- I'm showing here with start=2 and n=4 are pulled from a separate table but you could simplify things by using a couple of parameters instead.
SQL> ed
Wrote file afiedt.buf
1 with t as (
2 select 2 id, 'apple' name from dual union all
3 select 23, 'banana' from dual union all
4 select 77, 'cranberry' from dual union all
5 select 23, 'dark chocolate' from dual union all
6 select 8, 'egg' from dual union all
7 select 2, 'yak' from dual union all
8 select 19, 'zebra' from dual
9 ),
10 x as (
11 select 2 start_pos, 4 n from dual
12 )
13 select *
14 from (
15 select distinct
16 id,
17 dense_rank() over (order by min_id_rnk) outer_rnk
18 from (
19 select id,
20 min(rnk) over (partition by id) min_id_rnk
21 from (
22 select id,
23 name,
24 rank() over (order by name) rnk
25 from t
26 )
27 )
28 )
29 where outer_rnk between (select start_pos from x) and (select start_pos+n-1 from x)
30* order by outer_rnk
SQL> /
ID OUTER_RNK
---------- ----------
23 2
77 3
8 4
19 5