Backend
3.95K subscribers
35 photos
706 links
Комьюнити Backend программистов.
Python, Java, Golang, PHP, C#, C/C++, DevOps

Сайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp
Download Telegram
​​Миграция базы данных PostgreSQL

Мы будем использовать две логические процедуры, а именно: репликацию и дампу.

Для начала необходимо подготовить исходный кластер, выполнив команды в данной последовательности:

ALTER ROLE <name> WITH REPLICATION, в скобках вместо name необходимо указать имя пользователя;

• Затем откройте postgresql.conf и укажите следующую информацию: wal_level = logical

• На этом этапе должна настроиться аутентификация в pg_hba.conf. Где <host>, укажите IP или DNS приемника:

host all all <host> md5

host replication all <host> md5


• Перезапустите систему управления базами данных с использованием инструкции: systemctl restart postgresql.

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

Чтобы перенести базу данных, используйте следующие утилиты: pg_dump и pg_restore.

В завершении необходимо создать публикацию в источнике при помощи инструкции CREATE PUBLICATION <name> FOR ALL TABLES, указав название публикации. Для создания публикации нужно обладать правами superuser.

После этого нужно создать подписки в приёмнике, применив инструкцию CREATE SUBSCRIPTION <name> CONNECTION, указав название подписки. Для этого действия нужно иметь доступ администратора dbaas_admin.

В связи с невозможностью репликации последовательностей необходимо произвести восстановление дампа с последовательностями при помощи утилиты psql. Для этого сбросьте подписку в приемнике, выполнив команду:

DROP SUBSCRIPTION.


Создать дамп можно при помощи вышеупомянутых утилит: pg_dump и pg_restore. Дамп базы данных можно создать в кастомном формате (для восстановления отдельных элементов) или в SQL формате.
👍6👀21🤯1
​​Что такое WebSockets

Веб-сокеты это продвинутая технология, позволяющая открыть постоянное двунаправленное сетевое соединение между браузером пользователя и сервером. С помощью его API вы можете отправить сообщение на сервер и получить ответ без выполнения http запроса, причём этот процесс будет событийно-управляемым.

Как это работает?

• Первое что мы делаем — отправляем обычный TCP-запрос на сервер, мы говорим, что хотим подключиться к серверу и ждём от него ответа. Такой процесс называется “рукопожатие” (Handshake), он используется повсеместно, например когда вы подключаетесь к роутеру ваш телефон отправляем запрос роутеру с ключами, роутер отвечает ОК и вы успешно подключаетесь.

• Затем происходит обмен данными: допустим один из множества клиентов отправил HTTP-запрос серверу и нужно отдать ответ не только одному клиенту, а целой сети! Сервер в таком случае отдаст обычный ответ отправителю запроса, а всем другим пришлёт пакеты по WebSocket-соединению с полезными данными.
👍15
​​Самая простая команда в работе с Kubernetes

Для начала, пожалуй, самое простое и полезное действие в работе с Kubernetes. Следующая команда включает автодополнение команд kubectl в оболочке bash:

echo "source <(kubectl completion bash)" >> ~/.bashrc


Автозаполнение kubectl пропишется в файл .bashrc и будет автоматически активироваться каждый раз при запуске оболочки. Это ускоряет набор длинных команд и параметров, таких как all-namespaces. Подробнее в справке Kubernetes по bash.
​​Мост

Приведем аналогию: Допустим, у вас есть сайт с несколькими страницами. Вы хотите позволить пользователям менять темы оформления страниц. Как бы вы поступили? Создали множественные копии каждой страницы для каждой темы или просто сделали отдельные темы и подгружали их в соответствии с настройками пользователей? Шаблон «Мост» позволяет реализовать второй подход.

Шаблон «Мост» — это предпочтение компоновки наследованию. Подробности реализации передаются из одной иерархии другому объекту с отдельной иерархией.

Шаблон «Мост» означает отделение абстракции от реализации, чтобы их обе можно было изменять независимо друг от друга.
👍6
Алгоритмы сортировки

