Столбец содержит символ из другого столбца - PullRequest
0 голосов
/ 11 сентября 2018

Есть ли простой способ получить условие "где столбец A имеет символ из символа столбца B"? В основном у меня есть колонка, в которой есть буквы для каждого дня рабочей недели; MTWHF. Мне нужно объединить две записи, где совпадают дни, что в основном происходит, если буквы совпадают в строках.

----------------
| ID | MetDays |
----------------
| 1  | 'MWF'   |
| 2  | 'TH'    |
| 3  | 'M'     |
| 4  | 'T'     |
| 5  | 'WHF'   |
----------------

SQL-запрос будет выглядеть примерно так:

SELECT MyTableA.ID AS IDa, MyTableB.ID AS IDb
FROM MyTable AS MyTableA
  JOIN MyTable AS MyTableB
    ON MyTableA.MetDays ???? MyTableB.MetDays

В этом случае у меня будет JOIN между;

-------------
| IDa | IDb |
-------------
| 1   | 3   |
| 1   | 5   |
| 2   | 4   |
| 2   | 5   |
| (reverse) |
| 3   | 1   |
| 4   | 2   |
| 5   | 1   |
| 5   | 2   |
-------------

Ответы [ 3 ]

0 голосов
/ 11 сентября 2018
create table MyTable ( ID int, MetDays varchar(5) )

insert into MyTable ( ID, MetDays ) values
( 1, 'MWF' ),
( 2, 'TH'  ),
( 3, 'M'   ),
( 4, 'T'   ),
( 5, 'WHF' )


;with 

  -- Create a table of the 5 characters.
  -- You might want to make this a permanent table.

  DayList as
  (       select 'M' as aDay 
    union select 'T' 
    union select 'W' 
    union select 'H' 
    union select 'F' ),

  -- Join MyTable with this list.
  -- The result will be one record for each letter in each row
  -- ID aDay
  --  1  M
  --  1  W
  --  1  F
  -- and so on

  MetDayList as
  ( select ID, aDay 
    from MyTable
    join DayList 
    on MyTable.MetDays like '%' + aDay + '%' )

  -- Self join this table

  select distinct A.ID as IDa, B.ID as IDb 
  from MetDayList A
  join MetDayList B 
  on A.ID <> B.ID
  and A.aDay=B.aDay 
  order by IDa, IDb
0 голосов
/ 11 сентября 2018

Следующий подход обходится без like и использует битовые маски.

CharIndex используется для определения наличия в строке конкретной буквы. Возвращает либо одну позицию символа, либо ноль. Sign используется, чтобы сложить все положительные значения до 1 при прохождении 0 через. При использовании подходящих множителей (1, 2, 4, 8, 16) совпадения объединяются в битовую маску, причем младший значащий бит (LSB) равен понедельнику, ....

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

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

Для повышения производительности битовые маски могут храниться в постоянном вычисляемом столбце. Обратите внимание, что индексация вряд ли будет очень полезна.

-- Sample data.
declare @Meetings as Table ( Id Int Identity, MetDays VarChar(5) );
insert into @Meetings ( MetDays ) values
  ( 'MWF' ), ( 'TH' ), ( 'M' ), ( 'T' ), ( 'WHF' );
select * from @Meetings;

