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


8

Я работаю с системой закупок и выставления счетов за продукты в MS Access 2013 и пытаюсь создать SQL-запрос, который будет возвращать самую последнюю цену покупки для каждого отдельного продукта.

Вот схема таблиц, с которыми я работаю: Таблицы в базе данных MS Access

Мое понимание SQL очень простое, и я попробовал следующий (неправильный) запрос, в надежде, что он вернет только одну запись на элемент (из-за DISTINCTоператора) и что он вернет только самую последнюю покупку (так как я сделал ORDER BY [Invoice Date] DESC)

SELECT DISTINCT ([Food items].Item), 
    [Food items].Item, [Food purchase data].[Price per unit], [Food purchase data].[Purchase unit], Invoices.[Invoice Date]
FROM Invoices
INNER JOIN ([Food items] 
    INNER JOIN [Food purchase data] 
    ON [Food items].ID = [Food purchase data].[Food item ID]) 
ON Invoices.ID = [Food purchase data].[Invoice ID]
ORDER BY Invoices.[Invoice Date] DESC;

Однако приведенный выше запрос просто возвращает все покупки продуктов питания (т.е. несколько записей для каждой записи в [Food items]), а результаты сортируются по убыванию. Может кто-нибудь объяснить мне, что я неправильно понимаю в отношении DISTINCTоператора? То есть, почему он не возвращает только одну запись для каждого элемента в [Food items]?

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

ОБНОВЛЕНИЕ: Итак, я попытался, оба из предложенных ниже ответов, и ни один из них не работает (они просто выдают синтаксические ошибки).

Основываясь на приведенных ниже предложениях и дальнейшем чтении в Интернете, я написал следующий новый запрос, используя функцию агрегирования max()и GROUP BYпредложение:

SELECT [Food purchase data].[Food item ID], [Food purchase data].[Price per unit], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

Но у меня все еще есть та же проблема: то есть, я все еще вижу более одного результата для каждого продукта. Кто-нибудь может объяснить, почему этот запрос не только возвращает самую последнюю покупку для каждого продукта питания?

ОБНОВЛЕНИЕ 2 (решено!) :

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

Сначала я создал это представление и назвал его «LatestInvoices»:

SELECT InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate, InvoicesMaxDate.MaxID
FROM [Food purchase data], Invoices, (SELECT [Food purchase data].[Food item ID] AS ItemID, MAX(Invoices.[Invoice Date]) AS MaxDate, MAX(Invoices.[Invoice ID]) AS MaxID
                FROM [Food purchase data], Invoices
                WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
                GROUP BY [Food purchase data].[Food item ID]
         )  AS InvoicesMaxDate
WHERE InvoicesMaxDate.MaxID = [Food purchase data].[Invoice ID] AND
                      InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
                      InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate,  InvoicesMaxDate.MaxID

Затем я написал еще один запрос, чтобы получить необходимые поля:

SELECT [Food items].ID AS FoodItemID, [Food items].Item AS FoodItem, [Food purchase data].[Price], [Food purchase data].[Price per unit], [Food purchase data].[Purchase unit], LatestInvoices.MaxDate as InvoiceDate
FROM [Food items], [Food purchase data], LatestInvoices
WHERE LatestInvoices.[MaxID] = [Food purchase data].[Invoice ID] AND
             LatestInvoices.ItemID = [Food purchase data].[Food item ID] AND
             LatestInvoices.ItemID = [Food items].ID
ORDER BY [Food items].Item;

Спасибо всем, кто нашел время, чтобы помочь мне с этим!


2
DISTINCTвозвращает строки, которые различаются по всем столбцам в строке, а не по одному столбцу.
Макс Вернон

2
Просто совет, избегайте использования пробелов в именах таблиц и столбцов. Тогда вам не нужно будет все окружать [и]
Макс Вернон

1
И это (возможно) лучше включать имя таблицы во всех IDстолбцах, поэтому IDв Invoicesтаблице становится InvoiceID.
Макс Вернон