• Быстрая сортировка — это алгоритм «разделяй и властвуй», который выбирает «основной» элемент из массива и разбивает остальные элементы на два подмассива. Затем подмассивы сортируются рекурсивно.

def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)

print(quicksort([3,6,8,10,1,2,1]))


• Сортировка слиянием: Алгоритм сортировки слиянием — это алгоритм «разделяй и властвуй», который делит массив на две части, сортирует две половины, а затем снова объединяет их.

def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)

def merge(left, right):
result = []
i = 0
j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
print(merge_sort([3,6,8,10,1,2,1]))


• Пирамидальная сортировка: Пирамидальная сортировка — это алгоритм сортировки на основе сравнения, который строит пирамиду из входных элементов, а затем многократно извлекает её максимальный элемент и помещает его в конец отсортированного выходного массива.

def heap_sort(arr):
n = len(arr)
for i in range(n, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)

def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
print(heap_sort([3,6,8,10,1,2,1]))
🔥10
​​Не забывайте обновлять зависимости проекта и используемое ПО

Своевременно обновляйте зависимости вашего проекта и ПО на сервере. Устаревший и уязвимый код – мечта злоумышленников, которые найдут в нём уязвимость в два счёта. Для получения свежих обновлений вам даже необязательно осуществлять процесс вручную: есть множество способов автоматизации. Например, инструменты управления зависимостями, такие как Dependabot в GitHub, который автоматически обнаруживает устаревшие или уязвимые зависимости и предлагает актуальные обновления.

Автоматизировать стоит и обновление сертификатов безопасности. Возможно, вы использовали или используете сертификаты от Let's Encrypt, а автоматизировать их обновление совсем не сложно с помощью Certbot.

То же касается и остального серверного ПО: если вы работаете с Linux, для многих его дистрибутивов, основанных на Debian/Ubuntu, с задачей автоматизации обновлений легко справится Unattended upgrades. Данная функция позволяет автоматически устанавливать обновления безопасности и не только их - всё зависит от того, какие настройки вы решите использовать. Ну а для совсем серьезных проектов с зоопарком из серверов созданы удобные решения, такие как Ansible, Chef, Puppet или Salt.
👍3
Какой HTTP метод используется для чтения данных?
Anonymous Quiz
93%
GET
5%
POST
2%
PUT
1%
DELETE
​​Что такое API: Ключевые понятия и примеры использования

API (Application Programming Interface) — это набор инструкций и правил, которые определяют, как различные программные компоненты могут взаимодействовать друг с другом. API определяет, какие запросы можно делать к компоненту, какие данные можно получать и отправлять, и какие действия можно выполнять.

Примеры использования API:


1. Web API: Веб-сервисы, такие как Twitter API или Google Maps API, предоставляют программным приложениям доступ к данным и функциям этих сервисов через интернет. Например, Twitter API позволяет приложениям получать ленту твитов, отправлять твиты и выполнять другие действия в Twitter.

2. Библиотечные API: Библиотеки программного обеспечения часто имеют свои API, которые определяют, как можно использовать функции и методы этой библиотеки. Например, API для работы с базами данных определяет методы для выполнения запросов к базе данных и получения результатов.

3. API операционной системы : Операционные системы также предоставляют свои API, которые определяют, как программы могут взаимодействовать с аппаратным и программным обеспечением компьютера. Например, Windows API позволяет программам работать с файлами, сетевыми ресурсами и графическим интерфейсом.

Подведем итог. API обеспечивает стандартизированный способ взаимодействия между программами, что позволяет разработчикам легко создавать интеграции и взаимодействовать с различными компонентами программного обеспечения.
👍111👀1
Компоновщик

Приведем аналогию: Каждая компания состоит из сотрудников. У каждого сотрудника есть одни и те же свойства: зарплата, обязанности, отчётность перед кем-то, субординация...

Компоновщик - это паттерн, который позволяет обрабатывать группу объектов так же, как и отдельный объект. Он используется для построения древовидной структуры объектов, где каждый узел может содержать как отдельные объекты, так и другие поддеревья.


