On Thu, Dec 10, 2020 at 8:03 PM Chris Angelico <ros...@gmail.com> wrote:

> > Here is an example of how I use it to build an arbitrary long SQL
> request without having to pay for long intermediate strings, both in
> computation on memory.
>
<snip>

> > I often had such cases, but ended up using the more costy str.join .
>
> Is it really more costly? With strings the size of SQL queries
> (keeping in mind that these strings (correctly) contain no actual
> data, just placeholders), I doubt you'll see any significant
> performance hit from this.


I had the same thought -- expecting that it would take some pretty darn big
intermediate strings to be any faster at all.  I was not quite right. If
you replace the "join" iterator with plain str.join(), it does run a touch
slower:
def join_iterable(table, columns, values):
    request = ''.join(chain(
        ('INSERT INTO ', table, '('),
        join(', ', columns),
        (') VALUES (',),
        chain.from_iterable(join(('), (',), (join(', ', ('%s' for v in
value)) for value in values))),
        (') ON DUPLICATE KEY UPDATE ',),
        chain.from_iterable(join((', '), ((c, '=VALUES(', c, ')') for c in
columns))),
    ))

    return request


def join_str(table, columns, values):
    request = ''.join(chain(
        ('INSERT INTO ', table, '('),
        ', '.join(columns),
        (') VALUES (',),
        chain.from_iterable('), ('.join(', '.join('%s' for v in value) for
value in values)),
        (') ON DUPLICATE KEY UPDATE ',),
        chain.from_iterable(', '.join(f"{c}=VALUES({c})" for c in columns)),
    ))
    return request

In [31]: %timeit join_iterable(table, columns, values)
8.65 µs ± 154 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [32]: %timeit join_str(table, columns, values)
13 µs ± 38.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

But if you get rid of the whole pile of nested iterators and chain, and
make it a simple str.join, with each bit using plain str.join(), it's
faster still.

def join_str_simple(table, columns, values):
    request = "".join(["INSERT INTO ",
                       f"{table}({', '.join(columns)})",
                       " VALUES (",
                       "), (".join(', '.join('%s' for v in value) for value
in values),
                       ") ON DUPLICATE KEY UPDATE ",
                       ", ".join(f"{c}=VALUES({c})" for c in columns),
                       ])
    return request

In [33]: %timeit join_str_simple(table, columns, values)
5.09 µs ± 26.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

And I would argue more readable. (though the double comprehensions are not
great ...)

Granted, you may lose that advantage if you had a lot more values -- but I
expect it wouldn't be a real issue until you had hundreds or more.


> Also, I would be VERY surprised if the cost
> of in-memory string manipulation exceeds the cost of an actual
> database transaction.
>

well, yeah -- these are all very fast.


> But more importantly: Is it any more readable? What you have there is
> pretty opaque. Is the str.join version worse than that?


you be the judge :-)

Code enclosed.

-CHB


-- 
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython
from itertools import chain #, join

def join(sep, iterable):
    notfirst=False
    for i in iterable:
        if notfirst:
            yield sep
        else:
            notfirst=True
        yield i

def join_iterable(table, columns, values):
    request = ''.join(chain(
        ('INSERT INTO ', table, '('),
        join(', ', columns),
        (') VALUES (',),
        chain.from_iterable(join(('), (',), (join(', ', ('%s' for v in value)) for value in values))),
        (') ON DUPLICATE KEY UPDATE ',),
        chain.from_iterable(join((', '), ((c, '=VALUES(', c, ')') for c in columns))),
    ))

    return request


def join_str(table, columns, values):
    request = ''.join(chain(
        ('INSERT INTO ', table, '('),
        ', '.join(columns),
        (') VALUES (',),
        chain.from_iterable('), ('.join(', '.join('%s' for v in value) for value in values)),
        (') ON DUPLICATE KEY UPDATE ',),
        chain.from_iterable(', '.join(f"{c}=VALUES({c})" for c in columns)),
    ))
    return request

def join_str_simple(table, columns, values):
# table = 'mytable'
# columns=('id', 'v1', 'v2')
# values = [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

    request = "".join([f"INSERT INTO ",
                       f"{table}({', '.join(columns)})",
                        " VALUES (",
                        "), (".join(', '.join('%s' for v in value) for value in values),
                        ") ON DUPLICATE KEY UPDATE ",
                        ", ".join(f"{c}=VALUES({c})" for c in columns),
                        ])
    return request

# args = list(chain.from_iterable(values))


table = 'mytable'
columns=('id', 'v1', 'v2')
values = [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

request = join_iterable(table, columns, values)
print(request)
print()
request = join_str(table, columns, values)
print(request)

print()
request = join_str_simple(table, columns, values)
print(request)


assert join_iterable(table, columns, values) == join_str(table, columns, values)
assert join_iterable(table, columns, values) == join_str_simple(table, columns, values)

#    > INSERT INTO mytable(id, v1, v2) VALUES (%s, %s, %s), (%s, %s, %s), (%s, %s, %s) ON DUPLICATE KEY UPDATE id=VALUES(id), v1=VALUES(v1), v2=VALUES(v2)
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/OX6DEXWDZ7QI32JJOVKSEA5BQMBJZHHB/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to