EXPLAIN EXTENDED

How to create fast database queries

Happy New Year: drawing fractals in SQL

with 5 comments

In one of my previous New Year's posts we drew snowflakes in PostgreSQL.

The algorithm we used to create the snowflakes is an implementation of an L-system, which is an example of a fractal. There are many more beautiful objects we can see in the winter: frozen trees, frost patterns on windows, cracks on ice etc., all of them being fractals.

Frost patterns

Today we will be constructing escape-time fractals. To build such a fractal, one would need to run a certain function for each point on the plane iteratively and see how many iterations does it take for the function to overflow: the more it takes, the brighter is the point's color.

I won't go deep into fractal theory now, just show that they can be constructed with SQL relatively easily. For instance, Mandelbrot set, one of the best known escape-time fractals, is almost a one-liner in PostgreSQL:

WITH    RECURSIVE
        q (r, i, rx, ix, g) AS
        (
        SELECT  r::DOUBLE PRECISION * 0.02, i::DOUBLE PRECISION * 0.02, .0::DOUBLE PRECISION, .0::DOUBLE PRECISION, 0
        FROM    generate_series(-60, 20) r, generate_series(-50, 50) i
        UNION ALL
        SELECT  r, i, CASE WHEN ABS(rx * rx + ix * ix) <= 2 THEN rx * rx - ix * ix END + r, CASE WHEN ABS(rx * rx + ix * ix) <= 2 THEN 2 * rx * ix END + i, g + 1
        FROM    q
        WHERE   rx IS NOT NULL
                AND g < 99
        )
SELECT  ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '')
FROM    (
        SELECT  i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
        FROM    q
        GROUP BY
                i, r
        ) q
GROUP BY
        i
ORDER BY
        i
array_to_string
..... ..@
..:..:.
.....
..:..
..:..
..-:..
.....=@#+:
....:.=@@=.....
:.-..+@*@*::..:.
..:-@@@@@@@@:.:-.
..@@@@@@@@@+%..
..@@@@@@@@@@-..
:-*@@@@@@@@@:-:
..:@@@@@@@@@@@..
...*@@@@@@@@@@:..
. ......-@@@@@@@@@.... .
..... ..:.......=@@@@@@@-........: ..
.-.:-.......==..*.=.::-@@@@@:::.:.@..*-. =.
...=...=...::+%.@:@@@@@@@@@@@@@+*#=.=:+-. ..-
.:.:=::*....@@@@@@@@@@@@@@@@@@@@@@@@=@@.....::...:.
...*@@@@=.@:@@@@@@@@@@@@@@@@@@@@@@@@@@=.=....:...::.
.::@@@@@:-@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@..-:@=*:::.
.-@@@@@-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.=@@@@=..:
...@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@@@@@:..
....:-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::
.....@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-..
.....@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-:...
.--:+.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...
.==@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-..
..+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-#.
...=+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
-.=-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..:
.*%:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@-
. ..:... ..-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.............. ....-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@=
.--.-.....-=.:..........::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
..=:-....=@+..=.........@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
.:+@@::@==@-*:%:+.......:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
::@@@-@@@@@@@@@-:=.....:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:
.:@@@@@@@@@@@@@@@=:.....%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.:@@@@@@@@@@@@@@@@@-...:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:-
:@@@@@@@@@@@@@@@@@@@-..%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
%@@@@@@@@@@@@@@@@@@@-..-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@::+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+
@@@@@@@@@@@@@@@@@@@@@@:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@@-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-
@@@@@@@@@@@@@@@@@@@@@@:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@::+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+
%@@@@@@@@@@@@@@@@@@@-..-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
:@@@@@@@@@@@@@@@@@@@-..%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
.:@@@@@@@@@@@@@@@@@-...:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:-
.:@@@@@@@@@@@@@@@=:.....%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
::@@@-@@@@@@@@@-:=.....:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:
.:+@@::@==@-*:%:+.......:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
..=:-....=@+..=.........@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
.--.-.....-=.:..........::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
.............. ....-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@=
. ..:... ..-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.*%:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@-
-.=-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..:
...=+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
..+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-#.
.==@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-..
.--:+.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...
.....@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-:...
.....@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-..
....:-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::
...@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@@@@@:..
.-@@@@@-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.=@@@@=..:
.::@@@@@:-@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@..-:@=*:::.
...*@@@@=.@:@@@@@@@@@@@@@@@@@@@@@@@@@@=.=....:...::.
.:.:=::*....@@@@@@@@@@@@@@@@@@@@@@@@=@@.....::...:.
...=...=...::+%.@:@@@@@@@@@@@@@+*#=.=:+-. ..-
.-.:-.......==..*.=.::-@@@@@:::.:.@..*-. =.
..... ..:.......=@@@@@@@-........: ..
. ......-@@@@@@@@@.... .
...*@@@@@@@@@@:..
..:@@@@@@@@@@@..
:-*@@@@@@@@@:-:
..@@@@@@@@@@-..
..@@@@@@@@@+%..
..:-@@@@@@@@:.:-.
:.-..+@*@*::..:.
....:.=@@=.....
.....=@#+:
..-:..
..:..
..:..
.....
..:..:.
..... ..@
101 rows fetched in 0.0011s (1.5469s)