Пример использования "Компоновщика":

Предположим, у нас есть задачи, которые могут быть либо простыми (например, "Написать отчет"), либо составными, состоящими из нескольких подзадач. Мы можем использовать шаблон "Компоновщик", чтобы представить эти задачи в виде древовидной структуры:

class Task:
def init(self, name):
self.name = name

def execute(self):
print(f"Выполнение задачи: {self.name}")


class CompositeTask(Task):
def init(self, name):
super().init(name)
self.subtasks = []

def add_subtask(self, task):
self.subtasks.append(task)

def remove_subtask(self, task):
self.subtasks.remove(task)

def execute(self):
super().execute()
for subtask in self.subtasks:
subtask.execute()


# Создаем задачи
task1 = Task("Написать отчет")
task2 = Task("Проверить отчет")

# Создаем составную задачу
composite_task = CompositeTask("Работа над проектом")
composite_task.add_subtask(task1)
composite_task.add_subtask(task2)

# Выполняем задачи
composite_task.execute()


В этом примере Task представляет базовый класс для всех задач, CompositeTask - это составная задача, которая может содержать другие задачи (как простые, так и составные). Мы можем добавлять и удалять подзадачи из составной задачи и выполнять все задачи, включая все подзадачи, с помощью одного вызова метода execute().
👍6🤔1
Проблема n+1

Проблема возникает в ORM при загрузке связанных сущностей из нескольких таблиц, когда для каждой записи в первичной таблице выполняется отдельный дополнительный запрос для получения связанных данных из другой таблицы.

🔽Пример


У нас есть три таблицы: "Place", "Users" и "Category". Каждое место может иметь несколько посетителей и одну категорию. Когда мы хотим получить список всех мест и их посетителей, мы можем сделать JOIN, который присоединяет эти таблицы. Но поскольку каждому месту может соответствовать несколько посетителей, то для каждого места будет выполняться отдельный запрос для извлечения всех его посетителей. Это создает дополнительный запрос (n+1), где n — количество записей в основной таблице (Place) , и 1 — запрос для каждого пользователя:

$query = $this->em
->getRepository(Place::class)
->createQueryBuilder('p')
->leftJoin('p.users', 'u')
->leftJoin('p.category', 'c')
->orderBy('p.title', 'ASC');


SQL запрос:

SELECT p0_.id AS id_0, p0_.title AS title_1, p0_.category_id AS category_id_2 
FROM place p0_
LEFT JOIN place_user p2_ ON p0_.id = p2_.place_id
LEFT JOIN "user" u1_ ON u1_.id = p2_.user_id
LEFT JOIN category c3_ ON p0_.category_id = c3_.id
ORDER BY p0_.title ASC


Как можно видеть из запроса, не происходит выборки пользователей, следовательно, получим дополнительные запросы на загрузку пользователей: 5021 query in 3306.08 ms.

Исправим запрос, добавив выборку пользователей:

$q = $this->em
->getRepository(Place::class)
->createQueryBuilder('p')
->select('u','c','p')
->leftJoin('p.users', 'u')
->leftJoin('p.category', 'c')
->orderBy('p.title', 'ASC');


SQL запрос:

SELECT p0_.id AS id_0, p0_.title AS title_1, u1_.id AS id_2, u1_.name AS name_3, u1_.is_active AS is_active_4, c2_.id AS id_5, c2_.title AS title_6, p0_.category_id AS category_id_7 
FROM place p0_
LEFT JOIN place_user p3_ ON p0_.id = p3_.place_id
LEFT JOIN "user" u1_ ON u1_.id = p3_.user_id
LEFT JOIN category c2_ ON p0_.category_id = c2_.id
ORDER BY p0_.title ASC"


В результате, всё выгружается за один запрос: 1 query in 29.32 ms.

Следует отметить, что ORM предоставляют возможность автоматической оптимизации запросов для избежания проблемы n+1, советую изучить эти механизмы по запросу в поисковике — lazy/eager/etc fetch {orm_name}.
👍14👀32
​​Что такое Аутентификация и чем она отличается от Авторизации?

