Как лучше всего выбрать минимальное значение из нескольких столбцов?

Учитывая следующую таблицу в SQL Server 2005:

ID   Col1   Col2   Col3
--   ----   ----   ----
1       3     34     76  
2      32    976     24
3       7    235      3
4     245      1    792

Как лучше всего написать запрос, который дает следующий результат (т.е. тот, который дает последний столбец - столбец, содержащий минимальные значения из Col1, Col2 и Col 3 для каждой строки)?

ID   Col1   Col2   Col3  TheMin
--   ----   ----   ----  ------
1       3     34     76       3
2      32    976     24      24
3       7    235      3       3
4     245      1    792       1

ОБНОВИТЬ:

Для пояснения (как я уже сказал в комментариях) в реальном сценарии база данных - правильно нормализованный. Эти столбцы «массива» находятся не в реальной таблице, а в результирующем наборе, который требуется в отчете. И новое требование состоит в том, что отчету также нужен столбец MinValue. Я не могу изменить базовый набор результатов и поэтому искал в T-SQL удобную карту «выхода из тюрьмы».

Я попробовал упомянутый ниже подход CASE, и он работает, хотя и немного громоздко. Это также сложнее, чем указано в ответах, потому что вам нужно учитывать тот факт, что в одной строке есть два минимальных значения.

В любом случае, я подумал, что опубликую свое текущее решение, которое с учетом моих ограничений работает довольно хорошо. Он использует оператор UNPIVOT:

with cte (ID, Col1, Col2, Col3)
as
(
    select ID, Col1, Col2, Col3
    from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
    select
        ID, min(Amount) as TheMin
    from 
        cte 
        UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
    group by ID
) as minValues
on cte.ID = minValues.ID

Сразу скажу, что не ожидаю, что это обеспечит лучшую производительность, но, учитывая обстоятельства (я не могу перепроектировать все запросы только для нового требования столбца MinValue), это довольно элегантный вариант «выйти из тюрьмы». карта".

IMHO авторское решение UNPIVOT превосходит другие ответы.

Joe Harris 11.11.2011 15:31

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

Patrick Honorez 27.08.2015 18:09
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
84
2
250 835
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Ответ принят как подходящий

Вероятно, будет много способов добиться этого. Я предлагаю использовать Case / When для этого. С 3 столбцами это не так уж и плохо.

Select Id,
       Case When Col1 < Col2 And Col1 < Col3 Then Col1
            When Col2 < Col1 And Col2 < Col3 Then Col2 
            Else Col3
            End As TheMin
From   YourTableNameHere

Это была моя первоначальная мысль. Но для настоящего запроса требуется 5 столбцов, и количество столбцов может увеличиваться. Таким образом, подход CASE становится немного громоздким. Но это действительно работает.

stucampbell 15.12.2008 16:41

Если количество столбцов может вырасти, вы определенно делаете это неправильно - см. Мой пост (разглагольствование о том, почему вам не следует настраивать схему БД таким образом :-).

paxdiablo 15.12.2008 16:49

Спасибо. Как я уже упоминал в другом комментарии. Я не запрашиваю реальные таблицы. Таблицы нормализованы правильно. Этот запрос является частью особенно сложного запроса и работает с промежуточными результатами из производных таблиц.

stucampbell 15.12.2008 16:57

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

Kev 15.12.2008 17:13

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

paxdiablo 16.12.2008 05:10

Дополнение к ответу от @Gmastros, поскольку я столкнулся с проблемой, что некоторые из столбцов имеют совпадающие данные, поэтому мне пришлось добавить знак =. Мои данные также могут иметь значение null, поэтому мне пришлось добавить оператор or, чтобы учесть это. Может быть, есть более простой способ сделать это, но я не нашел его за последние 6 месяцев, которые искал. Спасибо всем, кто здесь участвует. Выберите Id, CaseWhen (Col1 <= Col2 OR Col2 имеет значение NULL) And (Col1 <= Col3 OR Col3 is null) Then Col1 When (Col2 <= Col1 OR Col1 is null) And (Col2 <= Col3 OR Col3 is null) Then Col2 Else Col3 End As TheMin из YourTableNameHere

