Особенности SQL

MySQL, PostgreSQL, MSSQL, Oracle и т.д. Диалекты SQL, формирование запросов, хранимые процедуры, тригерры, транзакции и многое другое

Особенности SQL

Сообщение Vladislav_133 21 фев 2009, 22:11

Язык SQL - основа управления реляционными базами данных. Сложность при использовании этого языка возникает в том, что не смотря на наличие стандартов, все равно разные производители вносят в команды языка различные нюансы, делающие программное обеспечение на стороне сервера трудно переносимым.
В даннйо ветке мне хотелось бы поговорить об особенностях SQL. Возможно появятся вопросы и кто-то поделится своим опытом.
Для начала приведу один очень интересный пример.
Пусть имеются две таблицы a(id, fio, adres), b(id,adres).
Обе таблицы содержат информацию об одних и тех же объектах. Скажем таблица a содержит информацию о студентах каког-либо факультета, а таблица b о студентах всего института. Столбец adres в таблице a не заполнен. Требуется заполнить его из таблицы b. Замечу, что решить задачку с помощью курсора не представляет особого труда. Я не буду даже на этом останавливаться. Вопрос заключается в том, как это сделать с помощью одной команды.
В MS SQL Server мне удалось найти два варианта решения.

Код: Выделить всё
update a set adres=b.adres
from a inner join b on a.id=b.id

Код: Выделить всё
update a set adres=
(select top  1 bb.adres from a aa,b bb where a.id=bb.id)

Замечу, что использование раздела from в команде update прописано в стандарте.

В PostgreSQL первый вариант хоть можно выполнить (с не значительными изменениями), но к должному результату он не приводит. А вот второй ваириант (со своей спецификой в PostgreSQL) проходит

Код: Выделить всё
update a set adres=
(select bb.adres from a aa,b bb where a.id=bb.id limit 1)

Как видите, не сложная задача, а переносимости никакой.
Если кто-то может привести, как решается данная задачка в других СУБД, прошу на ветку.

Хочется также услышать о других интересных примерах из опыта работы с базами данных.
Последний раз редактировалось Vladislav_133 12 мар 2009, 20:20, всего редактировалось 2 раз(а).
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение xdsl 22 фев 2009, 02:46

MySQL:

update a,b
set a.adres=b.adres
where a.id=b.id
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение Vladislav_133 22 фев 2009, 19:50

Да, чертовски удобно.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение Vladislav_133 12 мар 2009, 20:18

Поступил вопрос о том, как можно делать запрос, чтобы выходило определенное количество строк, начиная с заданной. Ясно, что такой вопрос волнует в основном web-программистов, поскольку в обычных клиент-серверных приложениях принято выводить все строки сразу (если их относительно не много), а уже там, в «клиенте» представлять их так, как это надо по сценарию.
Прежде всего, несколько отвлеченных слов о MySQL и вообще о развитии ПО. Это развитие подчиняется вполне определенным закономерностям. Какова особенность MySQL?
1. Он де факто являлся и является основным (но не единственным) инструментом для построения web-приложений (особенно не коммерческих).
2. Это СУБД долгое время не имело средств программирования на стороне сервера.
Оба фактора привели к тому, что в MySQL появились некоторые возможности, которых еще не было в таких тяжеловесных СУБД как Oracle, MS SQL Server и др. Разработчики СУБД старались вместить в командах больше возможностей, чем это было в обычных реализациях SQL. Чтобы компенсировать отсутствие других возможностей.
В частности к этому можно отнести и возможность обновлять одной командой update сразу несколько таблиц и ключевое слово limit в команде select.
С помощью этого слова имеется возможность вывести определенное количество строк в запросе, начиная с заданной. В других СУБД этому уделяли меньше внимания, поскольку на стороне сервера можно написать процедуру, которая будут выполнять это же действие.
Далее речь пойдет о Transact SQL, поскольку именно о нем был задан вопрос. В начале, я приведу пример именно процедуры, которая эту проблему решает. Для этого имеется такой объект как курсор. Вот схема решения
Код: Выделить всё
create procedure имя
(
   --какие-то параметры

   @n1 int, --номер строки, откуда начинать вывод
   @n2 int –количество выводимых строк
)
As
Begin

   Declare @t  table (…) – вы этой таблице и будем выводить результат

--объявить курсор
declare cur cursor
local forward_only static –эти параметры можно и опустить
for select …    --здесь нужный запрос
--открываем курсор
open cur
fetch next from cur
while @@fetch_status = 0
begin