Аутентификация и авторизация - это два различных процесса, которые используются для обеспечения безопасности доступа к ресурсам в информационных системах.

Аутентификация:


• Аутентификация - это процесс проверки подлинности пользователя или сущности.
• В ходе аутентификации пользователь предоставляет учетные данные (например, имя пользователя и пароль), которые затем сравниваются с данными, хранящимися в системе.
• Цель аутентификации - убедиться в том, что пользователь является тем, за кого он себя выдает.
Пример: При входе на сайт пользователь вводит свое имя пользователя и пароль. Если эти данные соответствуют данным в базе данных, пользователь успешно аутентифицирован и получает доступ к защищенным ресурсам.

Авторизация:


• Авторизация - это процесс определения прав доступа пользователя к определенным ресурсам или функциональности.
• В ходе авторизации система определяет, имеет ли пользователь право на доступ к конкретному ресурсу или выполнение определенной операции.
• Цель авторизации - определить, что пользователь имеет право на выполнение определенных действий.
Пример: После успешной аутентификации пользователь получает определенный уровень доступа (например, администратор, модератор или обычный пользователь). На основе этого уровня доступа система определяет, какие действия пользователь может выполнять, например, редактирование профиля или удаление контента.

Итак, основное различие между аутентификацией и авторизацией заключается в том, что аутентификация проверяет личность пользователя, а авторизация определяет права доступа пользователя к ресурсам.
🔥14👍4
Напишите фрагмент кода, нарушающий принцип Don't Repeat Yourself (DRY). Объясните, почему у него плохая организация, и исправьте