Chad Portman 22.09.2015 21:58

Этот ответ неверен, так как он не учитывает нули или столбцы с равными значениями. См. Комментарий от @ChadPortman

Joe 05.01.2016 19:15

Это грубая сила, но работает

 select case when col1 <= col2 and col1 <= col3 then col1
           case when col2 <= col1 and col2 <= col3 then col2
           case when col3 <= col1 and col3 <= col2 then col3
    as 'TheMin'
           end

from Table T

... потому что min () работает только с одним столбцом, а не с несколькими столбцами.

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

John Zabroski 07.12.2020 22:19

Лучший способ сделать это, вероятно, - это сделать это с помощью нет - странно, что люди настаивают на хранении своих данных таким образом, который требует "гимнастики" SQL для извлечения значимой информации, тогда как есть гораздо более простые способы достичь желаемого результата, если вы просто структурируйте свою схему немного лучше :-)

На мой взгляд, способ верно сделать это - иметь следующую таблицу:

ID    Col    Val
--    ---    ---
 1      1      3
 1      2     34
 1      3     76

 2      1     32
 2      2    976
 2      3     24

 3      1      7
 3      2    235
 3      3      3

 4      1    245
 4      2      1
 4      3    792

с ID/Col в качестве первичного ключа (и, возможно, Col в качестве дополнительного ключа, в зависимости от ваших потребностей). Тогда ваш запрос превратится в простой select min(val) from tbl, и вы по-прежнему сможете обрабатывать отдельные «старые столбцы» отдельно, используя where col = 2 в других ваших запросах. Это также позволяет легко расширять, если количество «старых столбцов» вырастет.

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


Однако, если по какой-то причине вы меняете эти столбцы не могу, я бы предложил использовать триггеры вставки и обновления и добавить столбец Другой, который эти триггеры устанавливают на минимум на Col1/2/3. Это сместит «стоимость» операции с выбора на обновление / вставку, которому она принадлежит - большинство таблиц базы данных, по моему опыту, читаются гораздо чаще, чем записываются, поэтому затраты на запись со временем становятся более эффективными.

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

ID   Col1   Col2   Col3   MinVal
--   ----   ----   ----   ------
 1      3     34     76        3
 2     32    976     24       24
 3      7    235      3        3
 4    245      1    792        1

Любой другой вариант, который должен принимать решения во время select, обычно является плохой идеей с точки зрения производительности, поскольку данные изменяются только при вставке / обновлении - добавление другого столбца занимает больше места в БД и будет немного медленнее для вставок и обновляет, но может быть много быстрее для выборок - предпочтительный подход должен зависеть от ваших приоритетов там, но, как указано, большинство таблиц читаются далеко чаще, чем они записываются.

Гм. Спасибо за обличительную речь. Реальная база данных нормализована правильно. Это был простой пример. Фактический запрос сложен, и 5 интересующих меня столбцов являются промежуточными результатами из производных таблиц.

stucampbell 15.12.2008 16:53

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

paxdiablo 15.12.2008 17:04

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

paxdiablo 15.12.2008 17:05

+1 за предложение триггера для сохранения исходной (если она ошибочна) структуры таблицы.

Scott Ferguson 11.12.2009 05:58

Что, если вы имеете дело с таблицей Hierarchy, присоединенной к ней самой?

Nathan Tregillus 23.11.2010 21:01

Привет, объясните, пожалуйста, как таблица с одним ключом и 5 числовыми свойствами не нормализована должным образом. Кажется, что это точно соответствует определению, по крайней мере, через третью нормальную форму.

nomen 18.04.2019 21:35

Если вы можете создать хранимую процедуру, она может принимать массив значений, и вы можете просто вызвать это.

В Oracle есть функция LEAST (), которая делает именно то, что вы хотите.