If (условие, например проверяется номер строки и количество строк)
begin
fetch next from cur into … --список переменных
insert into @t1 (список полей) values(список переменных)
end
end
close cur
dealocate cur
--выводим нужные строки
select * from @t
end

Конечно, длинно, но вполне универсально. По сути, вы сами конструируете, что и как надо выводить.
Рассмотрим более короткий путь. Этот путь основывается на одной из функций ранжирования Row_Number(). Эти функции не являются изобретением MS SQl Server, они прописаны в стандарте.
Вот простой пример. Надеюсь из него все ясно.
Код: Выделить всё
select t1.fio,t1.adres from
(select Row_Number() over (order by fio desc) as n, fio,adres from a) as t1
where t1.n between 2 and 40

Здесь рассматривается запрос к одной таблице, но с тем же успехом можно использовать подобные конструкции и для произвольного количества таблиц. Также обращаю ваше внимание, что в выражении where можно использовать параметры и переменные. Так что все в ваших руках.
Последний раз редактировалось Vladislav_133 13 мар 2009, 13:22, всего редактировалось 1 раз.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение xdsl 12 мар 2009, 20:42

Если я все правильно понял, то в обоих предложенных вариантах есть один агромадный недостаток, а именно: сначала производится полная выборка, а потом из этой выборки возвращается требуемое кол-во строк. Даже если нужно только одну строку из набора данных - будь добр обработать весь миллион.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение Vladislav_133 12 мар 2009, 21:16

В представленных примерах, разумеется, это так.
Но можно придумать еще несколько вариантов.
В select есть ключевое слово top, с помощью него можно указать максимальное количество выводимых строк. Например так select top 20 * from table. Можно сделать следующее. Вывести n строк, сохраняя последнее значение ключа (например в таблице). Далее уже дело техники.
Но соглашусь, что такого удобного средства как limit в transact sql нет.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение xdsl 13 мар 2009, 00:13

У limit, к сожалению, есть свои ограничения, о которых далеко не все знают (хотя в документации от производителя это черным по белому написано, но кто-ж читает документацию? ;) ). Напомню, что у limit есть два параметра - номер записи, с которой начинать и количество возвращаемых записей. Если с кол-м записей проблем нет, то вот с номером исходной записи на больших запросах начинаются проблемы. А именно - все записи до номера исходной записи также будут выбраны, но после окончания выборки - отброшены. Т.е. если хотим начать просмотр с миллионной записи, то сервер обработает и все предыдущие 999999. Вот такой неожиданный Упс. Если этого не знать заранее, то проблема всплывает обычно на этапе интенсивной эксплуатации продукта, когда переписывать код становится уже очень затруднительно.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение alekam 23 мар 2009, 22:59

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

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

если говорить про веб, то хранимые процедуры используют редко. С одной стороны, MySQL они появились относительно недавно. С другой, разделять бизнесс-логику между кодом и БД - внесение дополнительной сложности в систему.
alekam
 
Сообщения: 46
Зарегистрирован: 23 дек 2008, 14:36
Полное имя: A.K.

Re: Особенности SQL

Сообщение xdsl 23 мар 2009, 23:36

alekam писал(а):хм. интересно, а как вы представляете алгорим выборки например первых пяти записей после 25ой? по-любому же надо пройти эти 25 первых, хотябы для проверки их наличия..
Я такого алгоритма не знаю. И думаю, что его просто нет. К сожалению, многие молодые программисты баз данных преклоняются перед функционалом того или иного SQL-сервера и просто не оценивают сложность исполнения запросов. Пока не столкнутся выборками объемом в несколько сотен тысяч записей из множества таблиц. Среди студентов, даже старших курсов, практически не наблюдается критического подхода к формированию sql-запросов. Лишь-бы работало. Это печально и с этим надо как-то бороться.

alekam писал(а):по поводу задачи. как вариант можно написать библиотеку расширений, которая реализует необходимый вам функционал в одну строчку. :lol:
Если на каждую задачу писать библиотеку расширений, можно стать профессиональным разработчиком библиотек расширений, но задачу так и не решить. В свою время для mysql4.0 писал на Си функцию-расширение, аналог которой официально появился только в mysql4.1 (group_concat). Куча времени на отладку, множество трудноуловимых ошибок. Отладил и активно применял, пока не перешел на mysql5.0. Однако времени на разработку этой функции потратил безмерно.