Фрагмент кода, нарушающий принцип DRY (Don't Repeat Yourself), может выглядеть следующим образом:
def calculate_rectangle_area(width, height):
area = width * height
print(f"Площадь прямоугольника: {area}")

def calculate_square_area(side_length):
area = side_length * side_length
print(f"Площадь квадрата: {area}")

# Вычисление площади прямоугольника
calculate_rectangle_area(5, 10)

# Вычисление площади квадрата
calculate_square_area(5)


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

Почему это плохо организовано и нарушает принцип DRY?


Дублирование кода: Обе функции содержат одинаковую логику вычисления площади (умножение двух чисел). Это приводит к дублированию кода, что делает его сложнее поддерживать и изменять в случае необходимости.

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

Как можно исправить это, соблюдая принцип DRY?


Cоздать одну универсальную функцию для вычисления площади, которая принимает тип фигуры в качестве параметра:
def calculate_area(shape, *args):
if shape == "rectangle":
width, height = args
area = width * height
print(f"Площадь прямоугольника: {area}")
elif shape == "square":
side_length = args[0]
area = side_length * side_length
print(f"Площадь квадрата: {area}")
else:
print("Неизвестная фигура")

# Вычисление площади прямоугольника
calculate_area("rectangle", 5, 10)

# Вычисление площади квадрата
calculate_area("square", 5)


В этом исправленном примере мы создали одну универсальную функцию calculate_area, которая принимает параметр shape для определения типа фигуры и переменное число аргументов args, которые передаются в зависимости от типа фигуры. Это позволяет нам избежать дублирования логики и сделать код более модульным и гибким для дальнейших изменений или расширений.
🤯9😁61
PHP

Реализуйте функцию findLargest(array $numbers) таким образом, чтобы она возвращала наибольшее число из целочисленного массива $numbers.

Note: массив всегда содержит по крайней мере одно число.


<?php /Starter code/?>
<?php
function findLargest(array $numbers) {
//Ваш код тут
}
?>


Жду ваши решения в комменты 👇
👍1
Active-Record

Active-Record
— шаблон проектирования, который поощряет внедрение в сам объект функций, таких как Insert, Update и Delete, и свойств, которые соответствуют столбцам некой базовой таблицы в базе данных.

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

Такой подход позволяет нам не использовать запросы SQL напрямую. И кроме того, позволяет сделать код более безопасным, так как если нельзя выполнить любой запрос напрямую, то и выполнение SQL инъекций становится более затруднительным или даже невозможным.

Посмотрим простой пример:

@Entity

public class ChessPlayer extends PanacheEntity {

public String firstName;

public String lastName;

public LocalDate birthDate;

@Version

public int version;

public void setLastName(String lastName) {

this.lastName = lastName.toUpperCase();

}

}


В результате будет сформирован SQL запрос следующего вида:

  insert 

into

ChessPlayer

(birthDate, firstName, lastName, version, id)

values

(?, ?, ?, ?, ?)


Останется только добавить значения для соответствующих переменных при обращении к данному классу.
2👀2👍1
​​Что такое Кеширование?

Кеширование (Caching) - это техника оптимизации производительности, которая заключается в временном сохранении результатов вычислений, запросов или доступа к данным, чтобы обеспечить быстрый доступ к этим результатам при последующих запросах. Цель кеширования - уменьшить нагрузку на ресурсы системы и повысить отзывчивость приложения.

Основные принципы кеширования:


Уменьшение времени доступа: Запросы к данным из кэша обычно выполняются быстрее, чем запросы к источнику данных (например, базе данных или удаленному сервису), что сокращает время ожидания.

Снижение нагрузки на ресурсы: Использование кэша позволяет снизить количество запросов к базе данных или другим источникам данных, что уменьшает нагрузку на сервер и сеть.

Улучшение масштабируемости: Кеширование способствует повышению производительности системы и обеспечивает более легкую масштабируемость при увеличении нагрузки.

Улучшение отказоустойчивости: Кэширование может улучшить отказоустойчивость приложения, позволяя обслуживать запросы даже при временной недоступности основного источника данных.

Примеры кеширования:


Кеширование на уровне приложения: Веб-приложения могут кэшировать результаты запросов к базе данных или вычислений на сервере. Например, приложение может кэшировать результаты SQL-запросов, чтобы избежать повторных запросов к базе данных при обработке одних и тех же запросов от пользователей.

• Кеширование на уровне сети:
Прокси-серверы или CDN (Content Delivery Network) могут кэшировать статические ресурсы (например, изображения, CSS, JavaScript), чтобы ускорить их доставку до конечных пользователей и снизить нагрузку на серверы.

• Кеширование на клиентской стороне:
Браузеры могут кэшировать загруженные ресурсы, такие как HTML, CSS, JavaScript и изображения, чтобы повторные запросы к веб-сайту были быстрее и более эффективными.
🔥51👍1
​​Какая разница между организацией кода и архитектурой?

Разница между организацией кода (code organization) и архитектурой (architecture) в разработке программного обеспечения заключается в том, что организация кода фокусируется на структуре, распределении и управлении самим кодом внутри проекта, в то время как архитектура определяет более высокоуровневую структуру системы, её компоненты, взаимодействия и принципы организации.

Организация кода (Code Organization):


• Организация кода касается того, как мы структурируем и управляем кодом внутри проекта.
• Это включает в себя правила и соглашения по именованию переменных, функций, классов, модулей и т.д., а также способы организации кода в файлы и директории.
• Цель организации кода - упростить поддержку, расширение и повторное использование кода.

# Пример структуры проекта Python
my_project/
├── main.py
├── utils/
│ ├── helper_functions.py
│ └── constants.py
├── models/
│ ├── user.py
│ └── post.py
└── tests/
├── test_utils.py
├── test_models.py
└── test_main.py


Архитектура (Architecture):


• Архитектура определяет структуру и организацию системы в целом, включая её компоненты, взаимодействия между компонентами, основные принципы проектирования и решения архитектурных проблем.
• Это высокоуровневое понятие, описывающее общую архитектурную концепцию системы, например, клиент-серверное взаимодействие, микросервисная архитектура, MVC (Model-View-Controller) и т.д.
• Цель архитектуры - обеспечить масштабируемость, надежность, безопасность и удобство сопровождения системы.

Пример архитектурных концепций:

MVC (Model-View-Controller): Разделение приложения на три основных компонента - модель (бизнес-логика и данные), представление (отображение данных на пользовательский интерфейс) и контроллер (управление взаимодействием между моделью и представлением).

Микросервисная архитектура: Разделение системы на небольшие автономные сервисы, каждый из которых выполняет определенную функцию и взаимодействует с другими сервисами посредством API.

Клиент-серверная архитектура: Разделение системы на клиентские приложения (которые отправляют запросы) и серверные приложения (которые обрабатывают запросы и предоставляют данные).

Таким образом, организация кода - это часть более широкого понятия архитектуры, которая фокусируется на том, как мы структурируем и управляем кодом внутри проекта, в то время как архитектура определяет общую структуру и организацию системы, включая её компоненты и взаимодействия.
🔥5👍4
Почему функциональное программирование это мейнстрим?

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

Иммутабельность данных (Immutable Data): Функциональное программирование поддерживает концепцию иммутабельности данных, где данные неизменяемы и не могут быть модифицированы после создания. Это упрощает понимание и предсказуемость поведения программы, поскольку отсутствует состояние, которое может меняться. Иммутабельность также способствует безопасности, особенно при параллельном выполнении кода.

// Пример на Scala с использованием неизменяемой коллекции
val numbers = List(1, 2, 3, 4, 5)
val doubledNumbers = numbers.map(_ * 2)


Функции высшего порядка (Higher-Order Functions): Функциональное программирование активно использует функции высшего порядка, которые могут принимать другие функции в качестве аргументов или возвращать их как результат. Это позволяет писать компактный и выразительный код, делая его более модульным и гибким.

// Пример на JavaScript с использованием функции высшего порядка
const applyOperation = (operation, x, y) => operation(x, y);
const add = (a, b) => a + b;
const result = applyOperation(add, 5, 3); // Результат: 8


Параллелизм и асинхронное программирование: Функциональное программирование способствует разработке параллельных и асинхронных приложений. Благодаря иммутабельности данных и отсутствию побочных эффектов, функциональные программы легче масштабируются и управляются в многопоточной среде.

# Пример на Elixir с использованием параллелизма
Enum.map([1, 2, 3, 4], fn x -> x * x end) # Результат: [1, 4, 9, 16]


Декларативный подход к программированию: Функциональное программирование способствует декларативному стилю кода, где разработчик описывает, что нужно сделать, а не как это сделать. Это делает код более понятным и чистым, уменьшая вероятность ошибок.

-- Пример на Haskell с декларативным описанием задачи
sumOfSquares :: [Int] -> Int
sumOfSquares xs = sum (map (^2) xs)


Работа с состоянием без побочных эффектов: Функциональное программирование поощряет работу с данными и состоянием без использования изменяемых переменных и побочных эффектов. Это делает код более предсказуемым и облегчает отладку.

; Пример на Clojure с работой без побочных эффектов
(defn sum-of-squares [xs]
(reduce + (map #(* % %) xs)))


Интерес к функциональному программированию продолжает расти из-за его способности улучшать модульность, управление состоянием, распределение и параллелизм в разработке программного обеспечения.
👍4
👍2
🗓 Будьте осторожны с датами

with new_table as (
select
patient_id
, first_name
, last_name
, time(birth_date, '+1 second') as birth_date

from patients
where TRUE
and patient_id = 1

UNION

select
patient_id
, first_name
, last_name
, birth_date

from patients
WHERE TRUE
and patient_id != 1
)

select
birth_date

from new_table
where TRUE
and birth_date between '1953-12-05' and '1953-12-06'


В этой базе данных все даты сокращены до дня. Это означает, что все значения времени столбца Birthday_date в этом примере равны 00:00:00. Однако в реальных наборах данных это обычно не так.

В зависимости от среды разработки SQL ваши настройки могут скрыть отображение времени. Но то, что время скрыто, не означает, что оно не является частью данных.

В приведенном выше примере я искусственно добавил секунду к пациенту №1. Как видите, этой 1-й секунды было достаточно, чтобы исключить пациента из результатов при использовании ключевого слова BETWEEN.

Еще один распространенный пример, который чаще всего упускают из вида специалисты по работе с данными, — это присоединение к датам, в которых все еще есть временной компонент. В большинстве случаев они действительно пытаются присоединиться к столбцам с сокращенной датой и, в конечном итоге, не получают желаемого результата или, что еще хуже, они не осознают, что получили неправильный результат.
👍5
​​Задача: Генератор коротких ссылок

Ваша компания отправляет СМС с трекинговой ссылкой, но ссылка достаточно длинная и из-за этого СМС выходит за 70 символов (длина 1 СМС). Необходимо спроектировать сервис-«укорачиватель ссылок», чтобы сэкономить деньги компании. Интервьюер при этом выступает заказчиком со стороны бизнеса и ему можно задавать вопросы по сути задачи.

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

Основные направления обсуждения:

как будем делать с точки зрения структуры приложения (в самом проекте/микросервисе и критерии, по которым мы делаем этот выбор). И тот, и другой варианты допустимы — все дело в аргументации. Если кандидат вообще не говорит про задачу с этой стороны, а, например, сразу переходит к структуре таблицы, то, скорее всего, кандидат никогда не задумывается над такими вопросами и/или не работал в более-менее крупных проектах;

структура данных и выбор хранилища (СУБД, key-value типа Redis, еще какие-то варианты, плюсы и минусы тех или иных вариантов). Опять же, в зависимости от вопросов кандидата и желания интервьюера можно подвести к выбору какого-то варианта, но в целом есть множество вариантов реализации, которые будут оправданы — опять же, вопрос в аргументации выбора;

хеши/коллизии, устойчивость к перебору ссылок (расчет количества необходимых вариантов). Важно, чтобы собеседуемый разработчик узнал про количество ссылок, оценил максимально возможную длину ссылки (и срок действия) и принял решение о том, какое количество символов стоит заложить в короткий URL. Плохо, если кандидат сильно перезакладывается или берет слишком маленькое количество символов, что может привести к тому, что допустимые комбинации в обозримом будущем закончатся;

дополнительно можно углубиться в смежные темы: деплой/мониторинг сервиса в том или ином виде в зависимости от выбора в предыдущих вопросах, приблизительная оценка ресурсов, минимально необходимых для эксплуатации, отказоустойчивость.
👍8
На вход подаются два неупорядоченных слайса любой длины. Надо написать функцию, которая возвращает их пересечение

Стандартная задача с leetcode и ее довольно часто спрашивают на собеседованиях в качестве простой задачи для разогрева.

Можно решить сортировкой, за более долгое время, но без выделения дополнительной памяти. А можно выделить дополнительную память и решить за линейное время.

Надо посчитать количество появлений элементов первого массива (лучше брать тот, что покороче) — используем для этого словарь. Потом пройтись по второму массиву и вычитать из словаря те элементы, которые есть в нем. По ходу добавляем в результат те элементы, у которых частота появлений больше нуля.

Решение (осторожно, много кода):


package main

import (
"fmt"
)

// На вход подаются два неупорядоченных массива любой длины.
// Необходимо написать функцию, которая возвращает пересечение массивов
func intersection(a, b []int) []int {
counter := make(map[int]int)
var result []int

for _, elem := range a {
if _, ok := counter[elem]; !ok {
counter[elem] = 1
} else {
counter[elem] += 1
}
}
for _, elem := range b {
if count, ok := counter[elem]; ok && count > 0 {
counter[elem] -= 1
result = append(result, elem)
}
}
return result
}

func main() {

a := []int{23, 3, 1, 2}
b := []int{6, 2, 4, 23}
// [2, 23]
fmt.Printf("%v\n", intersection(a, b))
a = []int{1, 1, 1}
b = []int{1, 1, 1, 1}
// [1, 1, 1]
fmt.Printf("%v\n", intersection(a, b))
}
👍8