Динамический дизайн таблицы (общая таблица поиска), нужен хороший запрос, чтобы получить значения - PullRequest
1 голос
/ 12 января 2011

sql2005

Это мой упрощенный пример: (на самом деле здесь более 40 таблиц, я только показал 2)

Я получил таблицу с именем tb_modules, с 3 столбцами (id, описание, имя таблицы как varchar):

 1, UserType, tb_usertype
 2, Religion, tb_religion

(последний столбец фактически является именем другой таблицы)

Я получил другую таблицу, которая выглядит следующим образом: tb_value (columns: id, tb_modules_ID, usertype_OR_religion_ID) значения:

 1111, 1, 45
 1112, 1, 55
 1113, 2, 123
 1114, 2, 234

так что я имею в виду 45, 55, 123, 234 - это ИД типа религии ИЛИ (45, 55, тип пользователя, 123, 234 ИД религии)

Не судите, я не проектировал базу данных

Вопрос Как мне сделать выборку, показывающую * из tb_value плюс один столбец Этот один столбец будет TITLE из tb_usertype или RELIGIONNAME из tb_religionТаблица

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

Кто-нибудь лучше идея?

Ответы [ 8 ]

2 голосов
/ 20 января 2011

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

-- Query 1
select tb_value.*,tb_religion.religion_name as ANY_DESCRIPTION
  from tb_value 
  JOIN tb_religion on tb_value.ANY_KIND_OF_ID = tb_religion.id
 WHERE tb_value.module_id = 2
-- combine it with...
UNION ALL
-- ...Query 2
select tb_value.*,tb_religion.title as ANY_DESCRIPTION
  from tb_value 
  JOIN tb_userType on tb_value.ANY_KIND_OF_ID = tb_userType.id
 WHERE tb_value.module_id = 1
-- combine it with...
UNION ALL
-- ...Query 3
select  lather, rinse, repeat for 40 tables!

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

2 голосов
/ 22 января 2011

Чтобы сделать это динамически, вам нужно создать SQL-оператор, который выглядит следующим образом

select tb_value.*, tb_usertype.title as Descr
from tb_value
    inner join tb_usertype
        on tb_value.extid = tb_usertype.id
where tb_value.tb_module_id = 1
union all
select tb_value.*, tb_religion.religionname as Descr
from tb_value
    inner join tb_religion
        on tb_value.extid = tb_religion.id
where tb_value.tb_module_id = 2
-- union 40 other tables

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

Если у вас есть имя поля для использования в tb_module, вы можете создать представление, которое делает то, что вы хотите. И вы можете добавить триггер к таблице tb_modules, который изменяет представление всякий раз, когда изменяется tb_modules. Таким образом, вам не нужно использовать динамический sql от клиента при выполнении запросов. Единственное, о чем вам нужно беспокоиться, это то, что таблицу необходимо создать в БД, прежде чем вы добавите новую строку в tb_modules

Редактировать 1 Конечно, код в триггере должен динамически создавать оператор alter view.

Редактировать 2 Вам также необходимо иметь поле с информацией о том, какие столбцы в tb_usertype, tb_religion и т. Д. Присоединяются к tb_value.extid (usertype_OR_religion_ID). Или вы можете предположить, что поле всегда будет называться id

Редактировать 3 Вот как вы можете построить триггер для tb_module, который изменяет представление v_values. Я добавил fieldname в качестве столбца в tb_modules, и я предполагаю, что поле id в связанных таблицах называется id.

create trigger tb_modules_change on tb_modules after insert, delete, update
as 

declare @sql nvarchar(max)
declare @moduleid int
declare @tablename varchar(50)
declare @fieldname varchar(50)

set @sql = 'alter view v_value as '

declare mcur cursor for
  select id, tablename, fieldname
  from tb_modules

open mcur
fetch next from mcur into @moduleid, @tablename, @fieldname
while @@FETCH_STATUS = 0
begin
    set @sql = @sql + 'select tb_value.*, '+@tablename+'.'+@fieldname+' '+
                      'from tb_value '+
                      'inner join '+@tablename+' '+
                        'on tb_value.extid = '+@tablename+'.id '+
                      'where tb_value.tb_module_id = '+cast(@moduleid as varchar(10))  
  fetch next from mcur into @moduleid, @tablename, @fieldname

  if @@FETCH_STATUS = 0
  begin
    set @sql = @sql + ' union all '
  end
