• Как сохранить введенные данные в программе?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    pickle - стандартная библиотека для маршалинга данных
    import pickle
    
    DATA_FILE_NAME = 'persistent_data.pickle'  # имя файла, где будут храниться персистентные данные
    try:  # открываем блок обработки ошибок:
        with open(DATA_FILE_NAME, 'rb') as f:  # открываем файл на бинарное чтение
            my_persistent_data = pickle.load(f)  # производим демаршалинг содержимого файла в переменную my_persistent_data
    except Exception:  # если при этом произошла какая-то ошибка (файл не открылся, данные не распарсились)
        my_persistent_data = {}  # то в эту переменную кладём пустой словарь
    
    print('Old data:', my_persistent_data)  # печатаем всё, сохранившееся с прошлого раза
    key = input('Введите имя параметра: ')  # запрашиваем у пользователя имя парамтра
    value = input(f'Значение параметра: {key} = ')  # запрашиваем у пользователя значение параметра
    my_persistent_data[key] = value  # присваиваем в словарь значение по указанному ключу
    
    with open(DATA_FILE_NAME, 'wb') as f:  # открываем файл на бинарную перезапись
        pickle.dump(my_persistent_data, f)  # сохраняем в файл данные из словаря (маршалинг)
    Ответ написан
  • Как реализовать прием callbackов, по сути прием и запись в БД параметров get запросов?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Ну так себе идея делать запись в БД по GET запросу.
    GET запрос не предназначен для изменения состояния бэкенда, а запись -- это и есть изменение состояния.
    Вам определённо нужны POST запросы.
    Обработать их можно каким-нибудь микрофреймворком типа Flask или Bottle.
    В качестве БД рекомендую MongoDB.
    По факту приёма запроса валидируете его и, если всё нормально, словарь параметров чистите от ненужных и добавляете в коллекцию.
    Ответ написан
  • Можете оценить код?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    К тому, что перечислил @fox_1 добавлю:
    1. используйте pathlib;
    2. выносите магические константы ("%Y-%m-%d-%H.%M.%S") в начало модуля, именуйте их внятно;
    3. используйте argparse;
    4. принимайте пути ('C:\\Users\\MinuteX\\Downloads\\This is a server of MC\\fantasy_city') в параметрах, можно использовать безопасные умолчания и относительные пути;
    5. не конкатенируйте строки (а особенно пути) плюсиком, используйте f-строки или .format(...);
    6. соберите код в функции, сделайте точку входа, анализируйте аргументы командной строки - даже в простых скриптах эта привычка даст вам много очков к опыту и привьёт правильный аккуратный подход к разработке инструментов;
    7. в простых скриптах print - это нормально, но лучше использовать logging, чтобы ваш код можно было использовать как модуль и переопределить логированаие;
    8. делайте выхлоп ошибок в stderr, а всякий полезный выхлоп в stdout, это позволит в командной строке на уровне пайпов направить логи работы вышего скрипта в разные файлы или потоки;
    9. не рассчитывайте, что ваш код будет работать вечно и стабильно. Сейчас у вас период бэкапа нестабильный, зависит от продолжительности самого бэкапа - это, как минимум, странно и неожиданно. Я бы такую штуку сделал простой и одношговой, то есть при запуске утилита делает один единственный бэкап и завершается с ошибкой, если что-то пошло не так. Это позволит занести её в crontab или в планировщик задач, а результаты работы и успешность запуска оценивать внешними механизмами;
    10. старайтесь всегда делать кросс-платформенный код, даже если он вряд ли будет запускаться на других ОС, даже если у вас нет времени протестировать его на разных ОС -- это сформирует у вас хорошую привычку и расширит кругозор, а также позволит не делать лишних специфических костылей.
    Ответ написан
  • Как передать logging из импортированных модулей в основную программу?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    Чтобы правильно использовать модуль logging, нужно во всех своих модулях содавать логгеры следующим образом:
    import logging
    log = logging.getLogger(__name__)

    В модулях, которые не запускаются сами по себе, никакого другого кода инициализации и настройки логирования не требуется.
    Если вы в своём модуле желаете различать два или более вида логов, то можно сделать как-то так:
    log = logging.getLogger(__name__)
    class MyModel:
        log = logging.getLogger(__name__ + '.MyModel')
        # ...

    В этом случае для этого класса будет свой логгер со своим именем.
    Также логгер можно создавать в метаклассе давать ему имя класса автиоматически, тогда каждый потомок будет иметь свой логгер со своим именем.
    Однако все эти логгеры ничего не знают о файлах и потоках, куда будут попадать их логи. Все фильтрации и перенаправления будут делаться в единой конфигурации логгирования в основной программе.

    В главном запускаемом файле кроме обычного создания логгера как во всех модулях будет ещё и инициализация и настройка системы логирования.
    В самом простом виде это что-то вроде такого:
    if __name__ == '__main__':
        logging.basicConfig(stream=sys.stderr, level='INFO', format='%(asctime)s %(levelname)-7s %(message)s')

    Тут говорится, что все логи уровня >= INFO будут в указанном формате направлены в stderr.

    В более сложном случае вы можете загрузить настройку логирования из конфигурационного файла или описать кодом:
    if __name__ == '__main__':
        # У вас может быть несколько разных способов форматировать код для разных мест:
        formatter_simple = Formatter(u'%(relativeCreated)08d %(levelname)-7s %(message)s')
        formatter_complex = Formatter(u'%(asctime)s %(levelname)-7s [%(filename)21s:%(lineno)-4d] %(message)s')
        # Несколько разных хендлеров для перехвата нужного вида сообщений и отправки в правильное место:
        handler_null      = logging.NullHandler()
        handler_screen    = handler(fmt=formatter_simple, stream=sys.stderr)
        handler_main_file = handler(
            fmt=formatter_complex,
            cls=logging.handlers.TimedRotatingFileHandler,
            when='midnight',
            backupCount=5,
            encoding='utf-8',
            filename=local_path('log/server.log'),
        )
        handler_errors_file = handler(
            fmt=formatter_complex,
            cls=logging.handlers.TimedRotatingFileHandler,
            when='midnight',
            backupCount=5,
            encoding='utf-8',
            filename=local_path('log/errors.log'),
            level='ERROR',
        )
        # А потом описываем сами логгеры:
        #   это корневой логер, пропускает все сообщения через себя насквозь и в то же отдаёт их своим хендлерам
        log_root       = logger(None, level='DEBUG', propagate=1, handlers=[handler_errors_file, handler_main_file, handler_screen])
        #   этот логер перехватывает логи торнадо-приложения, пропускает через себя и отдаёт хендлерам:
        log_app        = logger('tornado.application', level='DEBUG', propagate=1, handlers=[handler_errors_file, handler_main_file, handler_screen])
        #   этот собирает логи событий модели уровня выше или равного INFO (не отладочные) 
        #     и отдаёт соответствующим хендлерам, дальше для обработки свои сообщения не пускает
        log_events     = logger('app.model.events',     level='INFO',  propagate=0, handlers=[handler_errors_file, handler_events_file])
        #   этот логер съедает и отдаёт спец-хендлеру (он не показан выше, но должен быть) все логи http доступа
        log_websrv     = logger('tornado.access',                    level='DEBUG', propagate=0, handlers=[handler_websrv_file])
        #    этот логер глотает и гасит за ненадобностью все логи, которые генерит библиотека PIL при работе с PNG-файлами
        log_pil        = logger('PIL.PngImagePlugin',                level='INFO',  propagate=0, handlers=[handler_null])

    Если сообщение не перехвачено и не остановлено специфичным логером, оно улетает корневому логеру.
    Ну примерно как-то так.
    Само собой всю эту настройку лучше вынести в отдельный модуль и запускать при старте главного файла.
    Логеры вроде log_pil, log_websrv и прочих сохранены в отдельные переменные, но по факту в коде эти переменные нигде не используются. Их можно и не присваивать вовсе. В модуле логирования все логгеры регистрируются глобально в списке и поэтому в каждом модуле не требуется ничего импортировать, достаточно создать логгер по имени. По этому имени логер ищется среди созданных или автоматом создаётся новый. Имена логгеров используют точечную нотацию, и по ступеням имени их удобно фильтровать.

    Такой подход имеет свои недостатки (синглтон, неявное описание, толерантность к ошибкам), достоинства его весомы: отсутствие ада зависимостей (когда непросто или невозможно определить ациклический порядок создания логеров), гибкость настройки, изоляция (возможность перекрытия и фильтрации не влезая в код логирования).

    P.S.
    Старайтесь не использовать CamelCase в именовании файлов проекта. Не смотря на то, что негласное это правило часто нарушается даже популярными библиотеками, всё же это часто создаёт лишнюю неразбериху и проблемы.
    В частности много геморроя можно поиметь переименовав файл сменив лишь регистр символа под контролем версий при работе с репозиторием на разных файловых системах. В винде, к примеру, регистр в именах сохраняется, но системой не различается, а в линукс различается. Таким образом винда не увидит переименования, а линукс увидит. Это может породить адский треш.
    Ответ написан
    Комментировать
  • Как парсить очень сложный текст?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Если вы хотите что-то парсить, это значит, что этого "чего-то" очень-очень много. Тем более, если хотите применять ML. В этом случае нужна еще и обучающая выборка, где есть много-премного примеров входных данных в размеченном виде.
    К примеру на сайте службы доставки есть районы, где они работают

    Из вопроса не ясно сколько "грязных" данных есть. Неужели на одном единственном сайте службы доставки НАСТОЛЬКО много адресов в таким виде? Или таких сайтов миллион?
    Паресеры и ML превосходят возможности человеческой обработки только на больших, очень больших масштабах.

    Из вашего вопроса складывается впечатление, что вы спрашиваете как сделать некий парсер, который будет парсить ЛЮБЫЕ грязные данные произвольного вида и типа в любых количествах.
    Детализируйте свой вопрос, Показывайте больше примеров.

    Сильного ИИ ещё не существует (а когда появится, он будет не менее ленив, чем те, кому призван помогать).

    Для вас с вашим слишком общим вопросом есть только общие рекомендации.
    извлеките данные в текстовом виде;
    • просмотрите глазами: если писать парсер дольше, чем перелопатить вручную, а при пополнении в датасет может прийти совершенно нового вида мусор, то парсер не нужен, обрабатывайте руками;
    • настраивайте обработку датасета по ступенчатой схеме так, чтобы на каждой ступени происходили минимальные не калечащие изменения как можно большего объёма данных, а выхлоп передавался на следующую ступень без потери данных предыдущего шага;
    • просматривайте изменения, затронутые каждым шагом, если видите порчу данных, делайте дополнительные промежуточные шаги;
    • серией ступеней из простых замен по шаблону и рег-экспу делайте текст более структурированным: заменяйте разделители по паттернам, экранируйте или удаляйте содержимое скобок, заменяйте кавычки на однотипные, убирайте мусорные элементы, которые гарантированно не дадут сведений о полезных данных; раскрывайте аббревиатуры и сокращения, приводите синонимы к одному из вариантов...
    • на каком-то этапе ваш входной датасет должен превратиться из монолитного текста в CSV-поток с одной единицей данных на строку;
    • дальше так же ступенчато делайте чистку и дедупликацию потока, разделяйте записи на отдельные поля, выделяйте новые атрибуты.

    Постройте пайп-лайн так, чтобы изменения, внесённые каждым шагом можно было анализировать в виде диффа. Сделайте отдельный аналитический "вью", где будут видны только затронутые правкой ступени данные. Сразу видно будет косяки и калечащие изменения.
    Считайте статистику правок каждой ступенью, реагируйте на крайней случаи.
    Важно, что на каждом этапе энтропия датасета должна уменьшаться. Любая потеря данных вредна, поскольку заметить, скажем, что у вас "г." обозначает не только город, но и, к примеру, "гражданский" в большом количестве топонимов в конце обработки -- это фатально. Вы запутаетесь. Нужно выносить удаляемые данные в отдельные поля и оставлять возможность делать по ним анализ на более поздних этапах.
    В целом исходный датасет у вас всегда созранён и всегда можно быстро повторить шаги.
    Сохраняйте этапы написания парсера в системе контроля версий, делайте чаще коммиты с внятными описнаиями.
    И да хранит вас Кнут и Страуструп.
    Ответ написан
    Комментировать
  • Как проверить наличие строки в файле в bash скрипте?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    $ grep "\## \[1\.0\.3\]" t.md 
    ## [1.0.3]
    $ grep "\## \[1\.0\.5\]" t.md

    Почему нельзя грепунть-то?
    Ответ написан
    4 комментария
  • Автоматическое размещение фото по папкам, реально?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Ну блин. Ща формально правильное решение приведу на тупой вопрос.
    Создаём тестовые "фотографии", тысячу штук:
    py "(f'{i:04}.img' for i in range(1000))" | xargs -n 1 touch

    А вот и наша программа:
    #!/bin/bashbin
    # Сама создаёт папку:
    mkdir my_folder
    # И переносит туда 15 фотографий:
    ls *.img | head -n 15 | xargs -n 1 mv -t my_folder

    Как видите всё возможно.
    На какой ОС?
    Пор какому принципу выбрать 15 фото из тысячи?
    С каким именем создать каталог?
    Что, один каталог?
    Почему 15 файлов? Всегда 15? Ок, 15 так 15.
    Ну что за люди, может они все телепаты, а только я один такой урод, что ситать чуджие мысли не могу на рсстоянии?

    UPD.
    Даже опечатки исправлять не буду. Прям до трясучки бесит вот такая постановка вопросов.
    Такое ощущение, что автору лень даже свой вопрос перечитать, прежде чем запостить, а не то, что погуглить и секунду подумать!

    UPD2.
    Ну ок, в каментах автор вопроса обмолвился (нечаянно, похоже), что ему надо все файлы распихать по папкам по 15 штук.
    Вот, держите:
    ls *.img | py -l "(p.mkdir(exist_ok=1) or f.rename(p/f) for p, f in ((pathlib.Path(f'{i//15:03}'), pathlib.Path(f)) for i, f in enumerate(l)))"

    Тут ls *.img перечисляет все файлы, а однострочник на питоне весь список перебирает, нумерует, создаёт N/15 каталогов (где N - общее количество файлов), и распихивает файлы по этим каталогам: с нулевого (по счету) по четырнадцатый в "000", с 15 по 29 в "001" и т.д.

    UPD3.
    Ну ок, для винды будет примерно так (попробовать не на чем, но должно работать):
    py "(p.mkdir(exist_ok=1) or f.rename(p/f) for p, f in ((pathlib.Path(f'{i//15:03}'), pathlib.Path(f)) for i, f in enumerate(sorted(pathlib.Path('.').glob('*.img')))))"
    Ответ написан
    Комментировать
  • Что лучше для оптимизации игры:нарисовать внешность персонажей или дать эту работу коду?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Нарисуйте хотя бы кое-как одного-двух персонажей, дайте им тупо разные цвета и не заморачивайтесь на монотонных рутинных деталях пока у вас не появится хоть что-то работающее.
    Почитайте про MVP.
    Есть вероятность, что вам не подходит ни первый вариант, ни второй, а проект тупо загнётся потому, что слишком сложный для вашего опыта.
    Может так случиться, что это оптимальный вариант, и вы сможете заняться чем-то более продуктивным, простым и даже интересным для вас.

    Ещё в интернете есть коллекции готовых бесплатных хорошо нарисованных ассетов. Пусть они, может быть, не вполне подходят вам по стилистике или задумке, но не придирайтесь. Главное - сделать что-то работающее, более-менее играбельное. Если вы на это способны и не тонете в бесконечных метаниях перфекциониста, которому не дают приступить к делу раздумья о том, как сделать все "правильно", то все у вас получится. Найдутся соратники, которые захотят вам нарисовать персонажей, найдутся программисты, которые захотят к вам в команду, чтобы кастомизировать ваших персов...

    Главное - это умение выделить главное и сконцентрироваться на нем.
    Ответ написан
    Комментировать
  • Как изменить порядок в объекте?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    d['statistics'].insert(2, d['statistics'].pop(0))
    2 - потому что индексируются элементы с нуля, то есть у третьего по счету будет индекс 2.
    При вставке элемента по индексу, тот элемент, что там уже был и все последующие сдвинутся вправо.
    Сначала вычисляются аргументы, а потом выполняется функция, куда они продаются. Это значит что справа первый элемент будет извлечён из списка, а потом вставлен на третье место.
    Ответ написан
  • Как ограничить диапазон двузначных чисел в нетривиальном регулярном выражении?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вы можете описать регуляркой число от 1-16 так: (1[0-6])|([1-9])
    Но регекспы не предназначены для условной фильтрации по выражениям.
    Обычно они определяют только синтаксис и разделяют текст на группы, которые потом нужно преобразовывать к типам и проверять.
    Если приспичило всё-таки, используйте мой вариант описания числа 1-16, но завтра вам потребуется определять четность, а послезавтра потребуется, чтобы правая граница интервала была вдвое больше левой... Надо где-то остановиться и где именно -- решать вам.

    Вот в применении к вашему случаю:
    ^((1[0-6]|[1-9])(-(1[0-6]|[1-9]))?)(,((1[0-6]|[1-9])(-(1[0-6]|[1-9]))?))*$

    Можете даже потестировать
    Ответ написан
    2 комментария
  • Как быстрее освоить Python + Django, если не шарю в бэкенде?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Прочитайте что такое http (начните с википедии).
    Скачайте (ждать доставки вам некогда) книжку издательства O'Relly про джанго. Прочитайте её махом без остановок и выполнения заданий. Потом ещё раз, но уже выполняйте всё.
    По ходу записывайте все незнакомые слова и термины, гуглите.
    Ищите на гитхабе работающие примеры готовых проектов на джанкго. Читайте код, ищите и разбирайтесь по непонятным местам.
    Подробнее трудно, так как не понятно, может вы вообще не шарите, а может с C# переезжаете.
    Ответ написан
    2 комментария
  • Как сформировать вложенный json из структуры?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    Если событий у вас действительно много, то рекомендую вам не пихать их в один JSON, а воспользоваться вот таким форматом: jsonlines.org
    Это разделённые абзацами строки с JSON'ами.
    Этот формат не требуют огромного количества памяти (на весь датасет разом), его можно обрабатывать потоково.
    Но в задаче не сказано, что отдельные треки одного событий идут подряд и отдельные координаты одного трека тоже идут подряд. Поэтому рассчитывать на это не приходится.
    Уточните задачу и можно будет оптимизировать решение по памяти перейдя к потоковой выдаче.
    import struct
    import json
    
    filepath = 'hits.dat'
    events = {}
    with open(filepath, 'rb') as fp:
        buffer = fp.read(28)
        while len(buffer) == 28:
            event_id, track_id, x, y, z = struct.unpack(">HHddd", buffer)
            buffer = fp.read(28)
    
            event = events.setdefault(event_id, dict(event_id=event_id, tracks={}))        
            track = event['tracks'].setdefault(track_id, dict(coordinates=[]))
            track['coordinates'].append(dict(x=x, y=y, z=z))
    
    with open('hits.json', 'w') as fp:
        json.dump(events, fp, indent=2)

    Файла у меня вашего нет, поэтому писал на ощупь. Проверяйте.
    У вас, кстати, ошибка в примере JSON'а.
    Нужно так:
    events = {
        1: {
            'event_id': 1,
            'tracks': {
                1: {        
                    'track_id': 1,
                    'coordinates': [
                        {'x': 1, 'y': 2, 'z': 3},
                        {'x': 4, 'y': 5, 'z': 6}
                    ]
                },
                2: {
                    'track_id': 2,
                    'coordinates': [
                        {'x': 12, 'y': 22, 'z': 33},
                        {'x': 44, 'y': 55, 'z': 66}
                    ]
                }
    
            }
        }
    }
    Ответ написан
  • Как сравнить две строки DataFrame без учета последовательности?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    def h(s):
        return tuple(sorted(s.split())
    
    > h('4 3 1 2')
    ('1', '2', '3', '4')
    
    > h('1 2 4 3')
    ('1', '2', '3', '4')
    Ответ написан
    Комментировать
  • Проект Эйлера. Задача №2. Как решить правильно?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    ВНИМАНИЕ! Это неправильное решение!
    Это сумма четных по счету элементов ряда фиббоначчи.
    a = 1
    b = 2
    s = b
    while b < 4*10**6:
        a = a + b
        b = a + b
        s += b
    
    print(s)

    А если четные (по значению) элементы складывать, то нужно так:
    s, a, b = 0, 1, 2
    while b < 4*10**6:
        if b % 2 == 0:
            s += b
        a, b = b, a + b   
    
    print(s)

    А ваше решение из вопроса неэффективно. Оно сначала строит список из всех элементов ряда, не превышающих заданного. Потом фильтрует их по четности, благо лениво и не требуется лишней памяти. Ну и суммирует фильтрат. Итого у этого алгоритма O(2*N) сложность по памяти и по столько же по операциям.
    Предложенный выше алгоритм -- O(1) по памяти и O(N) по операциям.

    И на старуху бывает проруха. Ага. невнимательность.
    Ответ написан
    Комментировать
  • Есть ли ПО для записи терминала Linux?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Тысячи их. И даже лучше чем видео.
    https://www.ostechnix.com/how-to-record-terminal-s...
    https://www.linuxlinks.com/terminalrecorders/
    Мне вот этот нравится:
    https://asciinema.org/
    Ответ написан
    2 комментария
  • Как очистить переменую от букв чтобы потом привратить в число?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    v = int(val_2.split('_')[-1])
    Ответ написан
    Комментировать
  • Почему не проходит валидацие через regex?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    (SM-\d+ - \[(Infrastructure|FIX)\]) - \S.*
    https://regex101.com/r/FAaQ5b/1

    Вообще вы не предоставили достаточно кейсов для нормального ответа.
    Может вам такое больше подойдёт:
    (SM-\d+ - )?(\[(Infrastructure|FIX)\] - )?\s*.*
    Тут тикет-id необязателен и тип коммита необязателен и в крайнес влучае проматчится хотя бы текст.
    Хотя если валидатор, то вы, наверно, хотите. чтобы все коммиты были к тикетам привязаны и отмечены типом.
    Ответ написан
  • Как сделать распознавание цифр на изображении?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    У вас несложная капча. Этапы распознавания следующие:
    1. Находим зоны связности по цвету.
    2. Пересекающиеся AABB ограничивающими прямоугольниками зоны объединяем в одну.
    3. Вырезаем зоны, масштабируем к единому размеру.
    4. Пастеризуем до монохромного.
    5. Цифры у нас семисегментные. Нужно 7 линейных детекторов: это линейные риски (черточки), которые гарантированно перечеркнут сегмент. Метрикой детектора будем считать количество закрашенных пикселей под риской. Детекторы с метрикой меньше пороговой считаем не обнаружили сегмента.
    6. По отдельному словарю декодируем набор включенных сегментов в цифру.
    Ответ написан
    4 комментария
  • Как правильно составить Sql запрос для получения данных из двух таблиц?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Если строго по условиям, то так:
    SELECT
      u.name, (SELECT COUNT(*) FROM stat s WHERE s.id_user = u.id)
    FROM users u
    WHERE u.date BETWEEN :dt1 AND :dt2


    Так вы посчитаете статьи только тех пользователей у которых они есть и удобно добавлять условия на сами статьи:
    SELECT u.name, count(*)
    FROM
      stat s
        JOIN users u ON u.id = s.id_user AND u.date BETWEEN :dt1 AND :dt2
    GROUP BY u.id, u.name

    Можно вывернуть наизнанку. Так получится вернуть и ноль статей для пользователей без статей. Как в первом запросе, но проагрегировать можно помимо количества статей, скажем, самое большое количество лайков (если бы у статьи было ещё число лайков). Или самую позднюю статью (если бы у статьи была дата публикации). Первый запрос на это не способен, вернее способен, но за счет либо доп затрат, либо ухищрений и не во всех диалектах.
    SELECT u.name, count(*)
    FROM
      users u  
        LEFT JOIN stat s ON s.id_user = u.id
    WHERE u.date BETWEEN :dt1 AND :dt2
    GROUP BY u.id, u.name
    Ответ написан
  • При разделении на предложения как определить предложение, которое не заканчивается на какой-либо знак препинания?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    /[^\.\!\?!。?,,::;;]+([\.\!\?!。?,,::;;]+|$)/g
    Ответ написан
    Комментировать