Its closest relative, the Burning Ship, is also simple:

WITH    RECURSIVE
        q (r, i, rx, ix, g) AS
        (
        SELECT  r::DOUBLE PRECISION * 0.04, i::DOUBLE PRECISION * 0.04, .0::DOUBLE PRECISION, .0::DOUBLE PRECISION, 0
        FROM    generate_series(-40, 20) r, generate_series(-40, 20) i
        UNION ALL
        SELECT  r, i, CASE WHEN ABS(rx * rx + ix * ix) <= 1E8 THEN rx * rx - ix * ix END + r, CASE WHEN ABS(rx * rx + ix * ix) <= 2 THEN ABS(2 * rx * ix) END + i, g + 1
        FROM    q
        WHERE   rx IS NOT NULL
                AND g < 99
        )
SELECT  ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '')
FROM    (
        SELECT  i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
        FROM    q
        GROUP BY
                i, r
        ) q
GROUP BY
        i
ORDER BY
        i
array_to_string
.
:.=
==*+:
#: @.@+@@@@:.
-@@ @@@@@@@.@@@@@.
.+@@@@@@@@@@@@@@@.
. - . -- :: + @@@@@@@@@@@@@@@@@..
@.. % .... - =+-=@:@@@@@@@@@@@@@@@@@@@@..
.. ...-=.+.:..%@.@+@@@@@@@@@@@@@@@@@@@@@@..
:.+.+@. -::-::@@@.:@@@@@@@@@@@@@@@@@@@@:..
..:..:@@=.-.=@@@@@@@@@@@@@@@@@@@@@@@@@@*..
.+:. .:=++#@@@=@@@@@@@@@@@@@@@@@@@@@@@@..
. :..:#+-@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
.. %- @@*@@:@@@@@@@@@@@@@@@@@@@@@@@@@@..
#*.@:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
: : *@#==+@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
.:- %-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
.-:+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
:-@@#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.
*-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+..
:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-..
:*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..
.....=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
...:@@@@@@:..::-@@@@@@@@@@@@@@@@@@@@
..............:#@@@@@@@@@@@@@@@@@.
......:=@@@@@@@@@@@@@@@@
....:+@@@@@@@@@@@@@@
....:@@@@@@@@@@@@@
....-@@@@@@@@@@@
...:@@@@@@@@@@
....=@@@@@@@@
...:@@@@@@
...:@@@@.
....
61 rows fetched in 0.0006s (0.4588s)

Now let's build something more wintery and New Year-y. For instance, this Julia set looks quite similar to cracks on ice:

WITH    RECURSIVE
        q (r, i, rx, ix, g) AS
        (
        SELECT  r::DOUBLE PRECISION * 0.000001, i::DOUBLE PRECISION * 0.000001,
                r::DOUBLE PRECISION * 0.000001, i::DOUBLE PRECISION * 0.000001,
                0
        FROM    generate_series(-40, 40) r, generate_series(-50, 50) i
        UNION ALL
        SELECT  r, i,
                CASE WHEN ABS(rx * rx + ix * ix) < 1E8 THEN rx * rx - ix * ix END + 0,
                CASE WHEN ABS(rx * rx + ix * ix) < 1E8 THEN 2 * rx * ix END + 1,
                g + 1
        FROM    q
        WHERE   rx IS NOT NULL
                AND g < 99
        )
SELECT  ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '')
FROM    (
        SELECT  i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
        FROM    q
        GROUP BY
                i, r
        ) q
GROUP BY
        i
ORDER BY
        i