О, это имеет смысл - я думал, что это DISTINCTбыли отдельные столбцы. Есть ли аналогичный оператор, который будет выбирать только на основе уникальности в одном столбце? Кроме того, спасибо за советы по соглашениям об именах - да, это очень раздражает, когда приходится использовать [ ... ]везде ... И я вижу, как включение имени таблицы в столбец ID может улучшить читаемость.
Дж. Тейлор

Ответы:


7

MS Access довольно ограничен.

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

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

SELECT
    FPD1.[Food item ID] AS ItemID
    ,MAX(I1.[Invoice Date]) AS MaxDate
FROM
    [Food purchase data] AS FPD1
    INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
GROUP BY
    FPD1.[Food item ID]

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

На основе синтаксиса MS Access вложенных объединений и использования этого примера из документов:

SELECT fields 
FROM 
  table1 INNER JOIN 
  (
      table2 INNER JOIN 
      (
          table3 INNER JOIN tablex ON table3.field3 = tablex.fieldx
      ) ON table2.field2 = table3.field3
  ) ON table1.field1 = table2.field2
;

Давайте попробуем собрать это вместе:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,MAX(I2.ID) AS MaxInvoiceID
FROM
    (
        SELECT
            FPD1.[Food item ID] AS ItemID
            ,MAX(I1.[Invoice Date]) AS MaxDate
        FROM
            [Food purchase data] AS FPD1
            INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
        GROUP BY
            FPD1.[Food item ID]
    ) AS InvoicesMaxDate INNER JOIN
    (
        [Food purchase data] AS FPD2 
        INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]
    ) ON
        InvoicesMaxDate.ItemID = FPD2.[Food item ID] AND
        --- you may need to put extra "ON" here as well, not sure
        InvoicesMaxDate.MaxDate = I2.[Invoice Date]
GROUP BY
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate

Теперь у нас есть ItemID и ID последнего Счета для этого Предмета. Присоединитесь к исходным таблицам, чтобы получить другие подробности (столбцы).

SELECT
    FI3.Item
    ,FI3.Item
    ,FPD3.[Price per unit]
    ,FPD3.[Purchase unit]
    ,I3.[Invoice Date]
FROM
    (
        SELECT
            InvoicesMaxDate.ItemID
            ,InvoicesMaxDate.MaxDate
            ,MAX(I2.ID) AS MaxInvoiceID
        FROM
            (
                SELECT
                    FPD1.[Food item ID] AS ItemID
                    ,MAX(I1.[Invoice Date]) AS MaxDate
                FROM
                    [Food purchase data] AS FPD1
                    INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
                GROUP BY
                    FPD1.[Food item ID]
            ) AS InvoicesMaxDate INNER JOIN
            (
                [Food purchase data] AS FPD2 
                INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]
            ) ON
                InvoicesMaxDate.ItemID = FPD2.[Food item ID] AND
                InvoicesMaxDate.MaxDate = I2.[Invoice Date]
        GROUP BY
            InvoicesMaxDate.ItemID
            ,InvoicesMaxDate.MaxDate
    ) AS LastInvoices INNER JOIN
    (
        [Food items] AS FI3 INNER JOIN
        (
            [Food purchase data] AS FPD3
            INNER JOIN Invoices AS I3 ON I3.ID = FPD3.[Invoice ID]
        ) ON FI3.ID = FDP3.[Food item ID]
    ) ON
        LastInvoices.MaxInvoiceID = I3.ID AND
        LastInvoices.ItemID = FI3.ID

На практике я бы создал представление для первого запроса с помощью одного соединения. Затем я создал бы второе представление, которое объединяет первое представление с таблицами, затем третье представление и т. Д., Чтобы избежать вложенных объединений или минимизировать их. Общий запрос будет легче читать.


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

Последняя попытка передать мое сообщение.