end
close mcur
deallocate mcur

exec sp_executesql @sql
2 голосов
/ 19 января 2011

В начале у нас есть это - что довольно грязно.

alt text

Чтобы немного убрать, я добавляю два представления и синоним:

create view v_Value as
select
      ID                      as ValueID
    , tb_modules_ID           as ModuleID
    , usertype_OR_religion_ID as RemoteID
from tb_value ;
go

create view v_Religion as
select
      ID
    , ReligionName as Title
from tb_religion ;
go

create synonym v_UserType for tb_UserType ;
go

И теперь модель выглядит как

alt text

Теперь проще написать запрос

;
with 
q_mod as (
    select
          m.ID                          as ModuleID
        , coalesce(x1.ID    , x2.ID)    as RemoteID
        , coalesce(x1.Title , x2.Title) as Title
        , m.Description                 as ModuleType
    from      tb_Modules as m
    left join v_UserType as x1 on m.TableName = 'tb_UserType'
    left join v_Religion as x2 on m.TableName = 'tb_Religion'
)
select
      a.ModuleID
    , v.ValueID
    , a.RemoteID
    , a.ModuleType
    , a.Title
from q_mod   as a
join v_Value as v on (v.ModuleID = a.ModuleID and v.RemoteID = a.RemoteID) ;

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

EDIT

Для создания динамического sql (или запроса на уровне приложения)

Изменить строки 6 и 7, x-index равен tb_modules.id

coalesce(x1. , x2. , x3. ..) 

Добавить строки в левое соединение (ниже строки 11)

left join v_SomeName as x3  on m.TableName = 'tb_SomeName'

SomeName равен tb_modules.description, а x-index соответствует tb_modules.id

РЕДАКТИРОВАТЬ 2

Возможно, проще всего было бы упаковать вышеуказанный запрос в представлениеи затем каждый раз, когда схема динамически изменяется, создайте и запустите ALTER VIEW.Таким образом, запрос не изменится с точки зрения приложения.

1 голос
/ 24 января 2011

Полагаю, вам нужно что-то вроде этого:

Добавление таблиц и одной строки на таблицу в tb_modules - это просто.

SET NOCOUNT ON
if OBJECT_ID('tb_modules') > 0 drop table tb_modules;
if OBJECT_ID('tb_value') > 0 drop table tb_value;
if OBJECT_ID('tb_usertype') > 0 drop table tb_usertype;
if OBJECT_ID('tb_religion') > 0 drop table tb_religion;
go

create table dbo.tb_modules (
    id int, 
    description varchar(20), 
    tablename varchar(255)
);

insert into tb_modules values (  1, 'UserType', 'tb_usertype');
insert into tb_modules values (  2, 'Religion', 'tb_religion');

create table dbo.tb_value(
    id int,
    tb_modules_ID int,
    usertype_OR_religion_ID int
);
insert into tb_value values (   1111, 1, 45);
insert into tb_value values (   1112, 1, 55);
insert into tb_value values (   1113, 2, 123);
insert into tb_value values (   1114, 2, 234);

create table dbo.tb_usertype(
    id int,
    UserType varchar(30)
);

insert into tb_usertype values ( 45, 'User_type_45');
insert into tb_usertype values ( 55, 'User_type_55');

create table dbo.tb_religion(
    id int,
    Religion varchar(30)
);

insert into tb_religion values ( 123, 'Religion_123');
insert into tb_religion values ( 234, 'Religion_234');

-- start of query

declare @sql varchar(max) = null

Select @sql = case when @sql is null then '          ' else @sql + char(10) + 'union all '  end 
    + 'Select ' + str(id) + ' type, id, ' + description + ' description from '  + tablename  from   tb_modules 

set @sql = 'select  v.id, tb_modules_ID , usertype_OR_religion_ID , t.description
from tb_value v
    join ( ' + @sql + ') as t
on v.tb_modules_ID = t.type and v.usertype_OR_religion_ID = t.id
'

Print @sql
exec( @sql)
1 голос
/ 22 января 2011