alekam писал(а):... разделять бизнес-логику между кодом и БД - внесение дополнительной сложности в систему.
Во всем есть свои плюсы-минусы. Вот потребуется вам переписать программу на другом языке или под другую платформу - сто раз пожалеете, что бизнес-логика - не в хранимых процедурах. С другой стороны, потребуется сменить сервер БД - умаетесь переводить хранимые процедуры на другой диалект.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение xdsl 23 мар 2009, 23:53

Даже не поленился поднять старый UDF-код своей функции sumstr:
Код: Выделить всё
#ifdef STANDARD
#include <stdio.h>
#include <string.h>
typedef unsigned long long ulonglong;
typedef long long longlong;
#else
#include <my_global.h>
#include <my_sys.h>
#endif
#include <mysql.h>
#include <m_ctype.h>
#include <m_string.h>

extern "C" {

my_bool sumstr_init( UDF_INIT* initid, UDF_ARGS* args, char* error );
void sumstr_deinit( UDF_INIT* initid );
void sumstr_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
void sumstr_clear(UDF_INIT *initid, char *is_null, char *error);
void sumstr_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
char* sumstr( UDF_INIT *initid, UDF_ARGS *args,
         char *result, unsigned long *length,
         char *is_null, char *error);
   
}


/*
** sumstr
*/
#define MAXLEN 255
#define MAXLENID 255
// максимальный размер разделителя
#define MAXDLEN 10
// максимальное кол-во элементов в результате
#define MAXELEN 100


typedef struct
{ uint index;
   uint strlen;
} STR_DATA;

typedef struct
{
  char str[MAXLEN];
  uint len;
  uint indexes[MAXELEN];
  uint lens[MAXELEN];
  uint count;   
  char delimiter[MAXDLEN];
  uint delimiterlen;
} SUMSTR_DATA;

my_bool sumstr_init( UDF_INIT* initid, UDF_ARGS* args, char* error )
{
   if (args->arg_count < 2)
   {
      strcpy(error,"must more 1 arguments- separator and data,data,...");
      return 1;
   }
   initid->max_length=MAXLEN;
   for (uint i=0; i<args->arg_count; i++) args->arg_type[i]=STRING_RESULT;

   if (!(args->args[0]))
   {
      strcpy(error,"first argument (delimiter) must be constant");
      return 1;
   }

   SUMSTR_DATA *data;
   data= new SUMSTR_DATA;
   if (!data)
   {
      strcpy(error,"cannot allocate memory for sumstr data");
      return 1;
   }
   bzero(data,sizeof(SUMSTR_DATA));
   data->count=0;
   initid->ptr = (char*)data;


   // сохраняем разделитель
   data->delimiterlen=args->lengths[0];
        if (data->delimiterlen>MAXDLEN) data->delimiterlen=MAXDLEN;
        memcpy(data->delimiter,args->args[0],data->delimiterlen);
   
   return 0;
}

void

sumstr_deinit( UDF_INIT* initid )
{

   delete initid->ptr;
}

void
sumstr_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* error )
{
   sumstr_clear(initid,is_null,error);
   sumstr_add(initid,args,is_null,error);   
}

void
sumstr_clear(UDF_INIT *initid, char *is_null, char *error)
{
     SUMSTR_DATA *data = (SUMSTR_DATA*) initid->ptr;
   data->len=0;
   data->count=0;
}


void
sumstr_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* error )
{
     SUMSTR_DATA *data = (SUMSTR_DATA*) initid->ptr;
   ulong arglen;

   // перебираем все элементы, кроме разделителя
   for (uint i=1; i<args->arg_count; i++)
   {
         if (!(args->args[i])) continue; 
    arglen=args->lengths[i];
    if (!arglen) continue;   

    uint result=0;
    for (uint k=0; k<data->count; k++)
    { // проверяем на наличие таких-же элементов. если есть, отбрасываем
      if (arglen==data->lens[k] && !memcmp(&data->str[data->indexes[k]],args->args[i],arglen))
       { result=1;
         break;
       }   
    }       

    if (result) continue;

    if (i==1 && data->len){
      // докопируем делимитер, если первый вход в этом наборе, но не первый вообще. возможно i==1 убрать вовсе
          memcpy(&data->str[data->len],data->delimiter,data->delimiterlen);
      data->len+=data->delimiterlen;
    }

    if (data->count >= MAXELEN) break;
    data->indexes[data->count]=data->len;
    data->lens[data->count]=arglen;
    data->count++;

    if (data->len+arglen >= MAXLEN) break;
        memcpy(&data->str[data->len],args->args[i],arglen);
        data->len+=arglen;
   }
}


