diff --git a/sbroad-cartridge/test_app/test/integration/groupby_test.lua b/sbroad-cartridge/test_app/test/integration/groupby_test.lua
index 04c9608d9d396679a4ee6d8fec513f79fe4f44ba..e5a08d686ea0f394b672bd5aec4d4467073c637c 100644
--- a/sbroad-cartridge/test_app/test/integration/groupby_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/groupby_test.lua
@@ -1724,3 +1724,750 @@ groupby_queries.test_total_with_groupby = function()
         {2, 6, 3},
     })
 end
+
+groupby_queries.test_having1 = function()
+    local api = cluster:server("api-1").net_box
+
+    -- with having
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        having sum("b") > 5
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {2, 6}
+    })
+    -- without having
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {2, 6},
+        {1, 3}
+    })
+end
+
+groupby_queries.test_having2 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        having "a" = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 3}
+    })
+end
+
+groupby_queries.test_having3 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "boolean_col", sum(distinct "f") as "sum" from "arithmetic_space"
+        group by "boolean_col"
+        having "boolean_col" = true
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "boolean_col", type = "boolean" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {true, 2}
+    })
+end
+
+groupby_queries.test_having_with_sq = function()
+    local api = cluster:server("api-1").net_box
+    local query_str = [[
+        SELECT "a", sum(distinct "b") as "sum", count(distinct "b") as "count" from "arithmetic_space"
+        group by "a"
+        ]];
+    -- without having
+    local r, err = api:call("sbroad.execute", {
+        query_str, {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" },
+        { name = "count", type = "integer" }
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 3, 2},
+        {2, 3, 1}
+    })
+    -- with having
+    r, err = api:call("sbroad.execute", {
+        query_str ..
+        [[ having count(distinct "b") in (select "a" from "arithmetic_space" where "a" = 2)]],
+        {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" },
+        { name = "count", type = "integer" }
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 3, 2},
+    })
+end
+
+groupby_queries.test_having_no_groupby = function()
+    local api = cluster:server("api-1").net_box
+
+    -- having condition is true
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum("a"), sum(distinct "a"), count("a"), count(distinct "a") from "arithmetic_space"
+        having count(distinct "a") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    -- todo: if executed on single tarantool instance, types are: (integer, integer, integer, integer)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "COL_2", type = "decimal" },
+        { name = "COL_3", type = "decimal" },
+        { name = "COL_4", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {6, 3, 4, 2}
+    })
+    -- having condition is false
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum("a"), sum(distinct "a"), count("a"), count(distinct "a") from "arithmetic_space"
+        having count(distinct "a") > 100
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "COL_2", type = "decimal" },
+        { name = "COL_3", type = "decimal" },
+        { name = "COL_4", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {})
+end
+
+groupby_queries.test_having_selection = function()
+    local api = cluster:server("api-1").net_box
+
+    -- with having
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "string_col", count(distinct "string_col"), count("string_col")
+        from "arithmetic_space"
+        where "id" > 2 or "string_col" = 'a'
+        group by "string_col"
+        having sum(distinct "a") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "string_col", type = "string" },
+        { name = "COL_1", type = "integer" },
+        { name = "COL_2", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 'c', 1, 2 }
+    })
+    -- without having
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "string_col", count(distinct "string_col"), count("string_col")
+        from "arithmetic_space"
+        where "id" > 2 or "string_col" = 'a'
+        group by "string_col"
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "string_col", type = "string" },
+        { name = "COL_1", type = "integer" },
+        { name = "COL_2", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 'a', 1, 2 },
+        { 'c', 1, 2 }
+    })
+end
+
+groupby_queries.test_having_join = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum(distinct "a"), "b", s
+        from "arithmetic_space" as t1 inner join
+        (
+            select cast(sum("a") / 6 as integer) as s
+            from "arithmetic_space2"
+            having count(distinct "a") > 1
+        ) as t2
+        on t1."c" = t2.s
+        group by s, t1."b"
+        having sum(distinct "a") in (2, 3) and count(distinct t2.s) = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "b", type = "integer" },
+        { name = "S", type = "integer" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 2, 3, 1 }
+    })
+end
+
+groupby_queries.test_having_full_query = function()
+    local api = cluster:server("api-1").net_box
+
+    -- data after join + where
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1."a", t2.b, t2.s, t1."d"
+        from "arithmetic_space" as t1 inner join
+        (select "b" as b, "string_col" as s from "arithmetic_space2") as t2
+        on t1."a" = t2.b
+        where t1."d" + t1."a" > 2
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "T1.a", type = "integer" },
+        { name = "T2.B", type = "integer" },
+        { name = "T2.S", type = "string" },
+        { name = "T1.d", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 1, "a", 2},
+        {1, 1, "b", 2},
+        {1, 1, "b", 2},
+        {2, 2, "a", 2},
+        {2, 2, "a", 1},
+    })
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1."a", count(distinct s)
+        from "arithmetic_space" as t1 inner join
+        (select "b" as b, "string_col" as s from "arithmetic_space2") as t2
+        on t1."a" = t2.b
+        where t1."d" + t1."a" > 2
+        group by "a"
+        having count(distinct s) > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "COL_1", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        { 1, 2 }
+    })
+end
+
+groupby_queries.test_having_inside_sq = function()
+    local api = cluster:server("api-1").net_box
+
+    -- check we can execute sq-s containing having clause
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1.s1, t2.s2 from (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        ) as t1 inner join (
+            select sum(distinct "a") as s2 from "arithmetic_space"
+            having sum("a") > 3
+        ) as t2 on t1.s1 = t2.s2 and t1.s1 in (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        )
+        where t2.s2 in (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        )
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "T1.S1", type = "decimal" },
+        { name = "T2.S2", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {
+        { 3, 3 }
+    })
+end
+
+groupby_queries.test_having_inside_union = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select sum(distinct "e") from "arithmetic_space"
+            having count(distinct "d") > 1
+            union all
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+        {3}
+    })
+end
+
+groupby_queries.test_having_inside_union1 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select "e" from "arithmetic_space"
+            union all
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "e", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+        {2},
+        {2},
+        {2},
+        {3}
+    })
+end
+
+groupby_queries.test_having_inside_except = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select count("e") - 1 from "arithmetic_space"
+            having count("e") = 4
+            except
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {})
+end
+
+groupby_queries.test_having_inside_except1 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select "d" from "arithmetic_space"
+            group by "d"
+            except
+            select sum(distinct "c") from "arithmetic_space"
+            having count(distinct "c") = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "d", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+    })
+end
+
+groupby_queries.test_having1 = function()
+    local api = cluster:server("api-1").net_box
+
+    -- with having
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        having sum("b") > 5
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {2, 6}
+    })
+    -- without having
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {2, 6},
+        {1, 3}
+    })
+end
+
+groupby_queries.test_having2 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "a", sum("b") as "sum" from "arithmetic_space"
+        group by "a"
+        having "a" = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 3}
+    })
+end
+
+groupby_queries.test_having3 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "boolean_col", sum(distinct "f") as "sum" from "arithmetic_space"
+        group by "boolean_col"
+        having "boolean_col" = true
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "boolean_col", type = "boolean" },
+        { name = "sum", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        {true, 2}
+    })
+end
+
+groupby_queries.test_having_no_groupby = function()
+    local api = cluster:server("api-1").net_box
+
+    -- having condition is true
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum("a"), sum(distinct "a"), count("a"), count(distinct "a") from "arithmetic_space"
+        having count(distinct "a") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    -- todo: if executed on single tarantool instance, types are: (integer, integer, integer, integer)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "COL_2", type = "decimal" },
+        { name = "COL_3", type = "decimal" },
+        { name = "COL_4", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {6, 3, 4, 2}
+    })
+    -- having condition is false
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum("a"), sum(distinct "a"), count("a"), count(distinct "a") from "arithmetic_space"
+        having count(distinct "a") > 100
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "COL_2", type = "decimal" },
+        { name = "COL_3", type = "decimal" },
+        { name = "COL_4", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {})
+end
+
+groupby_queries.test_having_selection = function()
+    local api = cluster:server("api-1").net_box
+
+    -- with having
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "string_col", count(distinct "string_col"), count("string_col")
+        from "arithmetic_space"
+        where "id" > 2 or "string_col" = 'a'
+        group by "string_col"
+        having sum(distinct "a") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "string_col", type = "string" },
+        { name = "COL_1", type = "integer" },
+        { name = "COL_2", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 'c', 1, 2 }
+    })
+    -- without having
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT "string_col", count(distinct "string_col"), count("string_col")
+        from "arithmetic_space"
+        where "id" > 2 or "string_col" = 'a'
+        group by "string_col"
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "string_col", type = "string" },
+        { name = "COL_1", type = "integer" },
+        { name = "COL_2", type = "decimal" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 'a', 1, 2 },
+        { 'c', 1, 2 }
+    })
+end
+
+groupby_queries.test_having_join = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT sum(distinct "a"), "b", s
+        from "arithmetic_space" as t1 inner join
+        (
+            select cast(sum("a") / 6 as integer) as s
+            from "arithmetic_space2"
+            having count(distinct "a") > 1
+        ) as t2
+        on t1."c" = t2.s
+        group by s, t1."b"
+        having sum(distinct "a") in (2, 3) and count(distinct t2.s) = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+        { name = "b", type = "integer" },
+        { name = "S", type = "integer" }
+    })
+    t.assert_items_equals(r.rows, {
+        { 2, 3, 1 }
+    })
+end
+
+groupby_queries.test_having_full_query = function()
+    local api = cluster:server("api-1").net_box
+
+    -- data after join + where
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1."a", t2.b, t2.s, t1."d"
+        from "arithmetic_space" as t1 inner join
+        (select "b" as b, "string_col" as s from "arithmetic_space2") as t2
+        on t1."a" = t2.b
+        where t1."d" + t1."a" > 2
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "T1.a", type = "integer" },
+        { name = "T2.B", type = "integer" },
+        { name = "T2.S", type = "string" },
+        { name = "T1.d", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {1, 1, "a", 2},
+        {1, 1, "b", 2},
+        {1, 1, "b", 2},
+        {2, 2, "a", 2},
+        {2, 2, "a", 1},
+    })
+    r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1."a", count(distinct s)
+        from "arithmetic_space" as t1 inner join
+        (select "b" as b, "string_col" as s from "arithmetic_space2") as t2
+        on t1."a" = t2.b
+        where t1."d" + t1."a" > 2
+        group by "a"
+        having count(distinct s) > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "a", type = "integer" },
+        { name = "COL_1", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        { 1, 2 }
+    })
+end
+
+groupby_queries.test_having_inside_sq = function()
+    local api = cluster:server("api-1").net_box
+
+    -- check we can execute sq-s containing having clause
+    local r, err = api:call("sbroad.execute", {
+        [[
+        SELECT t1.s1, t2.s2 from (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        ) as t1 inner join (
+            select sum(distinct "a") as s2 from "arithmetic_space"
+            having sum("a") > 3
+        ) as t2 on t1.s1 = t2.s2 and t1.s1 in (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        )
+        where t2.s2 in (
+            select sum(distinct "a") as s1 from "arithmetic_space"
+            having sum("a") > 3
+        )
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "T1.S1", type = "decimal" },
+        { name = "T2.S2", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {
+        { 3, 3 }
+    })
+end
+
+groupby_queries.test_having_inside_union = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select sum(distinct "e") from "arithmetic_space"
+            having count(distinct "d") > 1
+            union all
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+        {3}
+    })
+end
+
+groupby_queries.test_having_inside_union1 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select "e" from "arithmetic_space"
+            union all
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "e", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+        {2},
+        {2},
+        {2},
+        {3}
+    })
+end
+
+groupby_queries.test_having_inside_except = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select count("e") - 1 from "arithmetic_space"
+            having count("e") = 4
+            except
+            select "b" from "arithmetic_space"
+            group by "b"
+            having count(distinct "d") > 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "COL_1", type = "decimal" },
+    })
+    t.assert_items_equals(r.rows, {})
+end
+
+groupby_queries.test_having_inside_except1 = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", {
+        [[
+            select "d" from "arithmetic_space"
+            group by "d"
+            except
+            select sum(distinct "c") from "arithmetic_space"
+            having count(distinct "c") = 1
+        ]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, {
+        { name = "d", type = "integer" },
+    })
+    t.assert_items_equals(r.rows, {
+        {2},
+    })
+end
diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs
index 97e478cbf7f2bd5512ec71688aa5be1c34db08c8..207de27a6654747f644e9051fae4356f86e45a3c 100644
--- a/sbroad-core/src/backend/sql/ir.rs
+++ b/sbroad-core/src/backend/sql/ir.rs
@@ -302,6 +302,7 @@ impl ExecutionPlan {
                             Node::Relational(rel) => match rel {
                                 Relational::Except { .. } => sql.push_str("EXCEPT"),
                                 Relational::GroupBy { .. } => sql.push_str("GROUP BY"),
+                                Relational::Having { .. } => sql.push_str("HAVING"),
                                 Relational::Insert { relation, .. } => {
                                     sql.push_str("INSERT INTO ");
                                     sql.push_str(relation.as_str());
diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs
index 876e8c119becbad08972947576525d01369d6e70..9db619b1c000491770be4f3fd66069677a7845ab 100644
--- a/sbroad-core/src/backend/sql/tree.rs
+++ b/sbroad-core/src/backend/sql/tree.rs
@@ -383,6 +383,8 @@ struct Select {
     join: Option<usize>,
     /// GroupBy syntax node
     groupby: Option<usize>,
+    /// Having syntax node
+    having: Option<usize>,
 }
 
 type NodeAdder = fn(&mut Select, usize, &SyntaxPlan) -> Result<bool, SbroadError>;
@@ -494,17 +496,60 @@ impl Select {
         }
     }
 
-    /// Constructor.
+    fn add_having(select: &mut Select, id: usize, sp: &SyntaxPlan) -> Result<bool, SbroadError> {
+        let sn = sp.nodes.get_syntax_node(id)?;
+        if let Node::Relational(Relational::Having { .. }) = sp.plan_node_or_err(&sn.data)? {
+            select.having = Some(id);
+            let left_id = sn.left_id_or_err()?;
+            let sn_left = sp.nodes.get_syntax_node(left_id)?;
+            let plan_node_left = sp.plan_node_or_err(&sn_left.data)?;
+            if let Node::Relational(
+                Relational::ScanRelation { .. }
+                | Relational::ScanSubQuery { .. }
+                | Relational::Motion { .. },
+            ) = plan_node_left
+            {
+                select.scan = left_id;
+                return Ok(true);
+            }
+            if !Select::add_one_of(
+                left_id,
+                select,
+                sp,
+                &[
+                    Select::add_selection,
+                    Select::add_inner_join,
+                    Select::add_groupby,
+                ],
+            )? {
+                return Err(SbroadError::Invalid(
+                    Entity::SyntaxPlan,
+                    Some(format!(
+                        "expected Scan or InnerJoin, or Selection, or GroupBy after Having. Got {plan_node_left:?}"
+                    ))));
+            }
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
+    /// `Select` node constructor.
     ///
-    /// There are several valid combinations of the `SELECT` command:
-    /// - projection -> selection -> join -> scan
-    /// - projection -> groupby -> selection -> join -> scan
-    /// - projection -> join -> scan
-    /// - projection -> groupby -> join -> scan
-    /// - projection -> selection -> scan
-    /// - projection -> groupby -> selection -> scan
-    /// - projection -> scan
-    /// - projection -> groupby -> scan
+    /// There are several valid combinations of the `SELECT` command.
+    /// The general view of all such commands is:
+    /// `Projection` -> set of additional relational operators (possibly empty) -> `Scan`,
+    /// where additional operators are: `Selection`, `Join`, `GroupBy`, `Having`.
+    ///
+    /// Some examples of valid sequences:
+    /// - `Projection` -> `Scan`
+    /// - `Projection` -> `Selection` -> `Join` -> `Scan`
+    /// - `Projection` -> `GroupBy` -> `Selection` -> `Join` -> `Scan`
+    /// - `Projection` -> `Having` -> `GroupBy` -> `Scan`
+    ///
+    /// Using functions `add_one_of` and `add_<rel_op_name>` constructor recursively traverses
+    /// `SyntaxNode`s tree and tries to add new children operators. In case none of allowed/required
+    /// children operators were met, it throws an error.
     fn new(
         sp: &SyntaxPlan,
         parent: Option<usize>,
@@ -521,6 +566,7 @@ impl Select {
                 selection: None,
                 join: None,
                 groupby: None,
+                having: None,
             };
             let left_id = sn.left_id_or_err()?;
             let sn_left = sp.nodes.get_syntax_node(left_id)?;
@@ -540,12 +586,13 @@ impl Select {
                     Select::add_selection,
                     Select::add_inner_join,
                     Select::add_groupby,
+                    Select::add_having,
                 ],
             )? {
                 return Err(SbroadError::Invalid(
                     Entity::SyntaxPlan,
                     Some(format!(
-                        "expected Scan, InnerJoin, Selection, GroupBy after Projection. Got {plan_node_left:?}"
+                        "expected Scan, InnerJoin, Selection, GroupBy, Having after Projection. Got {plan_node_left:?}"
                     ))));
             }
             if select.scan != 0 {
@@ -746,9 +793,12 @@ impl<'p> SyntaxPlan<'p> {
                 }
                 Relational::Selection {
                     children, filter, ..
+                }
+                | Relational::Having {
+                    children, filter, ..
                 } => {
                     let left_id = *children.first().ok_or_else(|| {
-                        SbroadError::UnexpectedNumberOfValues("Selection has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues(format!("{node:?} has no children."))
                     })?;
                     let filter_id = match self.snapshot {
                         Snapshot::Latest => *filter,
diff --git a/sbroad-core/src/executor/bucket.rs b/sbroad-core/src/executor/bucket.rs
index 6d3dffc17bb5747880ae946c1f9d3fe10b475821..18b65fcbb37d6d97526a391600520823a8d870c1 100644
--- a/sbroad-core/src/executor/bucket.rs
+++ b/sbroad-core/src/executor/bucket.rs
@@ -351,6 +351,9 @@ where
                 | Relational::GroupBy {
                     children, output, ..
                 }
+                | Relational::Having {
+                    children, output, ..
+                }
                 | Relational::ScanSubQuery {
                     children, output, ..
                 } => {
diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs
index 27fbf722bb2e9db495c8346e4474a72bbe1e4c11..6ecab455642b1f03d58c807b4a33ba9dc96bd77e 100644
--- a/sbroad-core/src/executor/ir.rs
+++ b/sbroad-core/src/executor/ir.rs
@@ -197,6 +197,7 @@ impl ExecutionPlan {
             | Relational::Selection { .. }
             | Relational::UnionAll { .. }
             | Relational::Values { .. }
+            | Relational::Having { .. }
             | Relational::ValuesRow { .. } => Ok(*top_id),
             Relational::Motion { .. } | Relational::Insert { .. } => Err(SbroadError::Invalid(
                 Entity::Relational,
@@ -398,12 +399,16 @@ impl ExecutionPlan {
                         filter: ref mut expr_id,
                         ..
                     }
+                    | Relational::Having {
+                        filter: ref mut expr_id,
+                        ..
+                    }
                     | Relational::Join {
                         condition: ref mut expr_id,
                         ..
                     } = rel
                     {
-                        // We transform selection's filter and join's condition to DNF for a better bucket calculation.
+                        // We transform selection's, having's filter and join's condition to DNF for a better bucket calculation.
                         // But as a result we can produce an extremely verbose SQL query from such a plan (tarantool's
                         // parser can fail to parse such SQL).
 
diff --git a/sbroad-core/src/executor/tests/exec_plan.rs b/sbroad-core/src/executor/tests/exec_plan.rs
index 2cd9343e2f8ab902c9fe9e53ef9d447cc2f491c2..905b3297c3696685015365204e5a4bc5b9b4989f 100644
--- a/sbroad-core/src/executor/tests/exec_plan.rs
+++ b/sbroad-core/src/executor/tests/exec_plan.rs
@@ -660,3 +660,191 @@ fn exec_plan_subtree_count_asterisk() {
         )
     );
 }
+
+#[test]
+fn exec_plan_subtree_having() {
+    let sql = format!(
+        "{} {} {}",
+        r#"SELECT t1."sys_op" || t1."sys_op", count(t1."sys_op"*2) + count(distinct t1."sys_op"*2)"#,
+        r#"FROM "test_space" as t1 group by t1."sys_op""#,
+        r#"HAVING sum(distinct t1."sys_op"*2) > 1"#
+    );
+    let coordinator = RouterRuntimeMock::new();
+
+    let mut query = Query::new(&coordinator, sql.as_str(), vec![]).unwrap();
+    let motion_id = *query
+        .exec_plan
+        .get_ir_plan()
+        .clone_slices()
+        .slice(0)
+        .unwrap()
+        .position(0)
+        .unwrap();
+    let mut virtual_table = VirtualTable::new();
+    virtual_table.add_column(Column {
+        name: "column_63".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    virtual_table.add_column(Column {
+        name: "column_12".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    virtual_table.add_column(Column {
+        name: "count_58".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id)
+    {
+        virtual_table.reshard(key, &query.coordinator).unwrap();
+    }
+
+    let mut vtables: HashMap<usize, Rc<VirtualTable>> = HashMap::new();
+    vtables.insert(motion_id, Rc::new(virtual_table));
+
+    let exec_plan = query.get_mut_exec_plan();
+    exec_plan.set_vtables(vtables);
+    let top_id = exec_plan.get_ir_plan().get_top().unwrap();
+    let motion_child_id = exec_plan.get_motion_subtree_root(motion_id).unwrap();
+
+    // Check groupby local stage
+    let subplan1 = exec_plan.take_subtree(motion_child_id).unwrap();
+    let subplan1_top_id = subplan1.get_ir_plan().get_top().unwrap();
+    let sp = SyntaxPlan::new(&subplan1, subplan1_top_id, Snapshot::Oldest).unwrap();
+    let ordered = OrderedSyntaxNodes::try_from(sp).unwrap();
+    let nodes = ordered.to_syntax_data().unwrap();
+    let (sql, _) = subplan1.to_sql(&nodes, &Buckets::All, "test").unwrap();
+    if let MotionPolicy::Segment(_) = exec_plan.get_motion_policy(motion_id).unwrap() {
+    } else {
+        panic!("Expected MotionPolicy::Segment for local aggregation stage");
+    };
+    assert_eq!(
+        sql,
+        PatternWithParams::new(
+            format!(
+                "{} {} {}",
+                r#"SELECT "T1"."sys_op" as "column_12", ("T1"."sys_op") * (?) as "column_63","#,
+                r#"count (("T1"."sys_op") * (?)) as "count_58" FROM "test_space" as "T1""#,
+                r#"GROUP BY "T1"."sys_op", ("T1"."sys_op") * (?)"#,
+            ),
+            vec![Value::Unsigned(2), Value::Unsigned(2), Value::Unsigned(2)]
+        )
+    );
+
+    // Check main query
+    let subplan2 = exec_plan.take_subtree(top_id).unwrap();
+    let subplan2_top_id = subplan2.get_ir_plan().get_top().unwrap();
+    let sp = SyntaxPlan::new(&subplan2, subplan2_top_id, Snapshot::Oldest).unwrap();
+    let ordered = OrderedSyntaxNodes::try_from(sp).unwrap();
+    let nodes = ordered.to_syntax_data().unwrap();
+    let (sql, _) = subplan2.to_sql(&nodes, &Buckets::All, "test").unwrap();
+    assert_eq!(
+        sql,
+        PatternWithParams::new(
+            format!(
+                "{} {} {} {}",
+                r#"SELECT ("column_12") || ("column_12") as "COL_1","#,
+                r#"(sum ("count_58")) + (count (DISTINCT "column_63")) as "COL_2" FROM"#,
+                r#"(SELECT "column_63","column_12","count_58" FROM "TMP_test_22")"#,
+                r#"GROUP BY "column_12" HAVING (sum (DISTINCT "column_63")) > (?)"#
+            ),
+            vec![Value::Unsigned(1u64)]
+        )
+    );
+}
+
+#[test]
+fn exec_plan_subtree_having_without_groupby() {
+    let sql = format!(
+        "{} {} {}",
+        r#"SELECT count(t1."sys_op"*2) + count(distinct t1."sys_op"*2)"#,
+        r#"FROM "test_space" as t1"#,
+        r#"HAVING sum(distinct t1."sys_op"*2) > 1"#
+    );
+    let coordinator = RouterRuntimeMock::new();
+
+    let mut query = Query::new(&coordinator, sql.as_str(), vec![]).unwrap();
+    let motion_id = *query
+        .exec_plan
+        .get_ir_plan()
+        .clone_slices()
+        .slice(0)
+        .unwrap()
+        .position(0)
+        .unwrap();
+    let mut virtual_table = VirtualTable::new();
+    virtual_table.add_column(Column {
+        name: "column_63".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    virtual_table.add_column(Column {
+        name: "column_12".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    virtual_table.add_column(Column {
+        name: "count_58".into(),
+        r#type: Type::Integer,
+        role: ColumnRole::User,
+    });
+    if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id)
+    {
+        virtual_table.reshard(key, &query.coordinator).unwrap();
+    }
+
+    let mut vtables: HashMap<usize, Rc<VirtualTable>> = HashMap::new();
+    vtables.insert(motion_id, Rc::new(virtual_table));
+
+    let exec_plan = query.get_mut_exec_plan();
+    exec_plan.set_vtables(vtables);
+    let top_id = exec_plan.get_ir_plan().get_top().unwrap();
+    let motion_child_id = exec_plan.get_motion_subtree_root(motion_id).unwrap();
+
+    // Check groupby local stage
+    let subplan1 = exec_plan.take_subtree(motion_child_id).unwrap();
+    let subplan1_top_id = subplan1.get_ir_plan().get_top().unwrap();
+    let sp = SyntaxPlan::new(&subplan1, subplan1_top_id, Snapshot::Oldest).unwrap();
+    let ordered = OrderedSyntaxNodes::try_from(sp).unwrap();
+    let nodes = ordered.to_syntax_data().unwrap();
+    let (sql, _) = subplan1.to_sql(&nodes, &Buckets::All, "test").unwrap();
+    if let MotionPolicy::Full = exec_plan.get_motion_policy(motion_id).unwrap() {
+    } else {
+        panic!("Expected MotionPolicy::Full after local stage");
+    };
+
+    assert_eq!(
+        sql,
+        PatternWithParams::new(
+            format!(
+                "{} {} {}",
+                r#"SELECT ("T1"."sys_op") * (?) as "column_44","#,
+                r#"count (("T1"."sys_op") * (?)) as "count_39" FROM "test_space" as "T1""#,
+                r#"GROUP BY ("T1"."sys_op") * (?)"#,
+            ),
+            vec![Value::Unsigned(2), Value::Unsigned(2), Value::Unsigned(2)]
+        )
+    );
+
+    // Check main query
+    let subplan2 = exec_plan.take_subtree(top_id).unwrap();
+    let subplan2_top_id = subplan2.get_ir_plan().get_top().unwrap();
+    let sp = SyntaxPlan::new(&subplan2, subplan2_top_id, Snapshot::Oldest).unwrap();
+    let ordered = OrderedSyntaxNodes::try_from(sp).unwrap();
+    let nodes = ordered.to_syntax_data().unwrap();
+    let (sql, _) = subplan2.to_sql(&nodes, &Buckets::All, "test").unwrap();
+    assert_eq!(
+        sql,
+        PatternWithParams::new(
+            format!(
+                "{} {} {}",
+                r#"SELECT (sum ("count_39")) + (count (DISTINCT "column_44")) as "COL_1""#,
+                r#"FROM (SELECT "column_63","column_12","count_58" FROM "TMP_test_14")"#,
+                r#"HAVING (sum (DISTINCT "column_44")) > (?)"#,
+            ),
+            vec![Value::Unsigned(1u64)]
+        )
+    );
+}
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 36b5e37440256963778bb3626dde5babb4971275..39ed7284000701f001057a53e5ed38476c62608b 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -135,10 +135,6 @@ impl Ast for AbstractSyntaxTree {
         // Is it global for every `ValuesRow` met in the AST.
         let mut col_idx: usize = 0;
 
-        let mut groupby_nodes: Vec<usize> = Vec::new();
-        let mut scan_nodes: Vec<usize> = Vec::new();
-        let mut sq_nodes: Vec<usize> = Vec::new();
-
         let mut betweens: Vec<Between> = Vec::new();
         // Ids of arithmetic expressions that have parentheses.
         let mut arith_expr_with_parentheses_ids: Vec<usize> = Vec::new();
@@ -251,7 +247,6 @@ impl Ast for AbstractSyntaxTree {
                         let t = metadata.table(table)?;
                         plan.add_rel(t);
                         let scan_id = plan.add_scan(&normalize_name_from_sql(table), None)?;
-                        scan_nodes.push(scan_id);
                         map.add(id, scan_id);
                     } else {
                         return Err(SbroadError::Invalid(
@@ -268,7 +263,6 @@ impl Ast for AbstractSyntaxTree {
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let plan_sq_id = plan.add_sub_query(plan_child_id, None)?;
-                    sq_nodes.push(plan_sq_id);
                     map.add(id, plan_sq_id);
                 }
                 Type::Reference => {
@@ -788,7 +782,6 @@ impl Ast for AbstractSyntaxTree {
                         children.push(plan_column_id);
                     }
                     let groupby_id = plan.add_groupby_from_ast(&children)?;
-                    groupby_nodes.push(groupby_id);
                     map.add(id, groupby_id);
                 }
                 Type::Join => {
@@ -827,9 +820,9 @@ impl Ast for AbstractSyntaxTree {
                         plan.add_join(plan_left_id, plan_right_id, plan_cond_id, kind)?;
                     map.add(id, plan_join_id);
                 }
-                Type::Selection => {
+                Type::Selection | Type::Having => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        SbroadError::UnexpectedNumberOfValues("Selection has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues(format!("{node:?} has no children."))
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let ast_filter_id = node.children.get(1).ok_or_else(|| {
@@ -839,8 +832,12 @@ impl Ast for AbstractSyntaxTree {
                         )
                     })?;
                     let plan_filter_id = map.get(*ast_filter_id)?;
-                    let plan_selection_id = plan.add_select(&[plan_child_id], plan_filter_id)?;
-                    map.add(id, plan_selection_id);
+                    let plan_node_id = match &node.rule {
+                        Type::Selection => plan.add_select(&[plan_child_id], plan_filter_id)?,
+                        Type::Having => plan.add_having(&[plan_child_id], plan_filter_id)?,
+                        _ => return Err(SbroadError::Invalid(Entity::AST, None)), // never happens
+                    };
+                    map.add(id, plan_node_id);
                 }
                 Type::Projection => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs
index c559a831b2c10f3e8e21667217f3724ecb62cd28..eb48590810839c13f78b81bed145a0972da0acda 100644
--- a/sbroad-core/src/frontend/sql/ast.rs
+++ b/sbroad-core/src/frontend/sql/ast.rs
@@ -55,6 +55,7 @@ pub enum Type {
     GtEq,
     GroupBy,
     GroupingElement,
+    Having,
     In,
     InnerJoinKind,
     Join,
@@ -143,6 +144,7 @@ impl Type {
             Rule::GroupingElement => Ok(Type::GroupingElement),
             Rule::Gt => Ok(Type::Gt),
             Rule::GtEq => Ok(Type::GtEq),
+            Rule::Having => Ok(Type::Having),
             Rule::In => Ok(Type::In),
             Rule::InnerJoinKind => Ok(Type::InnerJoinKind),
             Rule::Join => Ok(Type::Join),
@@ -234,6 +236,7 @@ impl fmt::Display for Type {
             Type::FunctionName => "FunctionName".to_string(),
             Type::Gt => "Gt".to_string(),
             Type::GtEq => "GtEq".to_string(),
+            Type::Having => "Having".to_string(),
             Type::InnerJoinKind => "inner".to_string(),
             Type::In => "In".to_string(),
             Type::Join => "Join".to_string(),
@@ -572,6 +575,7 @@ impl AbstractSyntaxTree {
                 3 => self.transform_select_3(*node, &children)?,
                 4 => self.transform_select_4(*node, &children)?,
                 5 => self.transform_select_5(*node, &children)?,
+                6 => self.transform_select_6(*node, &children)?,
                 _ => return Err(SbroadError::Invalid(Entity::AST, None)),
             }
         }
@@ -676,13 +680,15 @@ impl AbstractSyntaxTree {
             [Type::Projection, Type::Scan, Type::Join],
             [Type::Projection, Type::Scan, Type::GroupBy],
             [Type::Projection, Type::Scan, Type::Selection],
+            [Type::Projection, Type::Scan, Type::Having],
         ];
         self.check(&allowed, children)?;
         self.nodes
             .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?;
         self.nodes
             .push_front_child(get_or_err(children, 0)?, get_or_err(children, 2)?)?;
-        self.nodes.set_children(select_id, vec![children[0]])?;
+        self.nodes
+            .set_children(select_id, vec![get_or_err(children, 0)?])?;
         Ok(())
     }
 
@@ -693,8 +699,11 @@ impl AbstractSyntaxTree {
     ) -> Result<(), SbroadError> {
         let allowed = [
             [Type::Projection, Type::Scan, Type::Selection, Type::GroupBy],
+            [Type::Projection, Type::Scan, Type::Selection, Type::Having],
+            [Type::Projection, Type::Scan, Type::GroupBy, Type::Having],
             [Type::Projection, Type::Scan, Type::Join, Type::Selection],
             [Type::Projection, Type::Scan, Type::Join, Type::GroupBy],
+            [Type::Projection, Type::Scan, Type::Join, Type::Having],
         ];
         self.check(&allowed, children)?;
         // insert Selection | InnerJoin as first child of GroupBy
@@ -714,6 +723,58 @@ impl AbstractSyntaxTree {
         &mut self,
         select_id: usize,
         children: &[usize],
+    ) -> Result<(), SbroadError> {
+        let allowed = [
+            [
+                Type::Projection,
+                Type::Scan,
+                Type::Join,
+                Type::Selection,
+                Type::GroupBy,
+            ],
+            [
+                Type::Projection,
+                Type::Scan,
+                Type::Join,
+                Type::Selection,
+                Type::Having,
+            ],
+            [
+                Type::Projection,
+                Type::Scan,
+                Type::Join,
+                Type::GroupBy,
+                Type::Having,
+            ],
+            [
+                Type::Projection,
+                Type::Scan,
+                Type::Selection,
+                Type::GroupBy,
+                Type::Having,
+            ],
+        ];
+        self.check(&allowed, children)?;
+        // insert Selection as first child of GroupBy
+        self.nodes
+            .push_front_child(get_or_err(children, 4)?, get_or_err(children, 3)?)?;
+        // insert InnerJoin as first child of Selection
+        self.nodes
+            .push_front_child(get_or_err(children, 3)?, get_or_err(children, 2)?)?;
+        // insert Scan as first child of InnerJoin
+        self.nodes
+            .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?;
+        // insert GroupBy as first child of Projection
+        self.nodes
+            .push_front_child(get_or_err(children, 0)?, get_or_err(children, 4)?)?;
+        self.nodes.set_children(select_id, vec![children[0]])?;
+        Ok(())
+    }
+
+    fn transform_select_6(
+        &mut self,
+        select_id: usize,
+        children: &[usize],
     ) -> Result<(), SbroadError> {
         let allowed = [[
             Type::Projection,
@@ -721,8 +782,12 @@ impl AbstractSyntaxTree {
             Type::Join,
             Type::Selection,
             Type::GroupBy,
+            Type::Having,
         ]];
         self.check(&allowed, children)?;
+        // insert GroupBy as first child of Having
+        self.nodes
+            .push_front_child(get_or_err(children, 5)?, get_or_err(children, 4)?)?;
         // insert Selection as first child of GroupBy
         self.nodes
             .push_front_child(get_or_err(children, 4)?, get_or_err(children, 3)?)?;
@@ -732,9 +797,9 @@ impl AbstractSyntaxTree {
         // insert Scan as first child of InnerJoin
         self.nodes
             .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?;
-        // insert GroupBy as first child of Projection
+        // insert Having as first child of Projection
         self.nodes
-            .push_front_child(get_or_err(children, 0)?, get_or_err(children, 4)?)?;
+            .push_front_child(get_or_err(children, 0)?, get_or_err(children, 5)?)?;
         self.nodes.set_children(select_id, vec![children[0]])?;
         Ok(())
     }
@@ -862,11 +927,12 @@ impl AbstractSyntaxTree {
                         }
                     }
                 }
-                Type::Selection => {
+                Type::Selection | Type::Having => {
                     let rel_id = rel_node.children.first().ok_or_else(|| {
-                        SbroadError::UnexpectedNumberOfValues(
-                            "AST selection has no children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues(format!(
+                            "AST {:?} has no children.",
+                            rel_node.rule
+                        ))
                     })?;
                     let filter = rel_node.children.get(1).ok_or_else(|| {
                         SbroadError::NotFound(
diff --git a/sbroad-core/src/frontend/sql/ast/tests.rs b/sbroad-core/src/frontend/sql/ast/tests.rs
index ced600ae834c9c80d03a4fb779246fc3294eb7ea..958b81ed0a6caa8d6ac5a7055b45eb6c673a964e 100644
--- a/sbroad-core/src/frontend/sql/ast/tests.rs
+++ b/sbroad-core/src/frontend/sql/ast/tests.rs
@@ -71,7 +71,22 @@ fn transform_select_3_group_by() {
 }
 
 #[test]
-fn transform_select_4() {
+fn transform_select_3_having() {
+    let query = r#"select sum("a") from t having sum("a") > 1"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_3_having.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_3_join() {
     let query = r#"select * from t1 inner join t2 on t1.a = t2.a"#;
     let ast = AbstractSyntaxTree::new(query).unwrap();
     let path = Path::new("")
@@ -79,7 +94,7 @@ fn transform_select_4() {
         .join("artifactory")
         .join("frontend")
         .join("sql")
-        .join("transform_select_4.yaml");
+        .join("transform_select_3_join.yaml");
     let s = fs::read_to_string(path).unwrap();
     let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
     assert_eq!(expected, ast);
@@ -101,7 +116,37 @@ fn transform_select_4_1() {
 }
 
 #[test]
-fn transform_select_5() {
+fn transform_select_4_having_1() {
+    let query = r#"select sum("a") from t1 where "a" > 1 having sum("a") > 1"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_4_having_1.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_4_having_2() {
+    let query = r#"select sum("a") from t1 group by "a" having sum("a") > 1"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_4_having_2.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_4_join_filter() {
     let query = r#"select * from t1 inner join t2 on t1.a = t2.a where t1.a > 0"#;
     let ast = AbstractSyntaxTree::new(query).unwrap();
     let path = Path::new("")
@@ -109,14 +154,14 @@ fn transform_select_5() {
         .join("artifactory")
         .join("frontend")
         .join("sql")
-        .join("transform_select_5.yaml");
+        .join("transform_select_4_join_filter.yaml");
     let s = fs::read_to_string(path).unwrap();
     let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
     assert_eq!(expected, ast);
 }
 
 #[test]
-fn transform_select_5_1() {
+fn transform_select_4_join_groupby() {
     let query = r#"select t1.a, t2.b from t1 inner join t2 on t1.a = t2.a group by t1.a, t2.b"#;
     let ast = AbstractSyntaxTree::new(query).unwrap();
     let path = Path::new("")
@@ -124,16 +169,91 @@ fn transform_select_5_1() {
         .join("artifactory")
         .join("frontend")
         .join("sql")
-        .join("transform_select_5_1.yaml");
+        .join("transform_select_4_join_groupby.yaml");
     let s = fs::read_to_string(path).unwrap();
     let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
     assert_eq!(expected, ast);
 }
 
 #[test]
-fn transform_select_6() {
+fn transform_select_4_join_having() {
+    let query = r#"select sum(a) from t1 inner join t2 on t1.a = t2.a having sum(t1.a) > 1"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_4_join_having.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_4_selection_having() {
+    let query = r#"select sum(a) from t1 where "a" > 1 group by a having sum(t1.a) > 1"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_4_selection_having.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_5() {
     let query = r#"select t1.a, t2.b from t1 inner join t2 on t1.a = t2.a where t1.a > 1 group by t1.a, t2.b"#;
     let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_5.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_5_having_1() {
+    let query = r#"select sum(a) + f(b) from t1 inner join t2 on t1.a = t2.a group by t1.a, t2.b having sum(t1.a) > 1 and avg(t2.b) > 10"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_5_having_1.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_5_having_2() {
+    let query = r#"select sum(a) from t1 inner join t2 on t1.a = t2.a where a + b / 2 > 1 having avg(t2.b) > 10"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
+    let path = Path::new("")
+        .join("tests")
+        .join("artifactory")
+        .join("frontend")
+        .join("sql")
+        .join("transform_select_5_having_2.yaml");
+    let s = fs::read_to_string(path).unwrap();
+    let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
+    assert_eq!(expected, ast);
+}
+
+#[test]
+fn transform_select_6() {
+    let query = r#"select sum(a) from t1 inner join t2 on t1.a = t2.a where a + b / 2 > 1 group by a having avg(t2.b) > 10"#;
+    let ast = AbstractSyntaxTree::new(query).unwrap();
     let path = Path::new("")
         .join("tests")
         .join("artifactory")
diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs
index 533be17e09591abf3dbfea33732f634f3ae3d8e6..dc3642afb41605f264c551fc9f7c8ff9af387153 100644
--- a/sbroad-core/src/frontend/sql/ir.rs
+++ b/sbroad-core/src/frontend/sql/ir.rs
@@ -186,7 +186,8 @@ impl Plan {
                     Relational::Selection { filter: tree, .. }
                     | Relational::Join {
                         condition: tree, ..
-                    },
+                    }
+                    | Relational::Having { filter: tree, .. },
                 ) => {
                     let capacity = self.nodes.len();
                     let mut expr_post = PostOrder::with_capacity(
@@ -233,7 +234,9 @@ impl Plan {
             // Append sub-query to relational node if it is not already there (can happen with BETWEEN).
             match self.get_mut_node(sq.relational)? {
                 Node::Relational(
-                    Relational::Selection { children, .. } | Relational::Join { children, .. },
+                    Relational::Selection { children, .. }
+                    | Relational::Join { children, .. }
+                    | Relational::Having { children, .. },
                 ) => {
                     // O(n) can become a problem.
                     if !children.contains(&sq.sq) {
@@ -251,7 +254,9 @@ impl Plan {
             // Generate a reference to the sub-query.
             let row_id: usize = match self.get_node(sq.relational)? {
                 Node::Relational(
-                    Relational::Selection { children, .. } | Relational::Join { children, .. },
+                    Relational::Selection { children, .. }
+                    | Relational::Join { children, .. }
+                    | Relational::Having { children, .. },
                 ) => {
                     let nodes = children.clone();
                     let sq_output = self.get_relation_node(sq.sq)?.output();
diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs
index 2606e50f84b7681c4f0893b26101dc83b86df27a..9787436a776095e78d1dbf58457ea0e3ed264cbf 100644
--- a/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -1530,6 +1530,210 @@ motion [policy: full]
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
+#[test]
+fn front_sql_having1() {
+    let input = r#"SELECT "a", sum("b") FROM "t"
+        group by "a"
+        having "a" > 1 and sum(distinct "b") > 1
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+    let expected_explain = String::from(
+        r#"projection ("column_12" -> "a", sum(("sum_52")) -> "COL_1")
+    having ROW("column_12") > ROW(1) and ROW(sum(distinct ("column_30"))) > ROW(1)
+        group by ("column_12") output: ("column_30" -> "column_30", "column_12" -> "column_12", "sum_52" -> "sum_52")
+            motion [policy: segment([ref("column_12")])]
+                scan
+                    projection ("t"."b" -> "column_30", "t"."a" -> "column_12", sum(("t"."b")) -> "sum_52")
+                        group by ("t"."a", "t"."b") output: ("t"."a" -> "a", "t"."b" -> "b", "t"."c" -> "c", "t"."d" -> "d", "t"."bucket_id" -> "bucket_id")
+                            scan "t"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
+#[test]
+fn front_sql_having2() {
+    let input = r#"SELECT sum("a") * count(distinct "b"), sum("a") FROM "t"
+        having sum(distinct "b") > 1 and sum("a") > 1
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+    let expected_explain = String::from(
+        r#"projection ((sum(("sum_38"))) * (count(distinct ("column_39"))) -> "COL_1", sum(("sum_38")) -> "COL_2")
+    having ROW(sum(distinct ("column_39"))) > ROW(1) and ROW(sum(("sum_38"))) > ROW(1)
+        motion [policy: full]
+            scan
+                projection ("t"."b" -> "column_39", sum(("t"."a")) -> "sum_38")
+                    group by ("t"."b") output: ("t"."a" -> "a", "t"."b" -> "b", "t"."c" -> "c", "t"."d" -> "d", "t"."bucket_id" -> "bucket_id")
+                        scan "t"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
+#[test]
+fn front_sql_having3() {
+    let input = r#"SELECT sum("a") FROM "t"
+        having sum("a") > 1
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+    let expected_explain = String::from(
+        r#"projection (sum(("sum_31")) -> "COL_1")
+    having ROW(sum(("sum_31"))) > ROW(1)
+        motion [policy: full]
+            scan
+                projection (sum(("t"."a")) -> "sum_31")
+                    scan "t"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
+#[test]
+fn front_sql_having4() {
+    let input = r#"SELECT sum("a") FROM "t"
+        having sum("a") > 1 and "b" > 1
+    "#;
+
+    let metadata = &RouterConfigurationMock::new();
+    let ast = AbstractSyntaxTree::new(input).unwrap();
+    let mut plan = ast.resolve_metadata(metadata).unwrap();
+    plan.replace_in_operator().unwrap();
+    plan.split_columns().unwrap();
+    plan.set_dnf().unwrap();
+    plan.derive_equalities().unwrap();
+    plan.merge_tuples().unwrap();
+    let err = plan.add_motions().unwrap_err();
+
+    assert_eq!(
+        true,
+        err.to_string()
+            .contains("HAVING argument must appear in the GROUP BY clause or be used in an aggregate function")
+    );
+}
+
+#[test]
+fn front_sql_having_with_sq() {
+    let input = r#"
+        SELECT "sysFrom", sum(distinct "id") as "sum", count(distinct "id") as "count" from "test_space"
+        group by "sysFrom"
+        having (select "sysFrom" from "test_space" where "sysFrom" = 2) > count(distinct "id")
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+
+    let expected_explain = String::from(
+        r#"projection ("column_12" -> "sysFrom", sum(distinct ("column_80")) -> "sum", count(distinct ("column_80")) -> "count")
+    having ROW($0) > ROW(count(distinct ("column_80")))
+        group by ("column_12") output: ("column_12" -> "column_12", "column_80" -> "column_80")
+            motion [policy: segment([ref("column_12")])]
+                scan
+                    projection ("test_space"."sysFrom" -> "column_12", "test_space"."id" -> "column_80")
+                        group by ("test_space"."sysFrom", "test_space"."id") output: ("test_space"."id" -> "id", "test_space"."sysFrom" -> "sysFrom", "test_space"."FIRST_NAME" -> "FIRST_NAME", "test_space"."sys_op" -> "sys_op", "test_space"."bucket_id" -> "bucket_id")
+                            scan "test_space"
+subquery $0:
+motion [policy: full]
+            scan
+                projection ("test_space"."sysFrom" -> "sysFrom")
+                    selection ROW("test_space"."sysFrom") = ROW(2)
+                        scan "test_space"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
+#[test]
+fn front_sql_unmatched_column_in_having() {
+    let input = r#"SELECT sum("a"), "a" FROM "t"
+        group by "a"
+        having sum("a") > 1 and "a" > 1 or "c" = 1
+    "#;
+
+    let metadata = &RouterConfigurationMock::new();
+    let ast = AbstractSyntaxTree::new(input).unwrap();
+    let mut plan = ast.resolve_metadata(metadata).unwrap();
+    plan.replace_in_operator().unwrap();
+    plan.split_columns().unwrap();
+    plan.set_dnf().unwrap();
+    plan.derive_equalities().unwrap();
+    plan.merge_tuples().unwrap();
+    let err = plan.add_motions().unwrap_err();
+
+    assert_eq!(
+        true,
+        err.to_string()
+            .contains("column \"c\" is not found in grouping expressions!")
+    );
+}
+
+#[test]
+fn front_sql_having_with_sq_segment_motion() {
+    // check subquery has Segment Motion on groupby columns
+    let input = r#"
+        SELECT "sysFrom", "sys_op", sum(distinct "id") as "sum", count(distinct "id") as "count" from "test_space"
+        group by "sysFrom", "sys_op"
+        having ("sysFrom", "sys_op") in (select "a", "d" from "t")
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+
+    let expected_explain = String::from(
+        r#"projection ("column_12" -> "sysFrom", "column_13" -> "sys_op", sum(distinct ("column_70")) -> "sum", count(distinct ("column_70")) -> "count")
+    having ROW("column_12", "column_13") in ROW($0, $0)
+        group by ("column_12", "column_13") output: ("column_12" -> "column_12", "column_70" -> "column_70", "column_13" -> "column_13")
+            motion [policy: segment([ref("column_12"), ref("column_13")])]
+                scan
+                    projection ("test_space"."sysFrom" -> "column_12", "test_space"."id" -> "column_70", "test_space"."sys_op" -> "column_13")
+                        group by ("test_space"."sysFrom", "test_space"."sys_op", "test_space"."id") output: ("test_space"."id" -> "id", "test_space"."sysFrom" -> "sysFrom", "test_space"."FIRST_NAME" -> "FIRST_NAME", "test_space"."sys_op" -> "sys_op", "test_space"."bucket_id" -> "bucket_id")
+                            scan "test_space"
+subquery $0:
+motion [policy: segment([ref("a"), ref("d")])]
+            scan
+                projection ("t"."a" -> "a", "t"."d" -> "d")
+                    scan "t"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
+#[test]
+fn front_sql_having_with_sq_segment_local_motion() {
+    // check subquery has no Motion, as it has the same distribution
+    // as columns used in GroupBy
+    let input = r#"
+        SELECT "sysFrom", "sys_op", sum(distinct "id") as "sum", count(distinct "id") as "count" from "test_space"
+        group by "sysFrom", "sys_op"
+        having ("sysFrom", "sys_op") in (select "a", "b" from "t")
+    "#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+
+    let expected_explain = String::from(
+        r#"projection ("column_12" -> "sysFrom", "column_13" -> "sys_op", sum(distinct ("column_70")) -> "sum", count(distinct ("column_70")) -> "count")
+    having ROW("column_12", "column_13") in ROW($0, $0)
+        group by ("column_12", "column_13") output: ("column_12" -> "column_12", "column_70" -> "column_70", "column_13" -> "column_13")
+            motion [policy: segment([ref("column_12"), ref("column_13")])]
+                scan
+                    projection ("test_space"."sysFrom" -> "column_12", "test_space"."id" -> "column_70", "test_space"."sys_op" -> "column_13")
+                        group by ("test_space"."sysFrom", "test_space"."sys_op", "test_space"."id") output: ("test_space"."id" -> "id", "test_space"."sysFrom" -> "sysFrom", "test_space"."FIRST_NAME" -> "FIRST_NAME", "test_space"."sys_op" -> "sys_op", "test_space"."bucket_id" -> "bucket_id")
+                            scan "test_space"
+subquery $0:
+scan
+            projection ("t"."a" -> "a", "t"."b" -> "b")
+                scan "t"
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
 #[test]
 fn front_sql_unique_local_aggregates() {
     // make sure we don't compute extra aggregates at local stage
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index 976ad55961cb7ad70fe546895166c686e3795e3a..cb938d450f8f84476dd5e2c33955f03aa8b70954 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -3,12 +3,12 @@ Command = _{ SOI ~ (ExplainQuery | Query) ~ EOF }
 ExplainQuery = _{ Explain }
     Explain = { ^"explain" ~ Query }
 
-// todo(ars): rename InnerJoin to Join
 Query = _{ Except | UnionAll | Select | Values | Insert }
     Select = {
         ^"select" ~ Projection ~ ^"from" ~ Scan ~
         Join? ~ (^"where" ~ Selection)? ~ 
-        (^"group" ~ ^"by" ~ GroupBy)?
+        (^"group" ~ ^"by" ~ GroupBy)? ~
+        (^"having" ~ Having)?
     }
         Projection = { (Asterisk | Column) ~ ("," ~ (Asterisk | Column))* }
             Column = { Alias | Expr | Value }
@@ -28,6 +28,7 @@ Query = _{ Except | UnionAll | Select | Values | Insert }
         LeftJoinKind = { ^"left" ~ (^"outer")? }
         Condition = { Expr }
         GroupBy = { GroupingElement ~ ("," ~ GroupingElement)* }
+        Having = { Expr }
     UnionAll = { Select ~ ^"union" ~ ^"all" ~ Select }
     Except = { Select ~ ((^"except" ~ ^"distinct") | ^"except") ~ Select }
     SubQuery = { "(" ~ (Except | UnionAll | Select | Values) ~ ")" }
diff --git a/sbroad-core/src/ir/aggregates.rs b/sbroad-core/src/ir/aggregates.rs
index 5fb60d50045186be804594b634b2dba367a9165d..7ec5495214e2579a278852331f316bbd2faaf822 100644
--- a/sbroad-core/src/ir/aggregates.rs
+++ b/sbroad-core/src/ir/aggregates.rs
@@ -180,7 +180,7 @@ impl SimpleAggregate {
     /// - Invalid aggregate
     /// - Could not find local alias position in child output
     #[allow(clippy::too_many_lines)]
-    pub fn create_column_for_final_projection(
+    pub fn create_final_aggregate_expr(
         &self,
         parent: usize,
         plan: &mut Plan,
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index 9c8da5f2d1d427de21160d22adfd25d49d2654f5..11b0513c1256e796dc2817accdcfd5e27b8ce480 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -441,26 +441,32 @@ impl Row {
                 Expression::Constant { value, .. } => {
                     row.add_col(RowVal::Const(value.clone()));
                 }
-                Expression::Reference { position, .. } => {
+                Expression::Reference { .. } => {
                     let rel_id: usize = *plan.get_relational_from_reference_node(*child)?;
 
                     let rel_node = plan.get_relation_node(rel_id)?;
-
-                    // If relation node doesn't have alias name it means
-                    // that this node is a sub-query and acts as a part of
-                    // the WHERE cause.
-                    if rel_node.scan_name(plan, *position)?.is_some() {
+                    if plan.is_additional_child(rel_id)? {
+                        if let Relational::ScanSubQuery { .. } | Relational::Motion { .. } =
+                            rel_node
+                        {
+                            let sq_offset = ref_map.get(&rel_id).ok_or_else(|| {
+                                SbroadError::NotFound(
+                                    Entity::SubQuery,
+                                    format!("with index {rel_id} in the map"),
+                                )
+                            })?;
+                            row.add_col(RowVal::SqRef(Ref::new(*sq_offset)));
+                        } else {
+                            return Err(SbroadError::Invalid(
+                                Entity::Plan,
+                                Some(format!(
+                                    "additional child ({rel_id}) is not SQ or Motion: {rel_node:?}"
+                                )),
+                            ));
+                        }
+                    } else {
                         let col = Col::new(plan, *child)?;
                         row.add_col(RowVal::Column(col));
-                    } else {
-                        let sq_offset = ref_map.get(&rel_id).ok_or_else(|| {
-                            SbroadError::NotFound(
-                                Entity::SubQuery,
-                                format!("with index {rel_id} in the map"),
-                            )
-                        })?;
-
-                        row.add_col(RowVal::SqRef(Ref::new(*sq_offset)));
                     }
                 }
                 Expression::Bool { .. }
@@ -724,6 +730,7 @@ enum ExplainNode {
     Projection(Projection),
     Scan(Scan),
     Selection(Selection),
+    Having(Selection),
     UnionAll,
     SubQuery(SubQuery),
     Motion(Motion),
@@ -741,6 +748,7 @@ impl Display for ExplainNode {
             ExplainNode::GroupBy(p) => p.to_string(),
             ExplainNode::Scan(s) => s.to_string(),
             ExplainNode::Selection(s) => format!("selection {s}"),
+            ExplainNode::Having(s) => format!("having {s}"),
             ExplainNode::UnionAll => "union all".to_string(),
             ExplainNode::SubQuery(s) => s.to_string(),
             ExplainNode::Motion(m) => m.to_string(),
@@ -879,6 +887,9 @@ impl FullExplain {
                 }
                 Relational::Selection {
                     children, filter, ..
+                }
+                | Relational::Having {
+                    children, filter, ..
                 } => {
                     let mut sq_ref_map: HashMap<usize, usize> =
                         HashMap::with_capacity(children.len() - 1);
@@ -906,7 +917,12 @@ impl FullExplain {
                     }
                     let filter_id = ir.undo.get_oldest(filter).map_or_else(|| *filter, |id| *id);
                     let s = Selection::new(ir, filter_id, &sq_ref_map)?;
-                    Some(ExplainNode::Selection(s))
+                    let explain_node = match &node {
+                        Relational::Selection { .. } => ExplainNode::Selection(s),
+                        Relational::Having { .. } => ExplainNode::Having(s),
+                        _ => return Err(SbroadError::DoSkip),
+                    };
+                    Some(explain_node)
                 }
                 Relational::UnionAll { .. } => {
                     if let (Some(right), Some(left)) = (stack.pop(), stack.pop()) {
diff --git a/sbroad-core/src/ir/helpers.rs b/sbroad-core/src/ir/helpers.rs
index a8276362436a15a4b88a08ee9a1c562030ae3d35..3d16cd302d1add3bd931dc208b44641228fec534 100644
--- a/sbroad-core/src/ir/helpers.rs
+++ b/sbroad-core/src/ir/helpers.rs
@@ -204,6 +204,12 @@ impl Plan {
                     writeln!(buf, "Filter")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *filter)?;
                 }
+                Relational::Having { filter, .. } => {
+                    writeln!(buf, "Having")?;
+                    formatted_tabulate(buf, tabulation_number + 1)?;
+                    writeln!(buf, "Filter")?;
+                    self.formatted_arena_node(buf, tabulation_number + 1, *filter)?;
+                }
                 Relational::GroupBy {
                     gr_cols, is_final, ..
                 } => {
@@ -240,6 +246,7 @@ impl Plan {
                 | Relational::Values { children, .. }
                 | Relational::Motion { children, .. }
                 | Relational::UnionAll { children, .. }
+                | Relational::Having { children, .. }
                 | Relational::GroupBy { children, .. }
                 | Relational::ValuesRow { children, .. } => {
                     formatted_tabulate(buf, tabulation_number + 1)?;
@@ -264,6 +271,7 @@ impl Plan {
                 | Relational::ScanSubQuery { output, .. }
                 | Relational::GroupBy { output, .. }
                 | Relational::Selection { output, .. }
+                | Relational::Having { output, .. }
                 | Relational::Values { output, .. }
                 | Relational::Motion { output, .. }
                 | Relational::UnionAll { output, .. }
diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs
index 69266f445b95f710db226966eef372f2ae9ca9e1..da17f7bb80f6f29834a774941fbc63b6224f5630 100644
--- a/sbroad-core/src/ir/operator.rs
+++ b/sbroad-core/src/ir/operator.rs
@@ -292,6 +292,11 @@ pub enum Relational {
         output: usize,
         is_final: bool,
     },
+    Having {
+        children: Vec<usize>,
+        output: usize,
+        filter: usize,
+    },
     UnionAll {
         /// Contains exactly two elements: left and right node indexes
         /// from the plan node arena.
@@ -369,6 +374,7 @@ impl Relational {
         match self {
             Relational::Except { output, .. }
             | Relational::GroupBy { output, .. }
+            | Relational::Having { output, .. }
             | Relational::Join { output, .. }
             | Relational::Insert { output, .. }
             | Relational::Motion { output, .. }
@@ -388,6 +394,7 @@ impl Relational {
         match self {
             Relational::Except { output, .. }
             | Relational::GroupBy { output, .. }
+            | Relational::Having { output, .. }
             | Relational::Join { output, .. }
             | Relational::Insert { output, .. }
             | Relational::Motion { output, .. }
@@ -408,6 +415,7 @@ impl Relational {
             Relational::Except { children, .. }
             | Relational::GroupBy { children, .. }
             | Relational::Join { children, .. }
+            | Relational::Having { children, .. }
             | Relational::Insert { children, .. }
             | Relational::Motion { children, .. }
             | Relational::Projection { children, .. }
@@ -430,6 +438,9 @@ impl Relational {
             | Relational::GroupBy {
                 ref mut children, ..
             }
+            | Relational::Having {
+                ref mut children, ..
+            }
             | Relational::Join {
                 ref mut children, ..
             }
@@ -525,6 +536,10 @@ impl Relational {
                 children: ref mut old,
                 ..
             }
+            | Relational::Having {
+                children: ref mut old,
+                ..
+            }
             | Relational::ValuesRow {
                 children: ref mut old,
                 ..
@@ -555,6 +570,7 @@ impl Relational {
             } => Ok(alias.as_deref().or(Some(relation.as_str()))),
             Relational::Projection { .. }
             | Relational::GroupBy { .. }
+            | Relational::Having { .. }
             | Relational::Selection { .. }
             | Relational::Join { .. } => {
                 let output_row = plan.get_expression_node(self.output())?;
@@ -1017,6 +1033,50 @@ impl Plan {
         Ok(select_id)
     }
 
+    /// Adds having node
+    ///
+    /// # Errors
+    /// - children list is empty
+    /// - filter expression is not boolean
+    /// - children nodes are not relational
+    /// - first child output tuple is not valid
+    pub fn add_having(&mut self, children: &[usize], filter: usize) -> Result<usize, SbroadError> {
+        let first_child: usize = match children.len() {
+            0 => {
+                return Err(SbroadError::UnexpectedNumberOfValues(
+                    "children list is empty".into(),
+                ))
+            }
+            _ => children[0],
+        };
+
+        if !self.is_trivalent(filter)? {
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("filter expression is not a trivalent expression.".into()),
+            ));
+        }
+
+        for child in children {
+            if let Node::Relational(_) = self.get_node(*child)? {
+            } else {
+                return Err(SbroadError::Invalid(Entity::Relational, None));
+            }
+        }
+
+        let output = self.add_row_for_output(first_child, &[], true)?;
+        let having = Relational::Having {
+            children: children.into(),
+            filter,
+            output,
+        };
+
+        let having_id = self.nodes.push(Node::Relational(having));
+        self.replace_parent_in_subtree(filter, None, Some(having_id))?;
+        self.replace_parent_in_subtree(output, None, Some(having_id))?;
+        Ok(having_id)
+    }
+
     /// Adds sub query scan node.
     ///
     /// # Errors
@@ -1309,7 +1369,7 @@ impl Plan {
         for (_, id) in rel_tree.iter(top_id) {
             let rel = self.get_relation_node(id)?;
             match rel {
-                Relational::Selection { children, .. } => {
+                Relational::Selection { children, .. } | Relational::Having { children, .. } => {
                     if children.iter().skip(1).any(|&c| c == node_id) {
                         return Ok(true);
                     }
@@ -1338,9 +1398,9 @@ impl Plan {
             return Ok(false);
         };
         match self.get_relation_node(rel_id)? {
-            Relational::Selection { .. } | Relational::Projection { .. } => {
-                Ok(children.first() != Some(&sq_id))
-            }
+            Relational::Selection { .. }
+            | Relational::Projection { .. }
+            | Relational::Having { .. } => Ok(children.first() != Some(&sq_id)),
             Relational::Join { .. } => {
                 Ok(children.first() != Some(&sq_id) && children.get(1) != Some(&sq_id))
             }
diff --git a/sbroad-core/src/ir/transformation/redistribution.rs b/sbroad-core/src/ir/transformation/redistribution.rs
index ee54b2319085132f338b0c171a24613a5b64f5ab..1b55fe29eeb8207cc61f5918070a7e60571f0256 100644
--- a/sbroad-core/src/ir/transformation/redistribution.rs
+++ b/sbroad-core/src/ir/transformation/redistribution.rs
@@ -1423,6 +1423,7 @@ impl Plan {
                 | Relational::ScanSubQuery { output, .. }
                 | Relational::Values { output, .. }
                 | Relational::GroupBy { output, .. }
+                | Relational::Having { output, .. }
                 | Relational::ValuesRow { output, .. } => {
                     self.set_distribution(output)?;
                 }
diff --git a/sbroad-core/src/ir/transformation/redistribution/groupby.rs b/sbroad-core/src/ir/transformation/redistribution/groupby.rs
index 6d9cbbc5b5b9a161b082117dd3ed6cb88bd8ed27..84d0d7c94ebcfc459a9dda54db54f97bdecb71b9 100644
--- a/sbroad-core/src/ir/transformation/redistribution/groupby.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/groupby.rs
@@ -5,7 +5,7 @@ use crate::ir::expression::Expression;
 use crate::ir::expression::Expression::StableFunction;
 use crate::ir::operator::Relational;
 use crate::ir::transformation::redistribution::{MotionKey, MotionPolicy, Strategy, Target};
-use crate::ir::tree::traversal::{PostOrder, EXPR_CAPACITY};
+use crate::ir::tree::traversal::{BreadthFirst, PostOrder, EXPR_CAPACITY};
 use crate::ir::{Node, Plan};
 use std::collections::{HashMap, HashSet};
 
@@ -283,7 +283,24 @@ impl<'plan> AggrCollector<'plan> {
     }
 }
 
+/// Maps id of `GroupBy` expression used in `GroupBy` (from local stage)
+/// to list of locations where this expression is used in other relational
+/// operators like `Having`, `Projection`.
+///
+/// For example:
+/// `select a from t group by a having a = 1`
+/// Here expression in `GroupBy` is mapped to `a` in `Projection` and `a` in `Having`
 type GroupbyExpressionsMap = HashMap<usize, Vec<ExpressionLocation>>;
+/// Maps id of `GroupBy` expression used in `GroupBy` (from local stage)
+/// to corresponding local alias used in local Projection. Note:
+/// this map does not contain mappings between grouping expressions from
+/// distinct aggregates (it is stored in corresponding `AggrInfo` for that
+/// aggregate)
+///
+/// For example:
+/// initial query: `select a, count(distinct b) from t group by a`
+/// map query: `select a as l1, b group by a, b`
+/// Then this map will map id of `a` to `l1`
 type LocalAliasesMap = HashMap<usize, Rc<String>>;
 type LocalAggrInfo = (AggregateKind, Vec<usize>, Rc<String>);
 
@@ -292,11 +309,9 @@ type LocalAggrInfo = (AggregateKind, Vec<usize>, Rc<String>);
 struct ExpressionMapper<'plan> {
     /// List of expressions ids of `GroupBy`
     gr_exprs: &'plan Vec<usize>,
-    /// Maps `GroupBy` expression to expressions used `Projection`
-    /// First element in pair is `Projection` column id,
-    /// second one is the id of expression
     map: GroupbyExpressionsMap,
     plan: &'plan Plan,
+    /// Id of relational node (`Projection`, `Having`, `OrderBy`)
     node_id: Option<usize>,
 }
 
@@ -316,6 +331,11 @@ impl<'plan> ExpressionMapper<'plan> {
     /// when match is found it is stored in map passed to [`ExpressionMapper`]'s
     /// constructor.
     ///
+    /// # Arguments
+    /// * `expr_root` - expression id from which matching will start
+    /// * `node_id` - id of relational node (`Having`, `Projection`, `OrderBy`),
+    /// where expression pointed by `expr_root` is located
+    ///
     /// # Errors
     /// - invalid references in any expression (`GroupBy`'s or node's one)
     /// - invalid query: node expression contains references that are not
@@ -331,6 +351,23 @@ impl<'plan> ExpressionMapper<'plan> {
     /// Helper function for `find_matches` which compares current node to `GroupBy` expressions
     /// and if no match is found recursively calls itself.
     fn find(&mut self, current: usize, parent: Option<usize>) -> Result<(), SbroadError> {
+        let Some(node_id) = self.node_id else {
+            return Err(SbroadError::Invalid(Entity::ExpressionMapper, None))
+        };
+        let is_ref = matches!(
+            self.plan.get_expression_node(current),
+            Ok(Expression::Reference { .. })
+        );
+        let is_sq_ref = is_ref
+            && self.plan.is_additional_child_of_rel(
+                node_id,
+                *self.plan.get_relational_from_reference_node(current)?,
+            )?;
+        // Because subqueries are replaced with References, we must not
+        // try to match these references against any GroupBy expressions
+        if is_sq_ref {
+            return Ok(());
+        }
         if let Some(gr_expr) = self
             .gr_exprs
             .iter()
@@ -341,9 +378,6 @@ impl<'plan> ExpressionMapper<'plan> {
             })
             .copied()
         {
-            let Some(node_id) = self.node_id else {
-                return Err(SbroadError::Invalid(Entity::ExpressionMapper, None))
-            };
             let location = ExpressionLocation::new(current, parent, node_id);
             if let Some(v) = self.map.get_mut(&gr_expr) {
                 v.push(location);
@@ -352,12 +386,12 @@ impl<'plan> ExpressionMapper<'plan> {
             }
             return Ok(());
         }
-        let node = self.plan.get_expression_node(current)?;
-        if let Expression::Reference { .. } = node {
+        if is_ref {
             // We found a column which is not inside aggregate function
             // and it is not a grouping expression:
             // select a from t group by b - is invalid
             let column_name = {
+                let node = self.plan.get_expression_node(current)?;
                 self.plan
                     .get_alias_from_reference_node(node)
                     .unwrap_or("'failed to get column name'")
@@ -652,12 +686,13 @@ impl Plan {
                         collector.collect_aggregates(*col, *node_id)?;
                     }
                 }
+                Relational::Having { filter, .. } => {
+                    collector.collect_aggregates(*filter, *node_id)?;
+                }
                 _ => {
                     return Err(SbroadError::Invalid(
                         Entity::Plan,
-                        Some(format!(
-                            "collect_aggregates: unexpected relational node ({node_id}): {node:?}"
-                        )),
+                        Some(format!("unexpected relational node ({node_id}): {node:?}")),
                     ))
                 }
             }
@@ -718,24 +753,22 @@ impl Plan {
                 .get_relational_children(rel_id)?
                 .ok_or_else(|| {
                     SbroadError::UnexpectedNumberOfValues(format!(
-                        "split_reduce_stage: expected relation node ({rel_id}) to have children!"
+                        "expected relation node ({rel_id}) to have children!"
                     ))
                 })?
                 .first()
                 .ok_or_else(|| {
                     SbroadError::UnexpectedNumberOfValues(format!(
-                        "split_reduce_stage: expected relation node ({rel_id}) to have children!"
+                        "expected relation node ({rel_id}) to have children!"
                     ))
                 })?;
             Ok(c)
         };
         let mut next: usize = final_proj_id;
-        // Currently in Reduce stage only Projection is possible.
-        // When any Having, OrderBy, Limit will be added this will change.
-        let max_reduce_nodes = 1;
+        let max_reduce_nodes = 2;
         for _ in 0..=max_reduce_nodes {
             match self.get_relation_node(next)? {
-                Relational::Projection { .. } => {
+                Relational::Projection { .. } | Relational::Having { .. } => {
                     finals.push(next);
                     next = get_first_child(next)?;
                 }
@@ -799,10 +832,16 @@ impl Plan {
 
             let mut mapper = ExpressionMapper::new(&grouping_expr, self);
             for node_id in finals {
-                if let Relational::Projection { output, .. } = self.get_relation_node(*node_id)? {
-                    for col in self.get_row_list(*output)? {
-                        mapper.find_matches(*col, *node_id)?;
+                match self.get_relation_node(*node_id)? {
+                    Relational::Projection { output, .. } => {
+                        for col in self.get_row_list(*output)? {
+                            mapper.find_matches(*col, *node_id)?;
+                        }
+                    }
+                    Relational::Having { filter, .. } => {
+                        mapper.find_matches(*filter, *node_id)?;
                     }
+                    _ => {}
                 }
             }
             gr_expr_map = mapper.get_matches();
@@ -812,26 +851,46 @@ impl Plan {
             // check that all column references are inside aggregate functions
             for id in finals {
                 let node = self.get_relation_node(*id)?;
-                if let Relational::Projection { output, .. } = node {
-                    for col in self.get_row_list(*output)? {
-                        let mut dfs = PostOrder::with_capacity(
+                match node {
+                    Relational::Projection { output, .. } => {
+                        for col in self.get_row_list(*output)? {
+                            let mut dfs = PostOrder::with_capacity(
+                                |x| self.nodes.aggregate_iter(x, false),
+                                EXPR_CAPACITY,
+                            );
+                            dfs.populate_nodes(*col);
+                            let nodes = dfs.take_nodes();
+                            for (_, id) in nodes {
+                                let n = self.get_expression_node(id)?;
+                                if let Expression::Reference { .. } = n {
+                                    let alias = match self.get_alias_from_reference_node(n) {
+                                        Ok(v) => v.to_string(),
+                                        Err(e) => e.to_string(),
+                                    };
+                                    return Err(SbroadError::Invalid(Entity::Query,
+                                                                    Some(format!("found column reference ({alias}) outside aggregate function"))));
+                                }
+                            }
+                        }
+                    }
+                    Relational::Having { filter, .. } => {
+                        let mut bfs = BreadthFirst::with_capacity(
                             |x| self.nodes.aggregate_iter(x, false),
                             EXPR_CAPACITY,
+                            EXPR_CAPACITY,
                         );
-                        dfs.populate_nodes(*col);
-                        let nodes = dfs.take_nodes();
+                        bfs.populate_nodes(*filter);
+                        let nodes = bfs.take_nodes();
                         for (_, id) in nodes {
-                            let n = self.get_expression_node(id)?;
-                            if let Expression::Reference { .. } = n {
-                                let alias = match self.get_alias_from_reference_node(n) {
-                                    Ok(v) => v.to_string(),
-                                    Err(e) => e.to_string(),
-                                };
-                                return Err(SbroadError::Invalid(Entity::Query,
-                                                                Some(format!("found column reference ({alias}) outside aggregate function"))));
+                            if let Expression::Reference { .. } = self.get_expression_node(id)? {
+                                return Err(SbroadError::Invalid(
+                                    Entity::Query,
+                                    Some("HAVING argument must appear in the GROUP BY clause or be used in an aggregate function".into())
+                                ));
                             }
                         }
                     }
+                    _ => {}
                 }
             }
         }
@@ -1208,6 +1267,18 @@ impl Plan {
         Ok(())
     }
 
+    /// Add final `GroupBy` node in case `grouping_exprs` are not empty
+    ///
+    /// # Arguments
+    /// * `child_id` - id if relational node that will the child of `GroupBy`
+    /// * `grouping_exprs` - list of grouping expressions ids (which does not include
+    /// grouping expressions from distinct arguments)
+    /// * `local_aliases_map` - map between expression from `GroupBy` to alias used
+    /// at local stage
+    ///
+    /// # Returns
+    /// - if `GroupBy` node was created, return its id
+    /// - if `GroupBy` node was not created, return `child_id`
     fn add_final_groupby(
         &mut self,
         child_id: usize,
@@ -1215,7 +1286,7 @@ impl Plan {
         local_aliases_map: &LocalAliasesMap,
     ) -> Result<usize, SbroadError> {
         if grouping_exprs.is_empty() {
-            // no GroupBy in the original query nothing to do
+            // no GroupBy in the original query, nothing to do
             return Ok(child_id);
         }
         let mut gr_cols: Vec<usize> = Vec::with_capacity(grouping_exprs.len());
@@ -1228,12 +1299,12 @@ impl Plan {
         for expr_id in grouping_exprs {
             let Some(local_alias) = local_aliases_map.get(expr_id) else {
                 return Err(SbroadError::Invalid(Entity::Plan,
-                Some(format!("add_final_groupby: could not find local alias for GroupBy expr ({expr_id})"))))
+                Some(format!("could not find local alias for GroupBy expr ({expr_id})"))))
             };
             let Some(position) = child_map.get(&*(*local_alias)) else {
                 return Err(SbroadError::Invalid(
                     Entity::Node,
-                    Some(format!("add_final_groupby: did not find alias: {local_alias} in child ({child_id}) output!")))
+                    Some(format!("did not find alias: {local_alias} in child ({child_id}) output!")))
                 )
             };
             let new_col = Expression::Reference {
@@ -1282,9 +1353,9 @@ impl Plan {
             HashMap<RelationalID, Vec<(GroupByExpressionID, ExpressionID, ExpressionParent)>>;
         let map: ParentExpressionMap = {
             let mut new_map: ParentExpressionMap = HashMap::with_capacity(map.len());
-            for (k, v) in map {
-                for location in v {
-                    let rec = (k, location.expr_id, location.parent_expr_id);
+            for (groupby_expr_id, locations) in map {
+                for location in locations {
+                    let rec = (groupby_expr_id, location.expr_id, location.parent_expr_id);
                     if let Some(u) = new_map.get_mut(&location.rel_id) {
                         u.push(rec);
                     } else {
@@ -1300,13 +1371,13 @@ impl Plan {
                 .get_relational_children(rel_id)?
                 .ok_or_else(|| {
                     SbroadError::UnexpectedNumberOfValues(format!(
-                        "patch_grouping_exprs: expected relation node ({rel_id}) to have children!"
+                        "expected relation node ({rel_id}) to have children!"
                     ))
                 })?
                 .first()
                 .ok_or_else(|| {
                     SbroadError::UnexpectedNumberOfValues(format!(
-                        "patch_grouping_exprs: expected relation node ({rel_id}) to have children!"
+                        "expected relation node ({rel_id}) to have children!"
                     ))
                 })?;
             let alias_to_pos_map = self
@@ -1319,11 +1390,11 @@ impl Plan {
                 let Some(local_alias) = local_aliases_map.get(&gr_expr_id) else {
                     return Err(SbroadError::Invalid(
                         Entity::Plan,
-                        Some(format!("patch_finals: failed to find local alias for groupby expression {gr_expr_id}"))))
+                        Some(format!("failed to find local alias for groupby expression {gr_expr_id}"))))
                 };
                 let Some(pos) = alias_to_pos_map.get(&*(*local_alias)).copied() else {
                     return Err(SbroadError::Invalid(Entity::Plan,
-                                                    Some(format!("patch_finals: failed to find alias '{local_alias}' in ({child_id}). Aliases: {}", alias_to_pos_map.keys().join(" ")))))
+                                                    Some(format!("failed to find alias '{local_alias}' in ({child_id}). Aliases: {}", alias_to_pos_map.keys().join(" ")))))
                 };
                 let new_ref = Expression::Reference {
                     parent: Some(rel_id),
@@ -1338,13 +1409,16 @@ impl Plan {
                         Relational::Projection { .. } => {
                             return Err(SbroadError::Invalid(
                                 Entity::Plan,
-                                Some(format!("patch_finals: invalid mapping between groupby expression {gr_expr_id} and projection one: expression {expr_id} has no parent"))
+                                Some(format!("invalid mapping between groupby expression {gr_expr_id} and projection one: expression {expr_id} has no parent"))
                             ))
                         }
+                        Relational::Having { filter, .. } => {
+                            *filter = ref_id;
+                        }
                         _ => {
                             return Err(SbroadError::Invalid(
                                 Entity::Plan,
-                                Some(format!("patch_finals: unexpected node in Reduce stage: {rel_id}"))
+                                Some(format!("unexpected node in Reduce stage: {rel_id}"))
                             ))
                         }
                     }
@@ -1388,19 +1462,34 @@ impl Plan {
         // After we added a Map stage, we need to update output
         // of nodes in Reduce stage
         if let Some(last) = finals.last() {
-            self.get_mut_relation_node(*last)?
-                .set_children(vec![finals_child_id])?;
+            if let Some(children) = self.get_mut_relation_node(*last)?.mut_children() {
+                if let Some(first) = children.get_mut(0) {
+                    *first = finals_child_id;
+                }
+            }
         }
         for node_id in finals.iter().rev() {
             let node = self.get_relation_node(*node_id)?;
             match node {
+                // Projection node is the top node in finals: its aliases
+                // must not be changed (because those are user aliases), so
+                // nothing to do here
                 Relational::Projection { .. } => {}
+                Relational::Having { children, .. } => {
+                    let child_id = *children.first().ok_or_else(|| {
+                        SbroadError::Invalid(
+                            Entity::Node,
+                            Some(format!("Having ({node_id}) has no children!")),
+                        )
+                    })?;
+                    let output = self.add_row_for_output(child_id, &[], true)?;
+                    *self.get_mut_relation_node(*node_id)?.mut_output() = output;
+                    self.replace_parent_in_subtree(output, None, Some(*node_id))?;
+                }
                 _ => {
                     return Err(SbroadError::Invalid(
                         Entity::Plan,
-                        Some(format!(
-                            "patch_finals: Unexpected node in reduce stage: {node:?}"
-                        )),
+                        Some(format!("Unexpected node in reduce stage: {node:?}")),
                     ))
                 }
             }
@@ -1441,7 +1530,7 @@ impl Plan {
                 .map(|(k, v)| (k.to_string(), v))
                 .collect::<HashMap<String, usize>>();
             for info in infos {
-                let final_expr = info.aggr.create_column_for_final_projection(
+                let final_expr = info.aggr.create_final_aggregate_expr(
                     parent,
                     self,
                     &alias_to_pos_map,
@@ -1449,14 +1538,12 @@ impl Plan {
                 )?;
                 if let Some(parent_expr) = info.parent_expr {
                     self.replace_expression(parent_expr, info.aggr.fun_id, final_expr)?;
-                } else if let Relational::Projection { .. } = self.get_mut_relation_node(parent)? {
-                    // currently final stage may contain only Projection node,
-                    // when Having will be added, here will be the logic of replacing
-                    // its filter
+                } else {
+                    let node = self.get_mut_relation_node(parent)?;
                     return Err(SbroadError::Invalid(
                         Entity::Aggregate,
                         Some(format!(
-                            "aggregate info from Projection has no parent! Info: {info:?}"
+                            "aggregate info for {node:?} that hat no parent! Info: {info:?}"
                         )),
                     ));
                 }
@@ -1562,9 +1649,13 @@ impl Plan {
         )?;
         self.add_motion_to_2stage(&grouping_positions, finals_child_id, &finals)?;
 
+        let mut having_id: Option<usize> = None;
         // skip Projection
         for node_id in finals.iter().skip(1).rev() {
             self.set_distribution(self.get_relational_output(*node_id)?)?;
+            if let Relational::Having { .. } = self.get_relation_node(*node_id)? {
+                having_id = Some(*node_id);
+            }
         }
 
         if matches!(
@@ -1579,6 +1670,14 @@ impl Plan {
             )?;
         }
 
+        // resolve subquery conflicts in HAVING
+        if let Some(having_id) = having_id {
+            if let Relational::Having { filter, .. } = self.get_relation_node(having_id)? {
+                let strategy = self.resolve_sub_query_conflicts(having_id, *filter)?;
+                self.create_motion_nodes(&strategy)?;
+            }
+        }
+
         Ok(true)
     }
 }
diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs
index 882c5c5565f578c2f9913250aafac9a697fa907f..93259769e6022f0452cd91d7e5d45c7f2b70c5a3 100644
--- a/sbroad-core/src/ir/tree/relation.rs
+++ b/sbroad-core/src/ir/tree/relation.rs
@@ -63,6 +63,7 @@ fn relational_next<'nodes>(
             | Relational::Projection { children, .. }
             | Relational::ScanSubQuery { children, .. }
             | Relational::Selection { children, .. }
+            | Relational::Having { children, .. }
             | Relational::UnionAll { children, .. }
             | Relational::Values { children, .. }
             | Relational::ValuesRow { children, .. },
diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs
index a99ec9694f97f6bf9bd67abef8a9e25373810e7a..9054cf25e7ad491680457167a78c0b640614a6a8 100644
--- a/sbroad-core/src/ir/tree/subtree.rs
+++ b/sbroad-core/src/ir/tree/subtree.rs
@@ -251,7 +251,11 @@ fn subtree_next<'plan>(
                                     // Check if the sub-query is an additional one.
                                     let parent = iter.get_plan().get_relation_node(parent_id);
                                     let mut is_additional = false;
-                                    if let Ok(Relational::Selection { children, .. }) = parent {
+                                    if let Ok(
+                                        Relational::Selection { children, .. }
+                                        | Relational::Having { children, .. },
+                                    ) = parent
+                                    {
                                         if children.iter().skip(1).any(|&c| c == *rel_id) {
                                             is_additional = true;
                                         }
@@ -398,6 +402,11 @@ fn subtree_next<'plan>(
                     filter,
                     output,
                     ..
+                }
+                | Relational::Having {
+                    children,
+                    filter,
+                    output,
                 } => {
                     let step = *iter.get_child().borrow();
 
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_having.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_having.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b2404c57edeed5b5807a0e1ff258253a01ccab54
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_having.yaml
@@ -0,0 +1,80 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 10
+      rule: Select
+      value: ~
+    - children: #1
+        - 8
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "1"
+    - children: #4
+        - 7
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #7
+      rule: FunctionName
+      value: sum
+    - children: #8
+        - 9
+      rule: Scan
+      value: ~
+    - children: [] #9
+      rule: Table
+      value: t
+    - children: #10
+        - 1
+        - 11
+      rule: Projection
+      value: ~
+    - children: #11
+        - 17
+      rule: Column
+      value: ~
+    - children: #12
+        - 15
+        - 13
+      rule: Function
+      value: ~
+    - children: #13
+        - 14
+      rule: Reference
+      value: ~
+    - children: [] #14
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #15
+      rule: FunctionName
+      value: sum
+    - children: [] #16
+      rule: AliasName
+      value: COL_1
+    - children: #17
+        - 12
+        - 16
+      rule: Alias
+      value: ~
+top: 10
+map:
+  5:
+    - 8
+  13:
+    - 1
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_join.yaml
similarity index 100%
rename from sbroad-core/tests/artifactory/frontend/sql/transform_select_4.yaml
rename to sbroad-core/tests/artifactory/frontend/sql/transform_select_3_join.yaml
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_1.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..47470283a3d5e5dde9501087d46bea5dbb232764
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_1.yaml
@@ -0,0 +1,102 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 15
+      rule: Select
+      value: ~
+    - children: #1
+        - 8
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "1"
+    - children: #4
+        - 7
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #7
+      rule: FunctionName
+      value: sum
+    - children: #8
+        - 13
+        - 9
+      rule: Selection
+      value: ~
+    - children: #9
+        - 11
+        - 10
+      rule: Gt
+      value: ~
+    - children: [] #10
+      rule: Unsigned
+      value: "1"
+    - children: #11
+        - 12
+      rule: Reference
+      value: ~
+    - children: [] #12
+      rule: ColumnName
+      value: "\"a\""
+    - children: #13
+        - 14
+      rule: Scan
+      value: ~
+    - children: [] #14
+      rule: Table
+      value: t1
+    - children: #15
+        - 1
+        - 16
+      rule: Projection
+      value: ~
+    - children: #16
+        - 22
+      rule: Column
+      value: ~
+    - children: #17
+        - 20
+        - 18
+      rule: Function
+      value: ~
+    - children: #18
+        - 19
+      rule: Reference
+      value: ~
+    - children: [] #19
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #20
+      rule: FunctionName
+      value: sum
+    - children: [] #21
+      rule: AliasName
+      value: COL_1
+    - children: #22
+        - 17
+        - 21
+      rule: Alias
+      value: ~
+top: 15
+map:
+  18:
+    - 1
+  5:
+    - 8
+  11:
+    - 13
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_2.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7ab3d344a65abf8432b835f7968c0e9c2f92f24e
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_having_2.yaml
@@ -0,0 +1,94 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 13
+      rule: Select
+      value: ~
+    - children: #1
+        - 8
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "1"
+    - children: #4
+        - 7
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #7
+      rule: FunctionName
+      value: sum
+    - children: #8
+        - 11
+        - 9
+      rule: GroupBy
+      value: ~
+    - children: #9
+        - 10
+      rule: Reference
+      value: ~
+    - children: [] #10
+      rule: ColumnName
+      value: "\"a\""
+    - children: #11
+        - 12
+      rule: Scan
+      value: ~
+    - children: [] #12
+      rule: Table
+      value: t1
+    - children: #13
+        - 1
+        - 14
+      rule: Projection
+      value: ~
+    - children: #14
+        - 20
+      rule: Column
+      value: ~
+    - children: #15
+        - 18
+        - 16
+      rule: Function
+      value: ~
+    - children: #16
+        - 17
+      rule: Reference
+      value: ~
+    - children: [] #17
+      rule: ColumnName
+      value: "\"a\""
+    - children: [] #18
+      rule: FunctionName
+      value: sum
+    - children: [] #19
+      rule: AliasName
+      value: COL_1
+    - children: #20
+        - 15
+        - 19
+      rule: Alias
+      value: ~
+top: 13
+map:
+  9:
+    - 11
+  16:
+    - 1
+  5:
+    - 8
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_filter.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_filter.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9d151c877058883b2f2c584590800102f76ae3a9
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_filter.yaml
@@ -0,0 +1,106 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 21
+      rule: Select
+      value: ~
+    - children: #1
+        - 7
+        - 2
+      rule: Selection
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "0"
+    - children: #4
+        - 6
+        - 5
+      rule: Reference
+      value: ~
+    - children: [] #5
+      rule: ColumnName
+      value: a
+    - children: [] #6
+      rule: ScanName
+      value: t1
+    - children: #7
+        - 19
+        - 18
+        - 16
+        - 8
+      rule: Join
+      value: ~
+    - children: #8
+        - 9
+      rule: Condition
+      value: ~
+    - children: #9
+        - 13
+        - 10
+      rule: Eq
+      value: ~
+    - children: #10
+        - 12
+        - 11
+      rule: Reference
+      value: ~
+    - children: [] #11
+      rule: ColumnName
+      value: a
+    - children: [] #12
+      rule: ScanName
+      value: t2
+    - children: #13
+        - 15
+        - 14
+      rule: Reference
+      value: ~
+    - children: [] #14
+      rule: ColumnName
+      value: a
+    - children: [] #15
+      rule: ScanName
+      value: t1
+    - children: #16
+        - 17
+      rule: Scan
+      value: ~
+    - children: [] #17
+      rule: Table
+      value: t2
+    - children: [] #18
+      rule: InnerJoinKind
+      value: inner
+    - children: #19
+        - 20
+      rule: Scan
+      value: ~
+    - children: [] #20
+      rule: Table
+      value: t1
+    - children: #21
+        - 1
+        - 22
+      rule: Projection
+      value: ~
+    - children: [] #22
+      rule: Asterisk
+      value: "*"
+top: 21
+map:
+  13:
+    - 19
+    - 16
+  22:
+    - 1
+  10:
+    - 19
+    - 16
+  4:
+    - 7
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_1.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_groupby.yaml
similarity index 100%
rename from sbroad-core/tests/artifactory/frontend/sql/transform_select_5_1.yaml
rename to sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_groupby.yaml
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_having.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_having.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e970c6504f7d50446804a09dbb909bace5c2b0f5
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_join_having.yaml
@@ -0,0 +1,138 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 23
+      rule: Select
+      value: ~
+    - children: #1
+        - 9
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "1"
+    - children: #4
+        - 8
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 7
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: a
+    - children: [] #7
+      rule: ScanName
+      value: t1
+    - children: [] #8
+      rule: FunctionName
+      value: sum
+    - children: #9
+        - 21
+        - 20
+        - 18
+        - 10
+      rule: Join
+      value: ~
+    - children: #10
+        - 11
+      rule: Condition
+      value: ~
+    - children: #11
+        - 15
+        - 12
+      rule: Eq
+      value: ~
+    - children: #12
+        - 14
+        - 13
+      rule: Reference
+      value: ~
+    - children: [] #13
+      rule: ColumnName
+      value: a
+    - children: [] #14
+      rule: ScanName
+      value: t2
+    - children: #15
+        - 17
+        - 16
+      rule: Reference
+      value: ~
+    - children: [] #16
+      rule: ColumnName
+      value: a
+    - children: [] #17
+      rule: ScanName
+      value: t1
+    - children: #18
+        - 19
+      rule: Scan
+      value: ~
+    - children: [] #19
+      rule: Table
+      value: t2
+    - children: [] #20
+      rule: InnerJoinKind
+      value: inner
+    - children: #21
+        - 22
+      rule: Scan
+      value: ~
+    - children: [] #22
+      rule: Table
+      value: t1
+    - children: #23
+        - 1
+        - 24
+      rule: Projection
+      value: ~
+    - children: #24
+        - 30
+      rule: Column
+      value: ~
+    - children: #25
+        - 28
+        - 26
+      rule: Function
+      value: ~
+    - children: #26
+        - 27
+      rule: Reference
+      value: ~
+    - children: [] #27
+      rule: ColumnName
+      value: a
+    - children: [] #28
+      rule: FunctionName
+      value: sum
+    - children: [] #29
+      rule: AliasName
+      value: COL_1
+    - children: #30
+        - 25
+        - 29
+      rule: Alias
+      value: ~
+top: 23
+map:
+  12:
+    - 21
+    - 18
+  15:
+    - 21
+    - 18
+  26:
+    - 1
+  5:
+    - 9
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_selection_having.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_selection_having.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2b78a8fb28d58b792ac411d642642c27858c1bfa
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_selection_having.yaml
@@ -0,0 +1,120 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 19
+      rule: Select
+      value: ~
+    - children: #1
+        - 9
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "1"
+    - children: #4
+        - 8
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 7
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: a
+    - children: [] #7
+      rule: ScanName
+      value: t1
+    - children: [] #8
+      rule: FunctionName
+      value: sum
+    - children: #9
+        - 12
+        - 10
+      rule: GroupBy
+      value: ~
+    - children: #10
+        - 11
+      rule: Reference
+      value: ~
+    - children: [] #11
+      rule: ColumnName
+      value: a
+    - children: #12
+        - 17
+        - 13
+      rule: Selection
+      value: ~
+    - children: #13
+        - 15
+        - 14
+      rule: Gt
+      value: ~
+    - children: [] #14
+      rule: Unsigned
+      value: "1"
+    - children: #15
+        - 16
+      rule: Reference
+      value: ~
+    - children: [] #16
+      rule: ColumnName
+      value: "\"a\""
+    - children: #17
+        - 18
+      rule: Scan
+      value: ~
+    - children: [] #18
+      rule: Table
+      value: t1
+    - children: #19
+        - 1
+        - 20
+      rule: Projection
+      value: ~
+    - children: #20
+        - 26
+      rule: Column
+      value: ~
+    - children: #21
+        - 24
+        - 22
+      rule: Function
+      value: ~
+    - children: #22
+        - 23
+      rule: Reference
+      value: ~
+    - children: [] #23
+      rule: ColumnName
+      value: a
+    - children: [] #24
+      rule: FunctionName
+      value: sum
+    - children: [] #25
+      rule: AliasName
+      value: COL_1
+    - children: #26
+        - 21
+        - 25
+      rule: Alias
+      value: ~
+top: 19
+map:
+  5:
+    - 9
+  15:
+    - 17
+  10:
+    - 12
+  22:
+    - 1
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5.yaml
index 9d151c877058883b2f2c584590800102f76ae3a9..3045f5f4d97c6b708eb24c992134d7c13e12e5eb 100644
--- a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5.yaml
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5.yaml
@@ -2,105 +2,183 @@
 nodes:
   arena:
     - children: #0
-        - 21
+        - 28
       rule: Select
       value: ~
     - children: #1
-        - 7
+        - 8
+        - 5
         - 2
-      rule: Selection
+      rule: GroupBy
       value: ~
     - children: #2
         - 4
         - 3
-      rule: Gt
+      rule: Reference
       value: ~
     - children: [] #3
-      rule: Unsigned
-      value: "0"
-    - children: #4
+      rule: ColumnName
+      value: b
+    - children: [] #4
+      rule: ScanName
+      value: t2
+    - children: #5
+        - 7
         - 6
-        - 5
       rule: Reference
       value: ~
-    - children: [] #5
+    - children: [] #6
       rule: ColumnName
       value: a
-    - children: [] #6
+    - children: [] #7
       rule: ScanName
       value: t1
-    - children: #7
-        - 19
-        - 18
-        - 16
-        - 8
-      rule: Join
-      value: ~
     - children: #8
+        - 14
         - 9
-      rule: Condition
+      rule: Selection
       value: ~
     - children: #9
-        - 13
+        - 11
         - 10
-      rule: Eq
+      rule: Gt
       value: ~
-    - children: #10
+    - children: [] #10
+      rule: Unsigned
+      value: "1"
+    - children: #11
+        - 13
         - 12
-        - 11
       rule: Reference
       value: ~
-    - children: [] #11
+    - children: [] #12
       rule: ColumnName
       value: a
-    - children: [] #12
+    - children: [] #13
       rule: ScanName
-      value: t2
-    - children: #13
+      value: t1
+    - children: #14
+        - 26
+        - 25
+        - 23
         - 15
-        - 14
+      rule: Join
+      value: ~
+    - children: #15
+        - 16
+      rule: Condition
+      value: ~
+    - children: #16
+        - 20
+        - 17
+      rule: Eq
+      value: ~
+    - children: #17
+        - 19
+        - 18
+      rule: Reference
+      value: ~
+    - children: [] #18
+      rule: ColumnName
+      value: a
+    - children: [] #19
+      rule: ScanName
+      value: t2
+    - children: #20
+        - 22
+        - 21
       rule: Reference
       value: ~
-    - children: [] #14
+    - children: [] #21
       rule: ColumnName
       value: a
-    - children: [] #15
+    - children: [] #22
       rule: ScanName
       value: t1
-    - children: #16
-        - 17
+    - children: #23
+        - 24
       rule: Scan
       value: ~
-    - children: [] #17
+    - children: [] #24
       rule: Table
       value: t2
-    - children: [] #18
+    - children: [] #25
       rule: InnerJoinKind
       value: inner
-    - children: #19
-        - 20
+    - children: #26
+        - 27
       rule: Scan
       value: ~
-    - children: [] #20
+    - children: [] #27
       rule: Table
       value: t1
-    - children: #21
+    - children: #28
         - 1
-        - 22
+        - 33
+        - 29
       rule: Projection
       value: ~
-    - children: [] #22
-      rule: Asterisk
-      value: "*"
-top: 21
+    - children: #29
+        - 40
+      rule: Column
+      value: ~
+    - children: #30
+        - 32
+        - 31
+      rule: Reference
+      value: ~
+    - children: [] #31
+      rule: ColumnName
+      value: b
+    - children: [] #32
+      rule: ScanName
+      value: t2
+    - children: #33
+        - 38
+      rule: Column
+      value: ~
+    - children: #34
+        - 36
+        - 35
+      rule: Reference
+      value: ~
+    - children: [] #35
+      rule: ColumnName
+      value: a
+    - children: [] #36
+      rule: ScanName
+      value: t1
+    - children: [] #37
+      rule: AliasName
+      value: a
+    - children: #38
+        - 34
+        - 37
+      rule: Alias
+      value: ~
+    - children: [] #39
+      rule: AliasName
+      value: b
+    - children: #40
+        - 30
+        - 39
+      rule: Alias
+      value: ~
+top: 28
 map:
-  13:
-    - 19
-    - 16
-  22:
+  2:
+    - 8
+  5:
+    - 8
+  34:
+    - 1
+  30:
     - 1
-  10:
-    - 19
-    - 16
-  4:
-    - 7
+  20:
+    - 26
+    - 23
+  17:
+    - 26
+    - 23
+  11:
+    - 14
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_1.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f642eba5ae1902fcfc95a6bea1e2e8fad35b3c21
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_1.yaml
@@ -0,0 +1,230 @@
+---
+nodes:
+  arena:
+    - children: #0 
+        - 38
+      rule: Select
+      value: ~
+    - children: #1
+        - 17
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 10
+        - 3
+      rule: And
+      value: ~
+    - children: #3
+        - 5
+        - 4
+      rule: Gt
+      value: ~
+    - children: [] #4
+      rule: Unsigned
+      value: "10"
+    - children: #5
+        - 9
+        - 6
+      rule: Function
+      value: ~
+    - children: #6
+        - 8
+        - 7
+      rule: Reference
+      value: ~
+    - children: [] #7
+      rule: ColumnName
+      value: b
+    - children: [] #8
+      rule: ScanName
+      value: t2
+    - children: [] #9
+      rule: FunctionName
+      value: avg
+    - children: #10
+        - 12
+        - 11
+      rule: Gt
+      value: ~
+    - children: [] #11
+      rule: Unsigned
+      value: "1"
+    - children: #12
+        - 16
+        - 13
+      rule: Function
+      value: ~
+    - children: #13
+        - 15
+        - 14
+      rule: Reference
+      value: ~
+    - children: [] #14
+      rule: ColumnName
+      value: a
+    - children: [] #15
+      rule: ScanName
+      value: t1
+    - children: [] #16
+      rule: FunctionName
+      value: sum
+    - children: #17
+        - 24
+        - 21
+        - 18
+      rule: GroupBy
+      value: ~
+    - children: #18
+        - 20
+        - 19
+      rule: Reference
+      value: ~
+    - children: [] #19
+      rule: ColumnName
+      value: b
+    - children: [] #20
+      rule: ScanName
+      value: t2
+    - children: #21
+        - 23
+        - 22
+      rule: Reference
+      value: ~
+    - children: [] #22
+      rule: ColumnName
+      value: a
+    - children: [] #23
+      rule: ScanName
+      value: t1
+    - children: #24
+        - 36
+        - 35
+        - 33
+        - 25
+      rule: Join
+      value: ~
+    - children: #25
+        - 26
+      rule: Condition
+      value: ~
+    - children: #26
+        - 30
+        - 27
+      rule: Eq
+      value: ~
+    - children: #27
+        - 29
+        - 28
+      rule: Reference
+      value: ~
+    - children: [] #28
+      rule: ColumnName
+      value: a
+    - children: [] #29
+      rule: ScanName
+      value: t2
+    - children: #30
+        - 32
+        - 31
+      rule: Reference
+      value: ~
+    - children: [] #31
+      rule: ColumnName
+      value: a
+    - children: [] #32
+      rule: ScanName
+      value: t1
+    - children: #33
+        - 34
+      rule: Scan
+      value: ~
+    - children: [] #34
+      rule: Table
+      value: t2
+    - children: [] #35
+      rule: InnerJoinKind
+      value: inner
+    - children: #36
+        - 37
+      rule: Scan
+      value: ~
+    - children: [] #37
+      rule: Table
+      value: t1
+    - children: #38
+        - 1
+        - 39
+      rule: Projection
+      value: ~
+    - children: #39
+        - 51
+      rule: Column
+      value: ~
+    - children: #40
+        - 46
+        - 45
+        - 41
+      rule: Addition
+      value: ~
+    - children: #41
+        - 44
+        - 42
+      rule: Function
+      value: ~
+    - children: #42
+        - 43
+      rule: Reference
+      value: ~
+    - children: [] #43
+      rule: ColumnName
+      value: b
+    - children: [] #44
+      rule: FunctionName
+      value: f
+    - children: [] #45
+      rule: Add
+      value: +
+    - children: #46
+        - 49
+        - 47
+      rule: Function
+      value: ~
+    - children: #47
+        - 48
+      rule: Reference
+      value: ~
+    - children: [] #48
+      rule: ColumnName
+      value: a
+    - children: [] #49
+      rule: FunctionName
+      value: sum
+    - children: [] #50
+      rule: AliasName
+      value: COL_1
+    - children: #51
+        - 40
+        - 50
+      rule: Alias
+      value: ~
+top: 38
+map:
+  30:
+    - 36
+    - 33
+  6:
+    - 17
+  18:
+    - 24
+  21:
+    - 24
+  47:
+    - 1
+  27:
+    - 36
+    - 33
+  13:
+    - 17
+  42:
+    - 1
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_2.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a54df54fa14fcb80bf7adca6f50e05b8ffef3add
--- /dev/null
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_having_2.yaml
@@ -0,0 +1,190 @@
+---
+nodes:
+  arena:
+    - children: #0
+        - 35
+      rule: Select
+      value: ~
+    - children: #1
+        - 9
+        - 2
+      rule: Having
+      value: ~
+    - children: #2
+        - 4
+        - 3
+      rule: Gt
+      value: ~
+    - children: [] #3
+      rule: Unsigned
+      value: "10"
+    - children: #4
+        - 8
+        - 5
+      rule: Function
+      value: ~
+    - children: #5
+        - 7
+        - 6
+      rule: Reference
+      value: ~
+    - children: [] #6
+      rule: ColumnName
+      value: b
+    - children: [] #7
+      rule: ScanName
+      value: t2
+    - children: [] #8
+      rule: FunctionName
+      value: avg
+    - children: #9
+        - 21
+        - 10
+      rule: Selection
+      value: ~
+    - children: #10
+        - 12
+        - 11
+      rule: Gt
+      value: ~
+    - children: [] #11
+      rule: Unsigned
+      value: "1"
+    - children: #12
+        - 19
+        - 18
+        - 13
+      rule: Addition
+      value: ~
+    - children: #13
+        - 16
+        - 15
+        - 14
+      rule: Multiplication
+      value: ~
+    - children: [] #14
+      rule: Unsigned
+      value: "2"
+    - children: [] #15
+      rule: Divide
+      value: /
+    - children: #16
+        - 17
+      rule: Reference
+      value: ~
+    - children: [] #17
+      rule: ColumnName
+      value: b
+    - children: [] #18
+      rule: Add
+      value: +
+    - children: #19
+        - 20
+      rule: Reference
+      value: ~
+    - children: [] #20
+      rule: ColumnName
+      value: a
+    - children: #21
+        - 33
+        - 32
+        - 30
+        - 22
+      rule: Join
+      value: ~
+    - children: #22
+        - 23
+      rule: Condition
+      value: ~
+    - children: #23
+        - 27
+        - 24
+      rule: Eq
+      value: ~
+    - children: #24
+        - 26
+        - 25
+      rule: Reference
+      value: ~
+    - children: [] #25
+      rule: ColumnName
+      value: a
+    - children: [] #26
+      rule: ScanName
+      value: t2
+    - children: #27
+        - 29
+        - 28
+      rule: Reference
+      value: ~
+    - children: [] #28
+      rule: ColumnName
+      value: a
+    - children: [] #29
+      rule: ScanName
+      value: t1
+    - children: #30
+        - 31
+      rule: Scan
+      value: ~
+    - children: [] #31
+      rule: Table
+      value: t2
+    - children: [] #32
+      rule: InnerJoinKind
+      value: inner
+    - children: #33
+        - 34
+      rule: Scan
+      value: ~
+    - children: [] #34
+      rule: Table
+      value: t1
+    - children: #35
+        - 1
+        - 36
+      rule: Projection
+      value: ~
+    - children: #36
+        - 42
+      rule: Column
+      value: ~
+    - children: #37
+        - 40
+        - 38
+      rule: Function
+      value: ~
+    - children: #38
+        - 39
+      rule: Reference
+      value: ~
+    - children: [] #39
+      rule: ColumnName
+      value: a
+    - children: [] #40
+      rule: FunctionName
+      value: sum
+    - children: [] #41
+      rule: AliasName
+      value: COL_1
+    - children: #42
+        - 37
+        - 41
+      rule: Alias
+      value: ~
+top: 35
+map:
+  27:
+    - 33
+    - 30
+  16:
+    - 21
+  38:
+    - 1
+  5:
+    - 9
+  24:
+    - 33
+    - 30
+  19:
+    - 21
\ No newline at end of file
diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml
index 3045f5f4d97c6b708eb24c992134d7c13e12e5eb..b55cda54cfd65c0472d5debe6f19ffd7711376ee 100644
--- a/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml
+++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml
@@ -1,184 +1,204 @@
 ---
 nodes:
   arena:
-    - children: #0
-        - 28
+    - children:
+        - 38
       rule: Select
       value: ~
-    - children: #1
-        - 8
-        - 5
+    - children:
+        - 9
         - 2
-      rule: GroupBy
+      rule: Having
       value: ~
-    - children: #2
+    - children:
         - 4
         - 3
+      rule: Gt
+      value: ~
+    - children: []
+      rule: Unsigned
+      value: "10"
+    - children:
+        - 8
+        - 5
+      rule: Function
+      value: ~
+    - children:
+        - 7
+        - 6
       rule: Reference
       value: ~
-    - children: [] #3
+    - children: []
       rule: ColumnName
       value: b
-    - children: [] #4
+    - children: []
       rule: ScanName
       value: t2
-    - children: #5
-        - 7
-        - 6
+    - children: []
+      rule: FunctionName
+      value: avg
+    - children:
+        - 12
+        - 10
+      rule: GroupBy
+      value: ~
+    - children:
+        - 11
       rule: Reference
       value: ~
-    - children: [] #6
+    - children: []
       rule: ColumnName
       value: a
-    - children: [] #7
-      rule: ScanName
-      value: t1
-    - children: #8
-        - 14
-        - 9
+    - children:
+        - 24
+        - 13
       rule: Selection
       value: ~
-    - children: #9
-        - 11
-        - 10
+    - children:
+        - 15
+        - 14
       rule: Gt
       value: ~
-    - children: [] #10
+    - children: []
       rule: Unsigned
       value: "1"
-    - children: #11
-        - 13
-        - 12
+    - children:
+        - 22
+        - 21
+        - 16
+      rule: Addition
+      value: ~
+    - children:
+        - 19
+        - 18
+        - 17
+      rule: Multiplication
+      value: ~
+    - children: []
+      rule: Unsigned
+      value: "2"
+    - children: []
+      rule: Divide
+      value: /
+    - children:
+        - 20
+      rule: Reference
+      value: ~
+    - children: []
+      rule: ColumnName
+      value: b
+    - children: []
+      rule: Add
+      value: +
+    - children:
+        - 23
       rule: Reference
       value: ~
-    - children: [] #12
+    - children: []
       rule: ColumnName
       value: a
-    - children: [] #13
-      rule: ScanName
-      value: t1
-    - children: #14
-        - 26
+    - children:
+        - 36
+        - 35
+        - 33
         - 25
-        - 23
-        - 15
       rule: Join
       value: ~
-    - children: #15
-        - 16
+    - children:
+        - 26
       rule: Condition
       value: ~
-    - children: #16
-        - 20
-        - 17
+    - children:
+        - 30
+        - 27
       rule: Eq
       value: ~
-    - children: #17
-        - 19
-        - 18
+    - children:
+        - 29
+        - 28
       rule: Reference
       value: ~
-    - children: [] #18
+    - children: []
       rule: ColumnName
       value: a
-    - children: [] #19
+    - children: []
       rule: ScanName
       value: t2
-    - children: #20
-        - 22
-        - 21
+    - children:
+        - 32
+        - 31
       rule: Reference
       value: ~
-    - children: [] #21
+    - children: []
       rule: ColumnName
       value: a
-    - children: [] #22
+    - children: []
       rule: ScanName
       value: t1
-    - children: #23
-        - 24
+    - children:
+        - 34
       rule: Scan
       value: ~
-    - children: [] #24
+    - children: []
       rule: Table
       value: t2
-    - children: [] #25
+    - children: []
       rule: InnerJoinKind
       value: inner
-    - children: #26
-        - 27
+    - children:
+        - 37
       rule: Scan
       value: ~
-    - children: [] #27
+    - children: []
       rule: Table
       value: t1
-    - children: #28
+    - children:
         - 1
-        - 33
-        - 29
+        - 39
       rule: Projection
       value: ~
-    - children: #29
-        - 40
+    - children:
+        - 45
       rule: Column
       value: ~
-    - children: #30
-        - 32
-        - 31
-      rule: Reference
+    - children:
+        - 43
+        - 41
+      rule: Function
       value: ~
-    - children: [] #31
-      rule: ColumnName
-      value: b
-    - children: [] #32
-      rule: ScanName
-      value: t2
-    - children: #33
-        - 38
-      rule: Column
-      value: ~
-    - children: #34
-        - 36
-        - 35
+    - children:
+        - 42
       rule: Reference
       value: ~
-    - children: [] #35
+    - children: []
       rule: ColumnName
       value: a
-    - children: [] #36
-      rule: ScanName
-      value: t1
-    - children: [] #37
-      rule: AliasName
-      value: a
-    - children: #38
-        - 34
-        - 37
-      rule: Alias
-      value: ~
-    - children: [] #39
+    - children: []
+      rule: FunctionName
+      value: sum
+    - children: []
       rule: AliasName
-      value: b
-    - children: #40
-        - 30
-        - 39
+      value: COL_1
+    - children:
+        - 40
+        - 44
       rule: Alias
       value: ~
-top: 28
+top: 38
 map:
-  2:
-    - 8
+  19:
+    - 24
   5:
-    - 8
-  34:
-    - 1
+    - 9
+  10:
+    - 12
   30:
-    - 1
-  20:
-    - 26
-    - 23
-  17:
-    - 26
-    - 23
-  11:
-    - 14
\ No newline at end of file
+    - 36
+    - 33
+  27:
+    - 36
+    - 33
+  22:
+    - 24
+  41:
+    - 1
\ No newline at end of file