Kev 15.12.2008 16:46

Спасибо, что втерлись в это :) Я не могу поверить, что у SQL Server нет эквивалента!

stucampbell 15.12.2008 17:05

Я даже собирался сказать: «Эй, в моем любимом pgsql этого тоже нет», но на самом деле он есть. ;) Саму функцию написать было бы несложно.

Kev 15.12.2008 17:09

О, кроме того, что T-SQL даже не поддерживает массивы (???) Ну, я думаю, у вас может быть функция с пятью параметрами, и если вам нужно больше, просто расширите ее ...

Kev 15.12.2008 17:11
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from   tbl_example

Это дубликат ответа G Mastros, поэтому, если вам интересно: я думаю, отсюда и происходит голосование против.

Tomalak 15.12.2008 16:58

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

Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From   YourTable T
       Inner Join (
         Select A.Id, Min(A.Col1) As TheMin
         From   (
                Select Id, Col1
                From   YourTable

                Union All

                Select Id, Col2
                From   YourTable

                Union All

                Select Id, Col3
                From   YourTable
                ) As A
         Group By A.Id
       ) As A
       On T.Id = A.Id

Это работает, но при увеличении количества строк производительность будет снижаться.

Tomalak 15.12.2008 16:56

Спасибо. Да, это работает. Как говорит Томалак, в моем запросе realword это было бы довольно неприятно для производительности. Но +1 за усилие. :)

stucampbell 15.12.2008 17:03

Если вы используете SQL 2005, вы можете сделать что-нибудь вроде этого:

;WITH    res
          AS ( SELECT   t.YourID ,
                        CAST(( SELECT   Col1 AS c01 ,
                                        Col2 AS c02 ,
                                        Col3 AS c03 ,
                                        Col4 AS c04 ,
                                        Col5 AS c05
                               FROM     YourTable AS cols
                               WHERE    YourID = t.YourID
                             FOR
                               XML AUTO ,
                                   ELEMENTS
                             ) AS XML) AS colslist
               FROM     YourTable AS t
             )
    SELECT  YourID ,
            colslist.query('for $c in //cols return min(data($c/*))').value('.',
                                            'real') AS YourMin ,
            colslist.query('for $c in //cols return avg(data($c/*))').value('.',
                                            'real') AS YourAvg ,
            colslist.query('for $c in //cols return max(data($c/*))').value('.',
                                            'real') AS YourMax
    FROM    res

Так в таком количестве операторов не заблудишься :)

Однако этот вариант может быть медленнее, чем другой вариант.

Это твой выбор...

Как я уже сказал, это может быть медленным, но если у вас слишком много столбцов (очевидно, из-за очень плохого дизайна БД!), Это может стоить использования (по крайней мере, для AVG). Вы не дали мне никаких подсказок, хороший это святая корова или плохой :) Может, вам стоит использовать голосование за / против, чтобы помочь мне разобраться.

leoinfo 16.12.2008 04:50

Это было не совсем хорошо или плохо;). Я не специалист по базам данных, поэтому я просто сказал «святая корова», потому что казалось, что на этот вопрос есть тривиальный ответ. Думаю, это хороший вариант, так как вам удалось предоставить гибкое, расширяемое решение проблемы!

dreamlax 16.12.2008 06:39

Оба этот вопрос И этот вопрос попробуй на это ответить.

Напомним, что в Oracle есть встроенная функция для этого, с Sql Server вы застряли либо при определении определяемой пользователем функции, либо при использовании операторов case.

Небольшой поворот в запросе на объединение:

DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)

INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)

SELECT
    ID,
    Col1,
    Col2,
    Col3,
    (
        SELECT MIN(T.Col)
        FROM
        (
            SELECT Foo.Col1 AS Col UNION ALL
            SELECT Foo.Col2 AS Col UNION ALL
            SELECT Foo.Col3 AS Col 
        ) AS T
    ) AS TheMin
FROM
    @Foo AS Foo