array_to_string
------==-----------------------------------------------::::::::::::::::::::::::::
-------==-----------------------------------------------:::::::::::::::::::::::::
--------=+----------------------------------------------:::::::::::::::::::::::::
---------==---------------------------------------------:::::::::::::::::::::::::
--------==-----------------------------------------------::::::::::::::::::::::::
---------=-----------------------------------------------::::::::::::::::::::::::
--------==------------------------------------------------:::::::::::::::::::::::
-------=====----------------------------------------------:::::::::::::::::::::::
------===-=-----------------------------------------------:::::::::::::::::::::::
------==---------------------------------------------------::::::::::::::::::::::
------==---------------------------------------------------::::::::::::::::::::::
-----===----------------------------------------------------:::::::::::::::::::::
--=-====----------------------------------------------------:::::::::::::::::::::
==+====------------------------------------------------------::::::::::::::::::::
=-====-------------------------------------------------------::::::::::::::::::::
---===--------------------------------------------------------:::::::::::::::::::
----==---------------------------------------------------------::::::::::::::::::
----==---------------------------------------------------------::::::::::::::::::
----+==---------------------------------------------------------:::::::::::::::::
-----====--------------------------------------------------------::::::::::::::::
-------===-------------------------------------------------------::::::::::::::::
--------==--------------------------------------------------------:::::::::::::::
--------+----------------------------------------------------------::::::::::::::
--------==---------------------------------------------------------::::::::::::::
-------===----------------------------------------------------------:::::::::::::
------=====----------------------------------------------------------::::::::::::
------=---==----------------------------------------------------------:::::::::::
-----=-----=====-------------------------------------------------------::::::::::
-----------=-=====------------------------------------------------------:::::::::
-----------------=-------------------------------------------------------::::::::
------------------==--=--------------------------------------------------::::::::
-------------------====---------------------------------------------------:::::::
-------------------==------------------------------------------------------::::::
-------------------*--------------------------------------------------------:::::
------------------=*---------------------------------------------------------::::
------------------==----------------------------------------------------------:::
-------------------==----------------------------------------------------------::
--------------------==----------------------------------------------------------:
--------------------===----------------------------------------------------------
--------------------==-----------------------------------------------------------
--------------------==-----------------------------------------------------------
-------------------===-----------------------------------------------------------
------------------====-----------------------------------------------------------
-------------=---==-====---------------------------------------------------------
--------------====----==---------------------------------------------------------
--------------==-------======-=---------------------=----------------------------
--------------=--------=-==-===-------------=------=-----------------------------
--------------=--------------==--------------------------------------------------
--------------=--------------===---------=-==-----==-----------------=-----------
------------=+----------------==--------======-==-=------------------=-----------
---------===--=---------------======----@----======---------------=--===---------
-----------=------------------=-==-======--------==----------------+=------------
-----------=-----------------==-----==-=---------===--------------=--------------
--------------------------------------------------==--------------=--------------
-----------------------------=------=-------------===-==-=--------=--------------
----------------------------=---------------------=-======-------==--------------
---------------------------------------------------------==----====--------------
---------------------------------------------------------====-==---=-------------
-----------------------------------------------------------====------------------
-----------------------------------------------------------===-------------------
-----------------------------------------------------------==--------------------
-----------------------------------------------------------==--------------------
----------------------------------------------------------===--------------------
:----------------------------------------------------------==--------------------
::----------------------------------------------------------==-------------------
:::----------------------------------------------------------==------------------
::::---------------------------------------------------------*=------------------
:::::--------------------------------------------------------*-------------------
::::::------------------------------------------------------==-------------------
:::::::---------------------------------------------------====-------------------
::::::::--------------------------------------------------=--==------------------
::::::::-------------------------------------------------------=-----------------
:::::::::------------------------------------------------------=====-=-----------
::::::::::-------------------------------------------------------=====-----=-----
:::::::::::----------------------------------------------------------==---=------
::::::::::::----------------------------------------------------------=====------
:::::::::::::----------------------------------------------------------===-------
::::::::::::::---------------------------------------------------------==--------
::::::::::::::----------------------------------------------------------+--------
:::::::::::::::--------------------------------------------------------==--------
::::::::::::::::-------------------------------------------------------===-------
::::::::::::::::--------------------------------------------------------====-----
:::::::::::::::::---------------------------------------------------------==+----
::::::::::::::::::---------------------------------------------------------==----
::::::::::::::::::---------------------------------------------------------==----
:::::::::::::::::::--------------------------------------------------------===---
::::::::::::::::::::-------------------------------------------------------====-=
::::::::::::::::::::------------------------------------------------------====+==
:::::::::::::::::::::----------------------------------------------------====-=--
:::::::::::::::::::::----------------------------------------------------===-----
::::::::::::::::::::::---------------------------------------------------==------
::::::::::::::::::::::---------------------------------------------------==------
:::::::::::::::::::::::-----------------------------------------------=-===------
:::::::::::::::::::::::----------------------------------------------=====-------
:::::::::::::::::::::::------------------------------------------------==--------
::::::::::::::::::::::::-----------------------------------------------=---------
::::::::::::::::::::::::-----------------------------------------------==--------
:::::::::::::::::::::::::---------------------------------------------==---------
:::::::::::::::::::::::::----------------------------------------------+=--------
:::::::::::::::::::::::::-----------------------------------------------==-------
::::::::::::::::::::::::::-----------------------------------------------==------
101 rows fetched in 0.0011s (0.9688s)