char*
sumstr( UDF_INIT *initid, UDF_ARGS *args,
         char *result, unsigned long *length,
         char *is_null, char *error)
{
     SUMSTR_DATA *data = (SUMSTR_DATA*) initid->ptr;
   *length = data->len;
   return data->str;
}

И стоит оно того - разводить такой кодинг, перезапускать после каждой компиляции сервер, часами отлаживать, задумчиво чесать затылок, когда sql-сервер отваливается из-за ошибок в коде функции, если одновременно есть возможность реализовать аналог в виде хранимой процедуры? Естественно, когда нет возможности создать аналог - приходится писать библиотеку расширений, тут ничего не поделаешь. Но это, на мой взгляд - самый крайний случай, если действительно уже ничего не помогает.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение alekam 23 мар 2009, 23:59

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

я так понимаю, что плюс тут только в том, что меньше кода переписывать?
alekam
 
Сообщения: 46
Зарегистрирован: 23 дек 2008, 14:36
Полное имя: A.K.

Re: Особенности SQL

Сообщение xdsl 24 мар 2009, 08:28

alekam писал(а):я так понимаю, что плюс тут только в том, что меньше кода переписывать?

Не только. Меньше возможностей запороть бизнес-логику, особенно если программный продукт не ваш, а куплен, скачан или достался "в наследство" от предыдущих разработчиков.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение Vladislav_133 24 мар 2009, 10:48

Есть еще один важный плюс использования хранимых процедур. Осуществлять защиту информации на уровне таблиц, столбцов и строк крайне не удобно. Я обычно предпочитаю так: полный запрет пользователям доступа к объектам базы данныъ, а весь доступ через хранимые процедуры. При этом для каждого можно расписать, какие группы процедур он может выполнить. Это чрезвычайно удобно для больших баз данных.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение Vladislav_133 25 мар 2009, 21:01

Общие табличные выражения.

В стандарте SQL 1999 года предусматривается возможность создания рекурсивных запросов. Понятие рекурсивного запроса основывается на понятие общего табличного выражения (CTE – Common Table Expression). Основная цель общего табличного выражения – упростить запросы за счет многократного использования одного и того же запроса. Формат общего табличного запроса прост и может быть представлен в следующем виде
Код: Выделить всё
WITH [ RECURSIVE ] <имя> [ ( <список столбцов> ) ]
AS (<запрос>)

Здесь <имя> - имя общего табличного выражения. <список столбцов> - список столбцов общего табличного выражения, который затем должен учитываться в запросе. Опция RECURSIVE призвана указывать, что CTE будет использоваться рекурсивно. Не все СУБД реализовали эту опцию, хотя сам механизм рекурсивности, разумеется, реализован. Я пока нашел эту возможность только у SQL Server (начиная с 2005).
Простой пример
Код: Выделить всё
WITH CTE_1 (ФИО,Оценки)
AS (SELECT a.ФИО, b.Оценка FROM Студенты a INNER JOIN Оценки b
ON a.id=b.id_студента)

SELECT AVG(Оценки) FROM CTE_1


Продолжение следует...
Последний раз редактировалось Vladislav_133 25 мар 2009, 22:46, всего редактировалось 1 раз.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение Vladislav_133 25 мар 2009, 21:13

Общие табличные выражения, продолжение.

Самое замечательное в общих табличных выражениях - это возможность рекурсивных запросов. Рассмотрим следующую незамысловатую таблицу {id,id_1,Текст_сообщения}. Эту таблицу можно интерпретировать в качестве хранилища для форума, в котором каждое сообщение может породить целую ветку других сообщений. id - первичный ключ, id_1 - внешний ключ (id_1=0 - корневое сообщение). Таблица, таким образом, ссылается на самую себя. Попробуйте обычными средствами получить сообщения произвольной ветки, произвольной глубины. С помощью общих табличных выражений это делается весьма просто.
Ниже представлен пример рекурсивного поиска всех сообщений, начиная с сообщения с идентификатором 3. В табличное выражение введен столбец глубина, показывающий уровень рекурсии. Данный столбец чрезвычайно полезен. Если переписать запрос как SELECT * FROM cte WHERE глубина=4, то получим все сообщения, глубина рекурсии которых по отношению к начальному уровню корневого сообщения равна 4.

