Какие компетентные сотрудники в магазинах.. Это по-моему один из топ-10 вопросов у покупателей компьютеров в последние 5 лет (когда планка 4 гб была пробита в потребительской технике)
Rusnire когда станет "много" зависит от ваших SELECT-ов. Если делать выборку по проиндексированному полю, то действительно 10M не проблема, а если делать LIKE по подстроке не в начале строки без полнотекстового индекса, то и 10K будет уже много.
hardwellZero
1) пароль полюбому в открытом виде хранить не стоит, лучше захэшировать. Т.к. широко известные хэш-функции производят хэши постоянной длины (например, SHA-1 дает 20-байтовый хэш), то имеет смысл поле password сделать фиксированной длины, либо byte(20), либо, если двоичные данные фиксированной длины не поддерживаются вашей СУБД, то хранить в шестнадцатиричном виде в char(40), по 2 hex-символа на байт.
2) уберите product_id из Order, зачем он там теперь?
3) подберите для датывремени подходящий тип данных в вашей СУБД. Строка ни к чему там, у всех нормальных баз есть что-то подходящее.
4) для цен и денег юзайте лучше Decimal. Он не теряет точности, и может хранить дробные значения. По возможности погуглите задачу хранения денежных значений (например про то, что их нельзя хранить в double/float).
Вкусовщина (не может претендовать на абсолютную объективность):
1) оформите названия внешних ключей в одинаковом стиле. id_category замените на category_id;
2) я обычно не дублирую имена таблиц в полях, которые характеризуют эту же сущность. По мне удобнее писать table.field, например category.name нежели category.category_name. Т.е. я напишу product.category_id, но не product.product_id. Вместо datetime_order можно creation_datetime, чтобы в запросах выглядело так: order.creation_datetime.
3) не забывайте, что поле paid у вас избыточное. Его можно оставить, чтобы не лезть все время в список транзакций, однако вы должны понимать, что оплаченность заказа можно определить и по наличию соответствующей записи в таблице Transaction. Таким образом, у вас есть два источника одной и той же информации, т.е. есть избыточность. Всякое избыточное хранение изменяющейся информации потенциально может привести к тому, что ее источники перейдут в противоречивое состояние (например, запись о транзакции есть, а поле paid установлено в false). Избыточность может быть полезна для ускорения выполнения запросов на выборку ценой усложнения структуры данных и усложнения запросов на модификацию. Для дальнейшего изучения гуглите "нормализация" и "денормализация". Это очень широкая проблемная область, изучите хотя бы нормальные формы.
4) назовите таблицы в одинаковом числе. Я называю в единственном, но это не так важно, важно чтобы было одинаково. Или Order и Transaction или Orders и Transactions.
Помните, что любое изменение схемы базы уже работающей куда более затратно, чем изменение кода приложения. Поэтому делайте аккуратно и вдумчиво :). Остались еще вопросы?
hardwellZero
date и time как правило в одном поле хранят, в зависимости от СУБД есть разные типы данных для этого, но по разным полям дату и время разносят редко (если нужно и то и другое).
С заказами все просто: в большинстве магазинов вы кладете в корзину несколько товаров, а потом сразу их все заказываете и оплачиваете целиком. В вашей текущей схеме нет раздельных понятий заказа и отдельного заказанного товара. Т.е. у вас Order это всегда ОДИН заказанный товар - нет сущности для группировки нескольких товаров в заказ. Я бы сделал Order(id, user_id, datetime, total_price) и OrderItem(order_id, product_id, quantity). Первое это как весь чек целиком из супермаркета, а OrderItem - это одна строчка в нем. Каждый элемент заказа - это заказ, к которому он относится, выбранный товар и сколько заказано этого товара.
В транзакции вижу добавили order_id, хорошо. Тогда, возможно, user_id избыточен, если оплатить заказ может только тот, кто его оформил (если так, то user_id можно взять из заказа, нет смысла его дублировать в транзакции). И вновь непонятно, зачем вам paid - у вас могут быть "неоплаченные" транзакции? Если да - то все ок, если нет - тогда уберите это поле. Хотя это странно конечно :). Неоплаченным может быть счет (invoice), а транзакция (финансовая) это и есть оплата как таковая.
hardwellZero
А, ну и сохраняйте еще дату+время оплаты. Все операции с деньгами - это как минимум дата+время, очень желательно - "место" (через какую систему, каков номер транзакции в платежной системе), и сумма. Без этой информации будут большие проблемы с клиентами и потерянными платежами.
hardwellZero
1) по заказам - классический простой вариант - пара Order и OrderItem
2) по total_price - ну не знаю, не знаю, в некоторых случаях и правда нужен, и в некоторых лучше считать стоимость как сумму цен на все элемент заказа. Поле выглядит избыточным. Точное решение зависит от бизнес-процессов в магазине (как меняются цены, считается стоимость заказов и т.д.);
3) paid - раз оплачено/неоплачено, то поле должно быть у ЗАКАЗА. И лучше даже не поле, а таблицу Transactions со ссылкой на ID заказа. Тогда при оплате вам будет достаточно вставить новую запись в таблицу транзакций с номером оплачиваемого заказа. Заказ считается оплаченным, если запись в Transactions, ссылающаяся на него, покрывает его стоимость. Это позволит даже оплачивать заказ частями - про проверке оплаты заказа все транзакции, ссылающиеся на заказ, нужно просуммировать, и посмотреть, покрывает ли сумма стоимость заказа или нет. Paid можно использовать разве что как денормализованное поле - чтобы не извлекать постоянно список всех транзакций на заказ, это да.
4) Сама таблица транзакций тоже странная. Вам надо что-то вроде (used_id, order_id, amount), ну или если всегда оплачивается строго ВСЯ сумма заказа, то (user_id, order_id). Каждая такая запись будет значит, что такой-то заказ полностью оплачен таким-то пользователем. Хотя я бы все-таки сохранил amount, чтобы таблица транзакций была в первую очередь логом фактических платежей, а уже во вторую очередь - доказательством оплаты стоимости заказа.
я думаю "в едином стиле" в данном случае немного притянуто). Ну т.е. конечно все запросы действительно можно упаковать в процедуры, но для запросов изменения данных это куда более оправдано (особенно если несколько запросов в одной транзакции).
Рекомендация Майкрософта звучит любопытно. Не могу сказать, что читаю много вайтпейперов по MSSQL, однако такая рекомендация выглядит странно в сочетании с Entity Framework, которую они рекомендуют для большинства бизнес-задач.
В любом случае, даже если пользуетесь ado.net напрямую, я думаю что достаточно составить правильный запрос с необходимыми агрегациями и сортировками, и пользоваться им непосредственно. Для повышения производительности используйте prepared statements, не думаю что будет много разницы с хранимками. На мой глаз ваши два селекта в хранимой процедуре смотрятся поистине странно ;)
@hardwellZero
Ну что-то вроде того. Попутно:
- таблицу категорий потеряли (или не нужна?)
- обычно пару (product_id, quantity) хранят в таблице вроде OrderItem, т.к. весь заказ целиком может включать несколько различных товаров.
- нужна ли заказу total_price?
- attribute_value у product_attr_value - я думаю вам нужна строка, а не int, т.к. характеристики у вас разного типа;
- что представляет из себя таблица Transactions и ее поле paid? совершенно непонятно как она будет работать и что делать.
product_attribute:
- id;
- category_id;
- attribute_name; # это скорее служебное имя, чем отображаемое. для отображения лучше завести отдельную таблицу отображаемых имен на разных языках
- attribute_type;
product_attribute_value:
- product_id --FK--> product.id;
- attribute_id --FK--> product_attribute.id;
- attribute_value; # строка или BLOB для хранения значения
В таблице category у вас будут записи (1, "Phone"), (2, "Notebook"), (3, "Tablet).
В таблице product_attribute у вас будут записи
(1, 1, "RAMSize", "INT"),
(1, 2, "FlashSize", "INT"),
(1, 3, "BatteryHours", "FLOAT"),
(2, 1, "HDDSize", "INT"),
(2, 1, "RAMSize", "INT"),
....
Этот подход ортогонален обычному реляционному проектированию. Вы сделали правильно, "классически" - есть сущности в базе, у них есть характеристики, каждая характеристика - это поле. Только есть проблема, и она в том, что природа реляционной модели - в использовании четкой схемы данных. Если в таблице есть поле "Вес", оно будет у всех записей этой таблицы (разве что, можно разрешить для него NULL-значения, хотя это тоже немножко костыль). Многие характеристики ("размер диагонали") адекватны для одних товаров, и абсолютно неадекватны для других. Поэтому четкой схемы для характеристик товаров выработать невозможно (для магазина широкого профиля). Кроме того, появляются новые категории и новые характеристики. Природа характеристик товаров не очень хорошо вписывается в реляционную модель, поэтому, если хотят ДЛЯ ОСТАЛЬНЫХ данных использовать все-таки реляционную базу (информация о клиентах, заказах, доставке и т.д., которая лучше укладывается в таблицы), то используют воркэраунд для реляционной модели под названием EAV.
hardwellZero
Да, будет одна с атрибутами. Точнее, одна с метаданными о самих атрибутах (т.е. с информацией о том, что есть такая характеристика как "Ширина" или "Вес"), а вторая - со значениями конкретных атрибутов для конкретных товаров.
Wolfak: вам MrDywar Pichugin объяснил смысл - либо держите объект или значение в статик-поле на протяжении работы всей программы, либо передаете ОДИН И ТОТ ЖЕ объект с вашим полем в те места, где его нужно использовать. Вообще, стоит вытащить эту переменную и вообще все предметные данные из формы в отдельный модельный объект, в нем все хранить и его передавать куда нужно.
Вы бы с языком поближе познакомились сначала, а раз уж за WPF взялись, так про MVVM почитайте. Например, публичные поля во многих случаях залог для потенциальных проблем в дальнейшей разработке,было бы неплохо обернуть в свойство.
Александр Оба - в смысле две версии одного и того же компонента? или оба в смысле msvcr и msvcp? 3dmax грузит ваши либы, свои, или и ваши и свои одновременно?
Hitsuzen
Неделя поиска работы - это ничего. Это для дворника, может, много, или грузчика, но не программиста. Я на первую фулл-тайм вакансию одно тестовое задание неделю писал, плюс еще переписка дня три-четыре. И потом все заглохло, когда я сказал, что еще учусь, пусть и на последнем курсе). Вакансия была шарповская. Еще одно собеседование (успешное, работаю уже 2.5 года, C++) - дня 2-3 ждал ответа по почте, прислали тестовое, делал 2 недели, еще через 2 дня позвали побеседовать, в течение двух дней сообщили, что берут. Ах да, дело было после НГ, вот на последних раб. днях декабря мы впервые списались по почте, а в начале февраля я вышел на работу. Итого - месяц (!) на то, чтобы понять who is who.
@Tenebrius
Всего запроса будет два, вставка данных в первую таблицу, и вставка данных во вторую, с использованием LAST_INSERT_ID() на месте parent_id:
INSERT INTO foo (val1, val2) VALUES(1,2); # id генерится
INSERT INTO foo2 (parent_id,val) VALUES(LAST_INSERT_ID(),14); # только что сгенеренный id вернется функцией last_insert_id() и сразу подставится в запрос