Это то, что вы написали на основе моих предложений выше:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,Invoices.[Invoice ID]
FROM [Food purchase data], Invoices, 
    (
        SELECT 
            [Food purchase data].[Food item ID] AS ItemID
            ,MAX(Invoices.[Invoice Date]) AS MaxDate
        FROM [Food purchase data], Invoices
        WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
        GROUP BY [Food purchase data].[Food item ID]
    )  AS InvoicesMaxDate
WHERE
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
    InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate, Invoices.[Invoice ID];

Вот что я имел в виду:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,MAX(Invoices.[Invoice ID]) AS [Invoice ID]
FROM [Food purchase data], Invoices, 
    (
        SELECT
            [Food purchase data].[Food item ID] AS ItemID
            ,MAX(Invoices.[Invoice Date]) AS MaxDate
        FROM [Food purchase data], Invoices
        WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
        GROUP BY [Food purchase data].[Food item ID]
    )  AS InvoicesMaxDate
WHERE
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
    InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate;

Вы видите разницу?

InvoicesMaxDateВозвращает MAX Invoice Dateдля каждого Food item ID. Если есть два счета для одного Food item IDи того же MAX, Invoice Dateмы должны выбрать один счет из них. Это делается путем группировки InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate. Здесь не должно быть никакой группировки Invoices.[Invoice ID], потому что мы хотим выбрать счет с максимальным идентификатором.

Как только вы сохранили этот запрос в виде LatestInvoicesпредставления, он будет использован в дальнейшем, как вы правильно написали (обратите внимание, что последний запрос использует LatestInvoices.[Invoice ID]и LatestInvoices.ItemID, но не использует LatestInvoices.MaxDate):

SELECT 
    [Food items].ID as FoodItemID
    ,[Food items].Item as FoodItem
    ,[Food purchase data].[Price]
    ,[Food purchase data].[Price per unit]
    ,[Food purchase data].[Purchase unit]
    ,Invoices.[Invoice Date]
FROM [Food items], [Food purchase data], Invoices, LatestInvoices
WHERE 
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    [Food items].ID = [Food purchase data].[Food item ID] AND
    LatestInvoices.[Invoice ID] = Invoices.[Invoice ID] AND 
    LatestInvoices.ItemID = [Food items].ID
ORDER BY [Food items].Item

Что касается того, почему ваш последний запрос в вопросе возвращает несколько строк на элемент:

SELECT 
    [Food purchase data].[Food item ID]
    , [Food purchase data].[Price per unit]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

Здесь вы группируете по [Food item ID]и [Price per unit], поэтому вы получите столько строк, сколько есть уникальных комбинаций этих двух столбцов.

Следующий запрос будет возвращать одну строку на [Food item ID].

SELECT 
    [Food purchase data].[Food item ID]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID];

Примечание, вы действительно должны использовать явное INNER JOINвместо ,. Этому синтаксису 20 лет.

SELECT 
    [Food purchase data].[Food item ID]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM
    [Food purchase data]
    INNER JOIN Invoices ON Invoices.ID = [Food purchase data].[Invoice ID]
GROUP BY [Food purchase data].[Food item ID];

Спасибо за ваш очень подробный ответ! Первый запрос, которым вы поделились, работал и действительно вытягивал самую последнюю дату счета для каждого отдельного продукта, что действительно полезно. Тем не менее, когда я попробовал следующие 2 запроса, которыми вы поделились, я получил "Syntax error (missing operator) in query expression"выражение INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]... Я поэкспериментирую с ним, чтобы узнать, смогу ли я заставить его работать.
Дж. Тейлор

@JesseTaylor, по-видимому, необходимо явно ставить скобки, (а )когда запрос использует несколько объединений и ONнемного перемещать предложение. У меня нет доступа для проверки, но я могу попытаться угадать правильный синтаксис, прочитав документы сегодня.
Владимир Баранов

@JesseTaylor, я обновил ответ и надеюсь, что угадаю синтаксис правильно. Пожалуйста, попробуйте и дайте мне знать, если это работает.
Владимир Баранов