Код: Выделить всё
WITH cte (id, id_1, Текст_сообщения, глубина)
AS
(
  SELECT a.id, a.id_1, a.Текст_сообщения, 0
  FROM table1 a
  WHERE id=3
  UNION ALL
  SELECT a.id, a.id_1, a. Текст_сообщения, глубина+1
  FROM table1 a
  INNER JOIN
  cte b
  ON b.id=a.id_1
)

SELECT * FROM cte


Общие табличные выражение по своим возможностям очень напоминают представления. Однако в представлениях изначально не была заложена возможность рекурсивного обращения. С другой стороны общие табличные выражения не являются объектами базы данных, а существуют только в программном коде на стороне сервера. По своей функциональности они представляют собой макро-шаблоны, которые могут использоваться в реальных запросах.
Аватара пользователя
Vladislav_133
Elite
 
Сообщения: 1386
Зарегистрирован: 13 дек 2008, 18:08
Полное имя: П.В.Ю.

Re: Особенности SQL

Сообщение alekam 17 июл 2009, 15:58

xdsl писал(а):Среди студентов, даже старших курсов, практически не наблюдается критического подхода к формированию sql-запросов. Лишь-бы работало. Это печально и с этим надо как-то бороться.
Я думаю, это можно решать только добавлением ограничений по времени выполнения варианта решения задачи, как на многих олимпиадах..

xdsl писал(а):Если на каждую задачу писать библиотеку расширений, можно стать профессиональным разработчиком библиотек расширений, но задачу так и не решить. В свою время для mysql4.0 писал на Си функцию-расширение, аналог которой официально появился только в mysql4.1 (group_concat). Куча времени на отладку, множество трудноуловимых ошибок. Отладил и активно применял, пока не перешел на mysql5.0. Однако времени на разработку этой функции потратил безмерно.
А долго вы использовали это расширение прежде чем перейти на mysql5? Иногда есть смысл в написании расширения. Иногда даже есть смысл даже в своем форке продукта. Гугл например для своих целей использует сильно доработанный своими силами mysql..

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

xdsl писал(а):С другой стороны, потребуется сменить сервер БД - умаетесь переводить хранимые процедуры на другой диалект.

Часто даже если вы не используете хранимые процедуры, переход на другую БД будет связан с проблемами (различия в синтаксисе, разные стратегии оптимизации запросов). Проблема тут в другом. При использовании хранимых процедур в веб-приложениях, у вас появляется еще одно звено которое надо дополнительно тестировать. Повышаются требования к специалистам, развивающим систему - в данном случае ко всему прочему они должны еще уметь работать с хранимыми процедурами. Другое дело если у вас БД способна отдавать результаты запросов в XML - так на сервере фактически можно обойтись без ЯП )
alekam
 
Сообщения: 46
Зарегистрирован: 23 дек 2008, 14:36
Полное имя: A.K.

Re: Особенности SQL

Сообщение xdsl 27 авг 2009, 11:52

alekam писал(а):
xdsl писал(а):Среди студентов, даже старших курсов, практически не наблюдается критического подхода к формированию sql-запросов. Лишь-бы работало. Это печально и с этим надо как-то бороться.
Я думаю, это можно решать только добавлением ограничений по времени выполнения варианта решения задачи, как на многих олимпиадах.

Тогда исходные данные для задачи должны находится в нескольких таблицах по миллиону строк, либо в десятках-сотнях связанных таблиц по десятку тысяч строк на каждую. Реальная промышленная задача. Понятно, что такой набор данных сложно сгенерировать адекватным учебным задачам. Наихудший вариант - сгенерировать случайным образом. Наилучший вариант - взять готовую, заполненную реальными данными БД. Тут всплывают проблемы конфиденциальности, которые иногда становятся непреодолимыми. В принципе, у меня есть одна такая табличка (уже с миллионом записей), конфиденциальность которой относительно невелика, а именно - журнал посещаемости веб-портала ШГПИ (https://shgpi.edu.ru/forum/viewtopic.php?f=19&t=127). Есть и реалтаймовские статистические обработчики на нее (http://shgpi.edu.ru/stats/). Заинтересованным преподавателям готов сделать выгрузку данных из нее для использования в учебном процессе.
xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.

Re: Особенности SQL

Сообщение xdsl 27 авг 2009, 12:27

xdsl
 
Сообщения: 1236
Зарегистрирован: 09 дек 2008, 05:16
Откуда: ВЦ ШГПИ
Полное имя: Слинкин Д.А.


Вернуться в Базы данных

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 0