@@ -400,6 +400,43 @@ def _(
400400 return apply_window_if_present (expr , window )
401401
402402
403+ @UNARY_OP_REGISTRATION .register (agg_ops .QcutOp )
404+ def _ (
405+ op : agg_ops .QcutOp ,
406+ column : typed_expr .TypedExpr ,
407+ window : typing .Optional [window_spec .WindowSpec ] = None ,
408+ ) -> sge .Expression :
409+ percent_ranks_order_by = sge .Ordered (this = column .expr , desc = False )
410+ percent_ranks = apply_window_if_present (
411+ sge .func ("PERCENT_RANK" ),
412+ window ,
413+ include_framing_clauses = False ,
414+ order_by_override = [percent_ranks_order_by ],
415+ )
416+ if isinstance (op .quantiles , int ):
417+ scaled_rank = percent_ranks * sge .convert (op .quantiles )
418+ # Calculate the 0-based bucket index.
419+ bucket_index = sge .func ("CEIL" , scaled_rank ) - sge .convert (1 )
420+ safe_bucket_index = sge .func ("GREATEST" , bucket_index , 0 )
421+
422+ return sge .If (
423+ this = sge .Is (this = column .expr , expression = sge .Null ()),
424+ true = sge .Null (),
425+ false = sge .Cast (this = safe_bucket_index , to = "INT64" ),
426+ )
427+ else :
428+ case = sge .Case ()
429+ first_quantile = sge .convert (op .quantiles [0 ])
430+ case = case .when (
431+ sge .LT (this = percent_ranks , expression = first_quantile ), sge .Null ()
432+ )
433+ for bucket_n in range (len (op .quantiles ) - 1 ):
434+ quantile = sge .convert (op .quantiles [bucket_n + 1 ])
435+ bucket = sge .convert (bucket_n )
436+ case = case .when (sge .LTE (this = percent_ranks , expression = quantile ), bucket )
437+ return case .else_ (sge .Null ())
438+
439+
403440@UNARY_OP_REGISTRATION .register (agg_ops .QuantileOp )
404441def _ (
405442 op : agg_ops .QuantileOp ,
0 commit comments