1
@JesseTaylor, пожалуйста. Прошло много времени с тех пор, как я использовал Access, и трудно понять его синтаксис. Одно замечание о вашем взгляде LatestInvoices: финал GROUPдолжен быть BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDateтолько без Invoices.[Invoice ID]. В SELECTчасти должно быть MAX(Invoices.[Invoice ID]) AS [Invoice ID]. Это весь смысл. Сначала (во внутреннем запросе) мы GROUP BY [Food item ID]и находим максимальную дату счета. С этой датой может быть несколько счетов, поэтому есть секунда, GROUP BYчтобы выбрать счет с максимальным идентификатором среди них.
Владимир Баранов

1
@JesseTaylor, к сожалению, вы меня не так поняли. Я обновил свой ответ, чтобы показать вам, что я имел в виду. Чтобы увидеть разницу, добавьте две накладные к вашим (выборочным) данным для одной ItemIDи той же большой даты и попробуйте оба запроса.
Владимир Баранов

3

Запрос, который просто работает из коробки:

SELECT Fi.Item, Fpd.[Price per unit], Fpd.[Purchase unit]
FROM [Food items] Fi INNER JOIN [Food purchase data] Fpd
ON Fpd.[Food item ID] = Fi.ID
WHERE Fpd.[Invoice ID] = (
  SELECT TOP 1 I.ID 
  FROM Invoices I INNER JOIN [Food purchase data] Fpd2
  ON Fpd2.[Invoice ID] = I.ID
  WHERE Fpd2.[Food item ID] = Fpd.[Food item ID]
  ORDER BY I.[Invoice Date] DESC
)

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

3

Я мог бы решить это с помощью следующего запроса:

Select MAX(AllItemBuyings.[invoice date]) as RecentBuyingDate, AllItemBuyings.[Food Item Id]  From 
(    
    select fpd.[Invoice Id], fpd.[Food Item Id], I.[invoice date] From [Food purchase data]as fpd 
    inner join invoices I on fpd.[Invoice Id] = I.ID

) as AllItemBuyings    
Group By AllItemBuyings.[Food Item Id]

Поскольку у меня нет Access, я проверил это на SQL Server. Я надеюсь, что это будет работать для вас.

Редактировать / Дополнительный запрос : чтобы добавить другие столбцы таблицы продуктов питания, я изменил запрос. Я сделал это способом, который мне не очень нравится. Если это нормально для вас, зависит от ваших данных и требований. Я снова присоединился к таблице счетов-фактур, используя дату заказа. В случае, если это дата, включая время моей работы, пожалуйста, помните об этом. Я не вижу другого пути в вашем сценарии. Может быть, есть лучшее решение, использующее рекурсивный запрос ...?

Пожалуйста, попробуйте и дайте мне знать, если это работает:

Select Recents.RecentBuyingDate, pd.* From 
(

   Select MAX(AllItemBuyings.[invoice date]) as RecentBuyingDate, AllItemBuyings.[Food Item Id]    From 
    (    
        select fpd.[Invoice Id], fpd.[Food Item Id], I.[invoice date], fpd.ID From [Food purchase data]as fpd 
        inner join invoices I on fpd.[Invoice Id] = I.ID

    ) as AllItemBuyings    
    Group By AllItemBuyings.[Food Item Id]

    ) as Recents    
    Join Invoices i on i.[invoice date] = Recents.RecentBuyingDate
    Join [Food purchase data] pd ON pd.[Invoice Id] = i.ID AND pd.[Food Item Id] = Recents.[Food Item Id]

Спасибо. Это правильно дает мне самую последнюю дату покупки для каждого элемента. Как бы я использовал это, чтобы вытащить все поля, которые я упомянул в вопросе (например Item, Price per unitи т. Д.)?
Дж. Тейлор

Новый предложенный вами запрос просто выдает сообщение об ошибке, в котором ничего не сказано, кроме «Синтаксическая ошибка в предложении FROM».
Дж. Тейлор

Возможно, Access требует, чтобы операция JOIN была в точности «INNER JOIN». Попробуйте INNSER JOIN вместо просто JOIN.
Magier

2

Я считаю, что ниже должно работать.