And this one resembles frost patterns on windows:

WITH    RECURSIVE
        q (r, i, rx, ix, g) AS
        (
        SELECT  r::DOUBLE PRECISION * 0.0002, i::DOUBLE PRECISION * 0.0002,
                r::DOUBLE PRECISION * 0.0002, i::DOUBLE PRECISION * 0.0002,
                0
        FROM    generate_series(-200, -120) r, generate_series(0, 100) i
        UNION ALL
        SELECT  r, i,
                CASE WHEN ABS(rx * rx + ix * ix) < 1E8 THEN rx * rx - ix * ix END - 0.70176,
                CASE WHEN ABS(rx * rx + ix * ix) < 1E8 THEN 2 * rx * ix END + 0.3842,
                g + 1
        FROM    q
        WHERE   rx IS NOT NULL
                AND g < 99
        )
SELECT  ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '')
FROM    (
        SELECT  i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
        FROM    q
        GROUP BY
                i, r
        ) q
GROUP BY
        i
ORDER BY
        i
array_to_string
::::::::::::::::::::::::::::::::::::----==+++---+--+-::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::#%---=+=----=---:::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::-==---===--+#----:::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::-----=++=+=*-===----::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::==---=+===@===+*=---+=::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::--=#*===@*++====*------::::::::::::::::::::::::::
::::::::::::::::::::::::::::@--:----==%++++@=+==----+==+=::::::::::::::::::::::::
::::::::::::::::::::::::::::-#=*--+===++**%+++#===-*==#=--:::::::::::::::::::::::
::::::::::::::::::::::::::::=+=---%==#@*@%**#====+=--=++=::::::::::::::::::::::::
:::::::::::::::::::::::::::**-#==*====+*#@*+++==*#---*---::::::::::::::::::::::::
::::::::::::::::::::::::::::#------===@+*%*#++==+@*----::::::::::::::::::::::::::
:::::::::::::::::::::::::::::------=%#+++%+++*====----:::::::::::::::::::::::::::
:::::::::::::::::::::::::::::----+==*==*++*+====-------::::::::::::::::::::::::::
::::::::::::::::::::::*::::::--#@+=+====+=#=+==+-----*=::::::::::::::::::::::::::
:::::::::::::::::---:---=-------%+*===+++====%=%==-*---::::::::::::::::::::::::::
::::::::::::::::::+*--#%*-==-----===-=+*+===#%+=+=+*==--::::::::::::::::::::::-#:
::::::::::::::::::-+-=*#+=------*+=---++%*==++=---==@#==::::::::::::::::::::::---
::::::::::::::::::---++#++=----=-=+----=+==-------=+++*+-:::::::::::::::::::--+=-
::::::::::::::::::::-=**+====+=--------===--------====#-::::::::::::::::=---#**+@
:::::::::::::::::::::-=@==++=----------=#==---------@-=-::::::::::::::::-%=-==*++
::::::::::::::::::::-=-==-**=--------=*==*=------------:::::::::::::::::-=*=+=++=
:::::::::::::::::::::--#=--+=-------#==*=+=------------::::::::::::::::-=-+--=*=-
::::::::::::::::::::::--------------+=*%@===-=%-------:::::::::::::::::-+----++=-
:::::::::::::::::::::++*------------=+##*+====+=------:::::::::::::::::------==@-
::::::::::::::::::::-=+=+-----------=#*+=+==++--------:::::::::::::::::---------=
:::::::::::::::::::-====-----::::---+=-==--+#+*-------::::::::::::::::::---------
::::::::::::::::::::::%-*-+-::::::---=-----=@=---------:::::::::::::::::---------
:::::::::::::::::::::::-#=-::::::::--------+==---------::::::::::::::::----------
:::::::::::::::::::::::-:::::::::::::-------++**---------::::::::::::::----------
::::::::::::::::::::::::::::::::::::::------==--------------::::::::::-----------
:::::::::::::::::::::::::::::::::::::::-----------#------------::::::--------===+
:::::::::::::::::::::::::::::::::::::::--------*=*--------------------------**+++
:::::::::::::::::::::::::::::::::::::::---------=@----------*---------------=+%*+
:::::::::::::::::::::::::::::::::::::::----------===-------**----------------*+++
:::::::::::::::::::::::::::::::::::::::---------+@*=--+*-=+=-----------------%=*=
:::::::::::::::::::::::::::::::::::::::---------=@%+#-==*%*===*%--------------===
::::::::::::::::::::::::::::::::::::::----------==+===+=+**#===---------------==@
:::::::::::::::::::::::::::::::::::::---------=*==%==*+++@%*+==--------------+**+
:::::::::::::::::::::::::::::::::::------------=========+**#+#+-------------===+=
:::::::::::::::::::::::::::::::::---=----------==--=====#++++=#-------------++=--
:::::::::::::::::::::::::::::::::@--==+-------------=*@+==*+=+---------------==--
:::::::::::::::::::::::::::::::::=-++*--------------=**#====+=-------------------
:::::::::::::::::::::::::::::::::-=**+=@+-----------=+++====%+-------------+=----
::::::::::::::::::::::::::::::::-*+*#%=+=-----------===++=====-------------===---
:::::::::::::::::::::::::::::::---@=*===-------------==*#==-----------=---==++=--
:::::::::::::::::::::::::::::::-*===+==--------------==+===-----------#==**+=----
:::::::::::::::::::::::::::::::-==-=#*+=-------------======----------*=#**@+*----
::::::::::::::::::::::+-==-:::--+---==#==----+==----===#+==----------+===+*%=----
::::::::::::::::::::::-*--=-----------@=+====++=========+===----------==@+=#-----
::::::::::::::::::::::-=@==------------==*==+%#*+======#*====---------===+===+---
::::::::::::::::::::::-+@@=%+-----------=*+++**+======++*=*==+=-------====+==%=--
:::::::::::::::::::::--=++==-----------=*+==+++++======+#+++#===-----=====+*=*#--
::::::::::::::::::::---====------=-----=====%++=========++#@*+*==--====+===+#++==
::::::::::::::::::::++=-=*=#-----===*========+#=========++*#*+=========@+++**#+==
:::::-::::::::::::::+---=#=+----=#%*+=========+========+#@+*+#========***+++##*+=
:::::-*::-+:::::::::----+--======+*#+=========+#==+=====+#++*==========*+=+*++**=
:::::=+--==--:::::::---------=#*=+++==========+%+***====#++=+========+#++==++*==+
::::--=-+---==-::::-----------====#*+====++===*++@@*+==+++===========+=====+==**-
:::::---==+-=---:::-----------+===#=*++=+@%+=+++@@*#++++*+==========+======*+=+=-
::::::-==+*=+-*----------------=======**##**+++++%++++**#+======+*#+#======*=----
::::::=+@#+===+*---------------=======+++%*+++++++++++++%*+*====++@*========-----
:::::--=+*+++===--------=-=----========++++++#*++**++++**%*+====++++=====--------
:::::===%*+=+---------=+=*===-==========++++###*+**@*++**@%+====++++=====--------
:::::--==++==----------+**+#+====#+#====++++*##***#**++*+@+++=++*+*+=====--------
::::---%===*==#--------=*#*=======#**+++**+++***%*****%*++++++++**##+====--------
:+-==--*--==+===------+=+*+======*#*++++%@*+@**##*#%**#*++++++##++#%*====--------
:+#@===---=*+=*+=--++===+=*+#==+*+***+@@#@#*##*#%%##**##+++*#*+++++*++====-------
::-==*----=@*===*-=++#=*==@++#=+##+++++**+***#@%#%%##***+++**++++*#*==========+*=
::---------=--------+**===+=====+++=+++++++**##%@@@#%%****@**++================+=
::==+---------------=============@===+++++*@*@%%@@@@#**#***#%++=================*
::--=----------------@===============+++++@%*##@@@@%#@%@*+**#++===============+*#
:::-+-----------------==--===========+%***@**@##%@##@*@#*+++++============+====+#
:::::----------------------==========+###*****##@#@#****+++++++===========+*====*
:::::::--------------------====*=+==+++*#++*****%#*#@#**++++++++++*======#%*==#==
::::::::-------------------==%+@+%++++#++++%#%*#@#*#%******+++++++*@+++%*+++++@+=
::::::::-------------------=++*#@++++++*+++*@****%********#**+#*+*#**+==*+++#*#+=
:::::::::------------------===*@*+**%+++++++##***@**++%#@%+++#***++=====+==++**#+
:::::::::------------------===+*+*%++=++++++*+++%#**+++*%++++++**++========*#*@*+
::::::::-------#-==--+=---====++++++===++#+*#+++%%*#+++++++++%##@@========+%+#**+
::::::::-------@=+==@@*========+++=====+**%**+++#++#+++++++++++*@+*======+*++++==
::::::::----=--=*=+=++=======+**@======+*@%**+++++++@+++++====++*++==========+=#=
:::::::-----#==++#++%=======@@**++=====++*@*++++++++%+++++===================+==-
::::::------===+*#*+#===@====#=##+=======++**+++++++*%#++#===============--------
-::::-------#-*+%@#++===++=+==============+#**++++%***+++#%=============---------
--------------++#*#+++*+**=*==============+*++===++*@*%+%*=============----------
---------------%+*+@++@++=================+++=====**@#++**+============----------
-------------=++=++==+@#+=================+*+=====+++#+=+*@=============---------
--------------+==@+===#*=============+*==+*+========*====+++*========@+=---------
----------------==%=====*==----======@*+++#+===============+@+@++=====#==--------
----------------==+=======------===+%+++++*#+==============+*+*%#=====*+=--------
--------------=#==+=====---------==@+@**+**%+==============+++**#===++#==--------
------------*=*+=++===-----------=%+#%%%*++*================++@++==+*+**==-------
---------==-=+#*+%#*==-----------===+*@#*+++===============++++++#+++@@*===------
---------@%==+@@*=====------------==#%**+**+=============#++*%*%+#+=++#+=++==%==-
---------=+#=+**++====------------+*==**+++*+==*=========+*+**#@%+===+==+*===+=%=
=--------=====*===++===-----------**+==@===**+++===========++%%%*+++====+===+#+@*
*+---**---@+---*==++=+=-------------======@@#++@===========++###+=%=-----=--===*+
++===+=---%-------+*==----------------====**#+============@+++#%+#=-----------===
*@#=@+=-----------==-------------------===%**++=====----=====+*#==------------=#+
+%+==@*---------------------------------===+======--------==+===#+-------------==
==*=-=*---------------------------------==*++====----------==*=+#=--------------=
101 rows fetched in 0.0011s (1.2188s)