Сначала выньте неработающего за спину и избавьте его от страданий.Они причиняют людям боль.

  • Из-за их некомпетентности, каждый раз, когда вы добавляете строку в модуль, вы должны изменять каждый запрос, который его использует.Хорошо для www.dailywtf.com.

  • У вас также нет ссылочной целостности, поскольку вы не можете определить FK в столбце this_or_that.Ваши данные подвергаются, вероятно, «коду», написанному тем же самым дизайнером.Без сомнения, вы знаете, что именно здесь создаются тупики.

  • Что это «суждение», то есть, чтобы вы понимали серьезность неразобранности и могли оправдатьзаменив его своим менеджерам.

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

  • «Динамический» зарезервирован для баз данных, невозможен для плоских мух.

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

Безопасные запросы на будущее <pre>CREATE VIEW UserReligion_vw AS SELECT [XxxxId] = id, -- replace Xxxx [ReligionId] = usertype_OR_religion_ID FROM tb_value WHERE tb_modules_ID = 1</p> <p>CREATE VIEW UserReligion_vw AS SELECT [XxxxId] = id, [ReligionId] = usertype_OR_religion_ID FROM tb_value WHERE tb_modules_ID = 2 С этого момента убедитесь, что все запросы, в настоящее время использующие undesign, изменены, чтобы вместо них использовался правильный View,Не используйте представления для обновления / удаления / вставки.

Ответ

Хорошо, теперь основной вопрос.Я могу думать о других подходах, но этот является лучшим.Вы заявили, что хотите, чтобы третий столбец также представлял собой ненормализованный кусочек куриных экскрементов и запас Title для [EITHER_Religion_OR_UserType_OR_This_OR_That].Правильно, значит, вы также учите пользователя запутываться;когда число модулей не вырастет, им будет очень интересно выяснить, что содержит столбец.Да, проблема всегда усложняется. <pre> SELECT [XxxxId] = id, [Whatever] = CASE tb_modules_ID WHEN 1 THEN ( SELECT name -- title, whatever FROM tb_religion WHERE id = V.usertype_OR_religion_ID ) WHEN 2 THEN ( SELECT name -- title, whatever FROM tb_usertype WHERE id = V.usertype_OR_religion_ID ) ELSE "(UnknownModule)" -- do not remove the brackets END FROM tb_value V WHERE conditions... -- you need something here Это называется коррелированный скалярный подзапрос .

  • Работает на любой версии Sybase начиная с 4.9.2 без ограничений.И SQL 2005 (последний раз я смотрел, во всяком случае, август 2009).Но на MS вы получите StackTrace, если объем tb_value большой, поэтому убедитесь, что в предложении WHERE есть некоторые условия.

  • Но MS сломал серверс их «новой» кодовой строкой 2008 года, поэтому она не работает при любых обстоятельствах (чем хуже ваши искаженные файлы, тем меньше вероятность того, что они будут работать; чем лучше ваша база данных, тем больше вероятность того, что она будет работать).Вот почему некоторые люди с РС молятся каждый день за следующий пакет услуг, а другие никогда не посещают церковь.

1 голос
/ 19 января 2011

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

Правильное решение заключается в создании правильно спроектированной новой базы данных, переносе данных и удалении существующего проекта.Проблемы, с которыми вы столкнетесь с вашим текущим дизайном, будут только расти.Новым разработчикам будет сложнее освоить новую систему.Это будет подвержено ошибкам.Там не будет целостности данных (например, принудительно атрибут «Дата начала» для анализа в качестве даты).Пользовательские запросы будут рутиной, и так далее.В конце концов, вы попадете в тот день, когда типы информации, требуемой от системы, просто слишком сложно извлечь, учитывая текущий дизайн.

1 голос
/ 12 января 2011

Хм ... возможно, есть лучшие решения, но вот мои пять центов:

SELECT 
id,tb_modules_ID,usertype_OR_religion_ID,
COALESCE(
 (SELECT TITLE FROM tb_usertype WHERE Id = usertype_OR_religion_ID),
 (SELECT RELIGIONNAME FROM tb_religion WHERE Id = usertype_OR_religion_ID),
 'N/A'
) AS SourceTable
FROM tb_valuehere

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

0 голосов
/ 22 января 2011

Я думаю, что он предназначен для использования с динамическим sql.

Может быть, разбить каждую строку tb_value.tb_modules_ID на собственную временную таблицу, названную с именем tb_modules.tablename.

Затем иметьsp. Итерируйте временные таблицы, соответствующие вашему соглашению об именах (по префиксу или суффиксу), создайте sql и выполните ваше объединение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...