Если бы столбцы были целыми числами, как в вашем примере, я бы создал функцию:

create function f_min_int(@a as int, @b as int) 
returns int
as
begin
    return case when @a < @b then @a else coalesce(@b,@a) end
end

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

select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)

если у вас 5 столбцов, то приведенное выше становится

select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)

Учитывая до смешного плохую производительность скалярных функций в MSSQL, я чувствую себя обязанным посоветовать против этого подхода. Если вы пойдете по этому пути, то хотя бы напишите функцию, которая принимает все 5 столбцов в качестве параметров сразу. Это все равно будет плохо, но, по крайней мере, немного менее плохо = /

deroby 10.04.2015 00:20

Рекурсия снизит производительность. Но это будет соответствовать требованиям.

Tino Jose Thannippara 10.11.2017 14:09

Если вы знаете, какие значения ищете, обычно это код состояния, может быть полезно следующее:

select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS

В MySQL используйте это:

select least(col1, col2, col3) FROM yourtable

Возможно, это не оператор SQL.

Tino Jose Thannippara 10.11.2017 14:09

но в некоторых случаях это так. для тех, это замечательный ответ

Kirby 22.06.2018 22:51

Доступно в Postgres с 8.1: postgresql.org/docs/8.1/functions-conditional.html#AEN12704

swrobel 16.01.2020 05:55

Это нестандартное расширение SQL поддерживается практически всеми базами данных, кроме сервера Microsoft SQL.

Mikko Rantalainen 09.10.2020 13:33
LEAST работает с последней версией управляемых экземпляров Microsoft SQL Server ~ 12 дней назад. reddit.com/r/SQLServer/comments/k0dj2r/…
John Zabroski 07.12.2020 04:51

Также работает в Informix, спасибо за ответ.

Uwe Ziegenhagen 11.12.2020 14:35

Вопрос в том, что SQL Server не имеет этой функции.

Colin 't Hart 16.02.2021 16:58

Вы можете использовать подход «грубой силы» с изюминкой:

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
    WHEN                  Col2 <= Col3 THEN Col2
    ELSE                                    Col3
END AS [Min Value] FROM [Your Table]

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

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
    WHEN                  Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
    WHEN                                   Col3 <= Col4 AND Col3 <= Col5 THEN Col3
    WHEN                                                    Col4 <= Col5 THEN Col4
    ELSE                                                                      Col5
END AS [Min Value] FROM [Your Table]

Обратите внимание, что если есть связь между двумя или более столбцами, то <= гарантирует, что мы выйдем из оператора CASE как можно раньше.

Вместо этого используйте <=, иначе будет использоваться последнее совпадающее минимальное значение вместо первого.

chezy525 04.11.2015 22:22

Использование CROSS APPLY:

SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) AS MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A

SQL Fiddle

Выглядит интересно, но я не могу заставить это работать. Не могли бы вы немного пояснить? Спасибо

Patrick Honorez 21.08.2015 14:00

@iDevlop Я вставил скрипт SQL в свой ответ

Nizam 21.08.2015 15:05

Я не знал, что это скалярная функция. Похоже, ваш ответ работает и без cross apply. Добавляет ли это ценность / производительность? stackoverflow.com/a/14712024/78522

Patrick Honorez 27.08.2015 15:48

@iDevlop Если он не обеспечивает производительности, он увеличивает читаемость. Например, я мог бы использовать что-то вроде where MinValue > 10, чего я не смог бы обойтись без CROSS APPLY.

Nizam 27.08.2015 16:52

действительно, тем временем у меня была возможность понять преимущества его "повторного использования". Спасибо. Сегодня я узнал 2 вещи ;-)

Patrick Honorez 27.08.2015 18:04

Спасибо с 2017 года! Мне просто нужно немного поправить это.

Mr.J 29.09.2017 08:37