Finally, this beautiful Julia set (known as Glynn set) looks like a snow-covered tree:

WITH    RECURSIVE
        q (r, i, rx, ix, g) AS
        (
        SELECT  x + r::DOUBLE PRECISION * step, y + i::DOUBLE PRECISION * step,
                x + r::DOUBLE PRECISION * step, y + i::DOUBLE PRECISION * step,
                0
        FROM    (
                SELECT  0.25 x, -0.55 y, 0.002 step, r, i
                FROM    generate_series(-40, 40) r
                CROSS JOIN
                        generate_series(-40, 40) i
                ) q
        UNION ALL
        SELECT  r, i,
                CASE WHEN (rx * rx + ix * ix) < 1E8 THEN (rx * rx + ix * ix) ^ 0.75 * COS(1.5 * ATAN2(ix, rx)) END - 0.2,
                CASE WHEN (rx * rx + ix * ix) < 1E8 THEN (rx * rx + ix * ix) ^ 0.75 * SIN(1.5 * ATAN2(ix, rx)) END,
                g + 1
        FROM    q
        WHERE   rx IS NOT NULL
                AND g < 99
        )
SELECT  ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '')
FROM    (
        SELECT  i, r, SUBSTRING(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
        FROM    q
        GROUP BY
                i, r
        ) q
GROUP BY
        i
ORDER BY
        i
array_to_string
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::------------------------::::::::::::::::::::::::::::::::::
:::::::::::::::::-------------------------------------:::::::::::::::::::::::::::
:::::::::::::----------------------------------------------::::::::::::::::::::::
::::::::::-----------------------------------------------------::::::::::::::::::
::::::---------------=====+*@@@@@@@@@@@@*+=======-----------------:::::::::::::::
:::--------------===@@@@@%@%@@%%@@@%@%%@@@@@@@@@@@@+====-------------::::::::::::
-------------==+@@@@@@%@@@@@@%%%%@@@%%%%%@@%@@%%@@@@@@@@@+===----------::::::::::
---------===@@@@@@%@@%%@%%%@%@@@%@%%@@@@@%%@@@@%@@@@%@@%@@@@@+==----------:::::::
------==+@@%@@@@@@%%@%%%@%%@%@%@%@%%@%@%@%%@%@@@%@%%%%@%%@@@@%@@+=----------:::::
---==+@@@@%@@@@@%@@%%@%@%%%%@%@%%%%%%%%@%%%#%@%@%@%@%%@@@@@%%@%@@@@==---------:::
-==@@@@%%%%%@%@@%@@%@%#@%@@#@#@%%#@@#@#@%@#@@#@%@%%%%#@#@%@%@@%%@%@@@+=---------:
@@%%@@%@%@@%%@%@%@#%@#%%%@@#@%@@%#@@#@%@@@#@@%@%@%@#@%%%#@@@@%@%@%%%@@@@==-------
@@@%@%@%%%@%@@@%#@##@#@%#@@###@@##@@##%@%##@@%%@@%@%@@@%@@#@%@@@%@%@%@%@@@==-----
@@%@%%%@%@@@#%@#%%#@##@@#@@#%#@%##@%#%#@%##@%##@@##%@%@#@@#@##@%@@%%@%@@%@@@==---
%%%%@%%@@%#@#%@%@%#@%#@@%##%@#%#@#%#%@#%#@#@#@#%@##%@###@@##@#@##@%@%%%@@@%%@@==-
%@%%@@#%@#%##@#%@@#@@#@@@##@@@##@%##@@%##@##%@@#%#%#%#@#@%#@@##@%@%%@%@%%%@%%@@@=
%@%%%@##@%@%#@#%@@%#%#@@@##@@@##@@##@@@##@##@@@##@@##@@%%#@@@#%@%#@%@##@%@@%@%@%@
%@@%#%#@#%@@#%%#@@%####@@##@@@##@@#%@@@##@##@@@##@@##@@%##@@@#@%#@@#%%@@%%@%@@@@@
@#%@@##@%#@@@###@@@####%@#%@@@##@@#@@@@##@#%@@@##@@#%@@@#%@@%#@#@@@#%%@#%%@%%@@%@
@%#@@@#@@#@@@###%@@#%@####%@@@###%#@@@@##%#@@@@##@%#@@@@#%@%####@@@#@##@%@@%@@%%%
@@#%@@%###%@@####@%#@@@####@@@#####%@@@####@@@@#####@@@@#%%##%#%@@%#@#@@%%##@%@%@
%@%%@@@####@@##@####@@@@###@@@######@@@####%@@@#####@@@%####@%#%@@###@@@##@#%##@@
##%#@@@##%##@##@%###@@@@%##@@%##%###@@%####%@@######@@@####@@@#%@####@@@#@@#%@#@#
@%##%@@#%@%####@@###@@@@@##%@###@@##%@######@@###@##%@@###@@@@#%##%#%@@%#@#%@@##@
@@@##%@#%@@%###@@%##@@@@@%#####@@@###%##@@##@###@@###@%##@@@@@###%@#%@%##%#@@@#%@
@@@@####%@@@###%@@##%@@@@@#####@@@@#####@@#####@@@@##@##%@@@@%###@@#%%##%##@@@#@@
@@@@@###%@@@@###@@###@@@@@##*#@@@@@####%@@#####@@@@#####@@@@@###@@%####@%#%@@#%%#
%@@@@%##%@@@@%##@@###@@@@@####@@@@@##*#%@@##*#%@@@@#####@@@@@##%@@####@@@#@@%###@
#@@@@@###@@@@@########@@@@####@@@@@%#*#@@@####@@@@@%#*#%@@@@%##@@@###@@@@#%%##@@@
##@@@@###@@@@@######*#%@@@###%@@@@@@#*#@@@####@@@@@%#*#@@@@@###@@###@@@@%####@@@@
###%@@%##@@@@@%#####*##@@@###%@@@@@@#*#@@@###%@@@@@@#*#@@@@#####%##%@@@@%###@@@@%
#####@@##%@@@@%#*#######@@#*#@@@@@@@#*#@@@#*#@@@@@@@###@@@%########@@@@@###@@@@@#
##########@@@@@#*#%@#####%#*#%@@@@@@#*#%#@#*#@@@@@@@#*#@@%##*#####%@@@@@##%@@@@##
#@@%####*##@@@@#*#@@@%#####*#%@@@@@@#*#####*#@@@@@@%#*#@########*#@@@@@%##@@@@##%
#@@@@%##**#%@@@#*#@@@@%#**#*#%@@@@@%#*##*##*#@@@@@@##*###*##%@####@@@@@###@@%###%
#%@@@@@#####%@%###@@@@@##***##@@@@@##*******#@@@@@@##*#**##@@@####@@@@###########
##@@@@@@%####%####@@@@@@##***#@@@@@#***##***#%@@@@@#****##@@@@####@@@%#*#########
##@@@@@@@%########@@@@@@@##**#@@@@@#***###**##@@@@%#***##@@@@@####@@%##*###%@@%##
##%@@@@@@@%##*#**#@@@@@@@%#**#%@@@##**##@#***#@@@@##**##@@@@@@####@######%@@@@###
###@@@@@@@@%#****#@@@@@@@@##**#@@@#**##@@##**#@@@@#**##@@@@@@@######*##%@@@@@@###
**#%@@@@@@@@##***#@@@@@@@@%#**#%@##**#%@@@#**##@@##**#@@@@@@@@##*##*##@@@@@@@@##%
#*##@@@@@@@@@#***#%@@@@@@@@##**#%#**##@@@@##**#@%#**##@@@@@@@@#****##@@@@@@@@###@
##*##@@@@@@@@##**##@@@@@@@@%#**##***#@@@@@@#**###***#@@@@@@@@%#***##@@@@@@@@%####
@#####@@@@@@@@#**##@@@@@@@@@#******##@@@@@@##**##**#%@@@@@@@@##**##@@@@@@@@@#####
@@#####@@@@@@@##**#@@@@@@@@@#******#%@@@@@@%#******#@@@@@@@@@##**#%@@@@@@@@##*###
@@%#*###@@@@@@@#**##@@@@@@@@##*****#@@@@@@@@#*****##@@@@@@@@@#**##@@@@@@@@%#####@
@@@##**##%@@@@@#***#@@@@@@@@%#****##@@@@@@@@#*****#%@@@@@@@@##**#@@@@@@@@%####%@@
#####***###%@@@##**##@@@@@@@@#****#%@@@@@@@@#*****#@@@@@@@@@#**##@@@@@@@#####%@@@
####******#####%#***#@@@@@@@@#****#@@@@@@@@@##****#@@@@@@@@##**#@@@@@@%##*##%@@@@
####*********####***##@@@@@@@#****#@@@@@@@@@%#***##@@@@@@@##***#@@@@####**##@@@@@
##########***********##@@@@@@#****#@@@@@@@@@%#***#%@@@@@@%#***########***#####%%#
@@@@@%%######*********#%@@@@@#***##@@@@@@@@@@#***#@@@@@@@#****####*********######
@@@@@@@@@@@%###********#%@@@@#***#%@@@@@@@@@@#***#@@@@@@##**************#######**
@@@@@@@@@@@@@@###*******#%@@@#***#%@@@@@@@@@@#***#@@@@@##*********###############
@@@@@@@@@@@@@@@@###******##@@#***#%@@@@@@@@@@#***#@@@%#********####%@@@@@@@@@@@%#
@@@@@@@@@@@@@@@@@@###*****##%#***#%@@@@@@@@@%#***#%@##*******###%@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@##******#*****#@@@@@@@@@##***###*******##%@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@##***********#@@@@@@@@@#************##%@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@%#**********#@@@@@@@@@#***********##@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@#*********#@@@@@@@@@#*********##@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@#********#@@@@@@@@%#********##@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@#********#@@@@@@@#********##@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@%#*******#@@@@@@@#*******##@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@##******#@@@@@@##******##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@#*******#@@@@@#******##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#******#@@@@%#******#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%#*****#%@@@#******#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#******#@@##*****#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#******#@#******#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#******##******#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#************#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#************#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#***********#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#**********#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#**********#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
81 rows fetched in 0.0010s (2.5781s)

You can run the queries live on http://sqlfiddle.com (don't forget to select Plaintext Output in the drop-down menu next to Run SQL):

Happy New Year!

Previous New Year posts:

Written by Quassnoi

December 31st, 2013 at 11:00 pm

5 Responses to 'Happy New Year: drawing fractals in SQL'

Subscribe to comments with RSS

  1. Haha, beautiful! OK, you’re nuts :-) Happy New Year!

    Lukas Eder

    1 Jan 14 at 14:10

  2. @lukaseder: Turing complete SQL made me nuts! Happy New Year to you too!

    Quassnoi

    1 Jan 14 at 21:36

  3. Clean and pretty. Thanks and happy New Year!

    Vincent Gagne

    3 Jan 14 at 09:16

  4. Hi,
    what is the Oracle version of Mandelbrot?

    VLADIMIR BOGOVAC

    18 Feb 20 at 15:22

  5. @Vladimir: I didn’t make any!

    Quassnoi

    18 Feb 20 at 15:23

Leave a Reply