Home > Mobile >  How can a add a condition after PARTITION BY of the OVER clause in SQL (Oracle)?
How can a add a condition after PARTITION BY of the OVER clause in SQL (Oracle)?

Time:11-04

In the following case, I just want to count films of each category with a length plus 5 or minus 5 minutes (respective the current length LEN)? How can I add this condition to the statement after PARTITION BY?

SELECT film_id, title, category_name, length AS LEN, 
       Count(film_id) OVER (PARTITION BY category_name)  
FROM film INNER JOIN film_category USING (film_id) 
          INNER JOIN category USING (category_id)
ORDER BY name, length;

I've tried with RANGE but it does not have the effect I want.

SELECT film_id, title, category_name, length AS LEN, 
       Count(film_id) OVER (PARTITION BY category_name)  
FROM film INNER JOIN film_category USING (film_id) 
          INNER JOIN category USING (category_id)
ORDER BY name, length;

CodePudding user response:

I'm afraid a simple windowing function cannot accomplish what you are trying to do. I would, instead, put your base query into a Common Table Expression (CTE), then cross all films of each category, allowing you to easily compare the lengths of each.

With basequery AS (
    SELECT film_id, title, category_name, length AS LEN, 
    FROM film INNER JOIN film_category USING (film_id) 
      INNER JOIN category USING (category_id)
)
Select film_id, title, category_name, count(*) as nbr_similar
From basequery A Inner Join basequery B
    On A.category_name=B.category_name
      AND B.LEN between A.LEN-5 and A.LEN 5
Group By film_id, title, category_name
Order By A.category_name, A.Len

Note that this counts the film itself in the counts of those fitting the interval. If you want to exclude that one and just count all others, you'll need to add

AND A.film_id<>B.film_id

CodePudding user response:

Use RANGE window in the analytic function:

SELECT film_id,
       title,
       category_name,
       length AS LEN, 
       Count(film_id) OVER (
         PARTITION BY category_id
         ORDER BY length
         RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING
       ) AS count_similar
FROM   film
       INNER JOIN film_category USING (film_id) 
       INNER JOIN category USING (category_id)
ORDER BY title, length;

Which, for the sample data:

CREATE TABLE film ( film_id, title, length ) AS
  SELECT LEVEL,
         CASE WHEN LEVEL <= 26 THEN CHR(64 LEVEL) ELSE CHR(70 LEVEL) END,
         LEVEL
  FROM   DUAL
  CONNECT BY LEVEL <= 52;

CREATE TABLE film_category ( film_id, category_id ) AS
  SELECT film_id, CEIL(film_id/26) FROM film;

CREATE TABLE category (category_id, category_name) AS
  SELECT LEVEL, CHR(64 LEVEL) FROM DUAL CONNECT BY LEVEL <= 2;

Outputs:

FILM_ID TITLE CATEGORY_NAME LEN COUNT_SIMILAR
1 A A 1 6
2 B A 2 7
3 C A 3 8
4 D A 4 9
5 E A 5 10
6 F A 6 11
7 G A 7 11
8 H A 8 11
9 I A 9 11
10 J A 10 11
11 K A 11 11
12 L A 12 11
13 M A 13 11
14 N A 14 11
15 O A 15 11
16 P A 16 11
17 Q A 17 11
18 R A 18 11
19 S A 19 11
20 T A 20 11
21 U A 21 11
22 V A 22 10
23 W A 23 9
24 X A 24 8
25 Y A 25 7
26 Z A 26 6
27 a B 27 6
28 b B 28 7
29 c B 29 8
30 d B 30 9
31 e B 31 10
32 f B 32 11
33 g B 33 11
34 h B 34 11
35 i B 35 11
36 j B 36 11
37 k B 37 11
38 l B 38 11
39 m B 39 11
40 n B 40 11
41 o B 41 11
42 p B 42 11
43 q B 43 11
44 r B 44 11
45 s B 45 11
46 t B 46 11
47 u B 47 11
48 v B 48 10
49 w B 49 9
50 x B 50 8
51 y B 51 7
52 z B 52 6

fiddle

  • Related