-- Play with the data.
declare @BusyDays as VarChar(5) = 'MWF'; -- I'm busy these days.
with
  BusyDays as ( -- Build a bitmask of the days that I'm busy.
    select @BusyDays as BusyDays,
      Sign( CharIndex( 'M', @BusyDays ) ) +
      Sign( CharIndex( 'T', @BusyDays ) ) * 2 +
      Sign( CharIndex( 'W', @BusyDays ) ) * 4 +
      Sign( CharIndex( 'H', @BusyDays ) ) * 8 +
      Sign( CharIndex( 'F', @BusyDays ) ) * 16 as BusyDaysBitMask ),
  MetDays as ( -- Build a bitmask for the days each meeting occurs.
    select MetDays,
      Sign( CharIndex( 'M', MetDays ) ) +
      Sign( CharIndex( 'T', MetDays ) ) * 2 +
      Sign( CharIndex( 'W', MetDays ) ) * 4 +
      Sign( CharIndex( 'H', MetDays ) ) * 8 +
      Sign( CharIndex( 'F', MetDays ) ) * 16 as MetDaysBitMask
      from @Meetings )
  select MD.MetDays, MD.MetDaysBitMask, BD.BusyDays, BD.BusyDaysBitMask,
    -- Bitwise AND of day bitmasks.  Zero means no days in common.
    MD.MetDaysBitMask & BD.BusyDaysBitMask as CollisionDaysBitMask,
    CD.CollisionDays
    from BusyDays as BD cross join
      MetDays as MD cross apply
      ( select -- Convert the collision bitmask back to a set of day letters.
          case when MD.MetDaysBitMask & BD.BusyDaysBitMask & 1 != 0 then 'M' else '' end +
          case when MD.MetDaysBitMask & BD.BusyDaysBitMask & 2 != 0 then 'T' else '' end +
          case when MD.MetDaysBitMask & BD.BusyDaysBitMask & 4 != 0 then 'W' else '' end +
          case when MD.MetDaysBitMask & BD.BusyDaysBitMask & 8 != 0 then 'H' else '' end +
          case when MD.MetDaysBitMask & BD.BusyDaysBitMask & 16 != 0 then 'F' else '' end as
            CollisionDays ) CD;

Для сравнения встреч по общим дням:

with
  MetDays as ( -- Build a bitmask for the days each meeting occurs.
    select MetDays,
      Sign( CharIndex( 'M', MetDays ) ) +
      Sign( CharIndex( 'T', MetDays ) ) * 2 +
      Sign( CharIndex( 'W', MetDays ) ) * 4 +
      Sign( CharIndex( 'H', MetDays ) ) * 8 +
      Sign( CharIndex( 'F', MetDays ) ) * 16 as MetDaysBitMask
      from @Meetings )
  select MDL.MetDays as 'MetDays Left', MDL.MetDaysBitMask as 'MetDaysBitMask Left',
    MDR.MetDays as 'MetDays Right', MDR.MetDaysBitMask as 'MetDaysBitMask Right',
    -- Bitwise AND of day bitmasks.  Zero means no days in common.
    MDL.MetDaysBitMask & MDR.MetDaysBitMask as CollisionDaysBitMask,
    CD.CollisionDays
    from MetDays as MDL cross join
      MetDays as MDR cross apply
      ( select -- Convert the collision bitmask back to a set of day letters.
          case when MDL.MetDaysBitMask & MDR.MetDaysBitMask & 1 != 0 then 'M' else '' end +
          case when MDL.MetDaysBitMask & MDR.MetDaysBitMask & 2 != 0 then 'T' else '' end +
          case when MDL.MetDaysBitMask & MDR.MetDaysBitMask & 4 != 0 then 'W' else '' end +
          case when MDL.MetDaysBitMask & MDR.MetDaysBitMask & 8 != 0 then 'H' else '' end +
          case when MDL.MetDaysBitMask & MDR.MetDaysBitMask & 16 != 0 then 'F' else '' end as
            CollisionDays ) CD;

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

0 голосов
/ 11 сентября 2018

Вот один из подходов, разделяющих дни на 5 отдельных столбцов с использованием substring:

select t1.id as IDa, t2.id as IDb
from mytable t1, mytable t2
where t1.id != t2.id and 
    (   (t1.metdays like '%' + substring(t2.metdays,1,1) + '%' and substring(t2.metdays,1,1) != '')
     or (t1.metdays like '%' + substring(t2.metdays,2,1) + '%' and substring(t2.metdays,2,1) != '')
     or (t1.metdays like '%' + substring(t2.metdays,3,1) + '%' and substring(t2.metdays,3,1) != '')
     or (t1.metdays like '%' + substring(t2.metdays,4,1) + '%' and substring(t2.metdays,4,1) != '')
     or (t1.metdays like '%' + substring(t2.metdays,5,1) + '%' and substring(t2.metdays,5,1) != '')
    )
order by t1.id, t2.id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...