@PatrickHonorez с CROSS APPLY вы можете ссылаться на результат внутри предложения WHERE и после него (GROUP BY, HAVING, WINDOW, SELECT и ORDER BY ... вы даже можете ссылаться на него внутри JOIN). Если используется внутри SELECT, вы можете получить к нему доступ в ORDER BY.

Salman A 05.12.2018 22:27

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

По сути, это похоже на запрос на объединение, требуется такое же количество проходов, но он может быть более эффективным (исходя из опыта, но потребует тестирования). В этом случае эффективность не была проблемой (8000 записей). Можно было индексировать и т. д.

--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
    drop table #temp1
if object_id('tempdb..#temp2') is not null
    drop table #temp2

select r.recordid ,  r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r 
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
 group by  r.recordid, recorddate, i.ReceivedDate,
 r.ReferenceNumber, i.InventionTitle



select recordid, recorddate [min date]
into #temp2
from #temp1

update #temp2
set [min date] = ReceivedDate 
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and  t1.ReceivedDate > '2001-01-01'

update #temp2 
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and  t1.[Min File Upload] > '2001-01-01'

update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'


select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
SELECT ID, Col1, Col2, Col3, 
    (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table

Спасибо за улов. Я пропустил этот ярлык. На самом деле я не знаю и не имею возможности проверить это. В дальнейшем будет более тщательно проверять теги.

dsz 06.01.2016 01:08

Несомненно, более элегантное решение - не уверен, почему оно не набирает больше голосов.

jwolf 24.06.2019 03:01

Для встроенных вычислений max / min это, безусловно, лучший способ сделать это.

Saxman 02.09.2020 20:36

Замечательное решение.

Phani 11.12.2020 02:37

Для нескольких столбцов лучше всего использовать оператор CASE, однако для двух числовых столбцов i и j вы можете использовать простую математику:

min (i, j) = (i + j) / 2 - абс (i-j) / 2

Эту формулу можно использовать для получения минимального значения нескольких столбцов, но это действительно беспорядочное прошлое 2, min (i, j, k) будет min (i, min (j, k))

SELECT [ID],
            (
                SELECT MIN([value].[MinValue])
                FROM
                (
                    VALUES
                        ([Col1]),
                        ([Col1]),
                        ([Col2]),
                        ([Col3])
                ) AS [value] ([MinValue])
           ) AS [MinValue]
FROM Table;

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


Я пришел из страны SAP ASE 16.0, и мне нужно было только взглянуть на статистику определенных данных, которые, IMHO, корректно хранятся в разных столбцах одной строки (они представляют разное время - когда прибытие чего-то планировалось, что ожидалось, когда действие началось и, наконец, сколько было фактического времени). Таким образом, я перенес столбцы в строки временной таблицы и, как обычно, выполнил свой запрос.

N.B. Не впереди универсального решения!

CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)

INSERT INTO #tempTable 
  SELECT ID, 'Col1', Col1
    FROM sourceTable
   WHERE Col1 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col2', Col2
    FROM sourceTable
   WHERE Col2 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col3', Col3
    FROM sourceTable
   WHERE Col3 IS NOT NULL

SELECT ID
     , min(dataValue) AS 'Min'
     , max(dataValue) AS 'Max'
     , max(dataValue) - min(dataValue) AS 'Diff' 
  FROM #tempTable 
  GROUP BY ID

Это заняло около 30 секунд на исходном наборе из 630000 строк и использовало только индексные данные, поэтому не то, что нужно запускать в критичном по времени процессе, но для таких вещей, как разовая проверка данных или отчет на конец дня, вы можете быть хорошо (но проверьте это со своими коллегами или начальством, пожалуйста!). Основным преимуществом этого стиля для меня было то, что я мог легко использовать больше / меньше столбцов и изменять группировку, фильтрацию и т. д., Особенно после копирования данных.

Дополнительные данные (columnName, maxes, ...) должны были помочь мне в моем поиске, поэтому они могут вам не понадобиться; Я оставил их здесь, возможно, чтобы зажечь какие-нибудь идеи :-).

Другие вопросы по теме