SELECT fi.[Item], fd.[Price per unit], MAX(i.[Invoice Date])
FROM [Invoices] AS i
INNER JOIN [Food Purchase Data] AS fd
    ON i.ID = fd.[Invoice ID]
INNER JOIN [Food items] AS fi
    ON fd.[Food item ID] = fi.ID
GROUP BY fi.Item, fd.[Price per unit]
ORDER BY i.[Invoice Date] DESC

Что касается того, почему ваш запрос не возвращает результаты, которые вам нравятся:

SELECT [Food purchase data].[Food item ID], [Food purchase data].[Price per unit], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

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

Например, если в двух таблицах было по 3 записи, а не возвращалась самая последняя дата, ваш запрос вернул бы что-то вроде: 1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 , 3

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

FROM [Food purchase data] AS fd, [Invoices] AS i
WHERE fd.[Invoice ID] = i.[ID]

ИЛИ

FROM [Food purchase data] AS fd
INNER JOIN [Invoices] AS i
    ON fd.[Invoice ID] = i.[ID]

Обновленные запросы, если они все еще не работают, попробуйте удалить псевдонимы и использовать полные имена столбцов.



0

Я согласен с предложениями Макса о вашей модели данных. Реализация этого сделает ваш SQL более читабельным в долгосрочной перспективе.

С учетом сказанного, DISTINCT будет отображать уникальные строки. Таким образом, чтобы показывать только самые последние, вы должны ограничить отображаемые столбцы.

Попробуйте что-то вроде:

SELECT [Food purchase data].[Food item ID], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM Invoices 
INNER JOIN ([Food items] ON [Food items].ID = [Food purchase data].[Food item ID]) 
GROUP BY [Food purchase data].[Food item ID]

(Перевод: для каждого товара в магазине отображается его самая последняя дата выставления счета.)

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

(Теоретически, вы также можете сделать вложенный запрос, но так как вы запросили простой, сохраненный запрос проще.)

ОБНОВЛЕНИЕ на основе вашего обновления:

Я собираюсь использовать предложения WHERE вместо JOINS, потому что у меня нет под рукой MS Access. Вы должны быть в состоянии использовать графический интерфейс для создания соединений между таблицами в MS Access на основе этой информации. (Пожалуйста, предоставьте SQLFiddle, если вам действительно нужна помощь в дальнейшем устранении неполадок.)

Шаг 1. Сохраните это как ВИД (например, «MostRecentInvoice»)

SELECT [Food purchase data].[Food item ID] AS FoodItemID, max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
WHERE [Food purchase data].[Food item ID] = Invoices.ID
GROUP BY [Food purchase data].[Food item ID];

Шаг 2: Используйте представление во втором запросе

SELECT (list all the fields you need here)
FROM MostRecentInvoice, Invoices, etc...
WHERE MostRecentInvoice.FoodItemID = [Food purchase data].[Food item ID] 
AND MostRecentInvoice.MostRecentInvoiceDate = Invoices.[Invoice Date]
AND (whatever else joins you'll need for the other tables)

... и ответить на ваш вопрос: 2-й запрос в обновлении не работает, поскольку столбец [Цена за единицу] находится в ваших операторах SELECT и GROUP BY. По сути, это означает, что вы просите просмотреть ВСЕ возможные значения [Цена за единицу], хотя на самом деле вам нужно только одно: самое последнее значение.


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

Извините, у меня не было времени самостоятельно создавать таблицы в Access. Я предположил, что у вас было немного опыта с объединениями, так как некоторые из них были в вашем вопросе. Вы пытались создать его, выполнив Create -> Query in Access?
chabzjo

Первый запрос, которым вы поделились, не дает правильных результатов из-за строки WHERE [Food purchase data].[Food item ID] = Invoices.ID... Я предполагаю, что вы имели в виду, WHERE [Food purchase data].[Invoice ID] = Invoices.[Invoice ID]но она по-прежнему возвращает несколько дат для каждого продукта, а не только самые последние.
Дж. Тейлор
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.