Преобразование пользовательских единиц SQL - PullRequest
0 голосов
/ 21 декабря 2010

Я ищу решение для пользовательского преобразования единиц измерения в SQL, база данных, используемая моей компанией, - сервер Microsoft SQL, мне нужно написать SQL для возврата коэффициента преобразования, основанного на «таблице преобразования единиц»

скажем:

Item: chicken wings (itemid 1001)
vendor: food wholesale ltd (vendorid 5000)
unitid: gram (id=10)
unitid: kilogram (id=500)
unitid: boxes (id=305)
Quantity: 1000 grams = 1kgs = 5 boxs

Таблица перевода единиц:

itemid | vendorid | unit1id | unit2id | quantity1 | quantity2

1001 5000 10 500 1000 1

1001 5000 500 305 1 5

Вопрос: Каков конечный запас для куриных крылышек в граммах, если у меня есть 10 коробок

Какнаписать этот sql, чтобы вернуть «коэффициент преобразования»?

Заранее спасибо

Ответы [ 4 ]

1 голос
/ 02 апреля 2014

Следующее решение протестировано на SQL Server 2012. Чтобы уменьшить размер кода на странице, я предоставляю только измерения массы, поскольку они проверены и работают.

CREATE TABLE [Measurement type]
(
   [Type ID] INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
   [Type Name] NVARCHAR(30) NOT NULL
)

CREATE TABLE [Measurement unit]
(
   [Unit ID] INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
   [Type ID] INT REFERENCES [Measurement type]([Type ID]) NOT NULL,
   [Unit name] NVARCHAR(30) NOT NULL,
   [Unit symbol] NVARCHAR(10) NOT NULL
)

/* Use both multiplier and divizor to reduce rounding errors */
CREATE TABLE [Measurement conversions]
(
   [Type ID] INT NOT NULL REFERENCES [Measurement type]([Type ID]),
   [From Unit ID] INT NOT NULL REFERENCES [Measurement unit]([Unit ID]),
   [To Unit ID] INT NOT NULL REFERENCES [Measurement unit]([Unit ID]),
   [From Unit Offset] FLOAT NOT NULL DEFAULT(0),
   [Multiplier] FLOAT NOT NULL DEFAULT(1),
   [Divizor] FLOAT NOT NULL DEFAULT(1),
   [To Unit Offset] FLOAT NOT NULL DEFAULT(0),
   PRIMARY KEY ([Type ID], [From Unit ID], [To Unit ID])
)

INSERT INTO [Measurement type]([Type ID], [Type Name]) VALUES(4, 'Mass')

INSERT INTO [Measurement unit]([Unit ID], [Type ID], [Unit name], [Unit symbol])
VALUES (28, 4, 'Milligram', 'mg'), (29, 4, 'Gram', 'g'),
       (30, 4, 'Kilogram', 'kg'), (31, 4, 'Tonne', 't'),
       (32, 4, 'Ounce', 'oz'), (33, 4, 'Pound', 'lb'),
       (34, 4, 'Stone', 's'), (35, 4, 'hundred weight', 'cwt'),
       (36, 4, 'UK long ton', 'ton')

INSERT INTO [Measurement conversions]([Type ID], [From Unit ID], [To Unit ID], [Multiplier], [Divizor])
VALUES (4, 28, 29, 1, 1000), (4, 28, 30, 1, 1000000), (4, 28, 31, 1, 1000000000),
       (4, 28, 32, 1, 28350), (4, 32, 33, 1, 16), (4, 32, 34, 1, 224),
       (4, 32, 35, 1, 50802345), (4, 32, 36, 1, 35840)

INSERT INTO [Measurement conversions]([Type ID], [From Unit ID], [To Unit ID], [From Unit Offset], [Multiplier], [Divizor], [To Unit Offset])
SELECT DISTINCT [Measurement Conversions].[Type ID],
                [Measurement Conversions].[To Unit ID],
                [Measurement Conversions].[From Unit ID],
                -[Measurement Conversions].[To Unit Offset],
                [Measurement Conversions].[Divizor],
                [Measurement Conversions].[Multiplier],
                -[Measurement Conversions].[From Unit Offset]
FROM [Measurement Conversions]
-- LEFT JOIN Used to assure that we dont try to insert already existing keys.
LEFT JOIN [Measurement conversions] AS [Existing]
ON [Measurement Conversions].[From Unit ID] = [Existing].[To Unit ID] AND [Measurement Conversions].[To Unit ID] = [Existing].[From Unit ID]
WHERE [Existing].[Type ID] IS NULL

Запускайте следующий запрос, пока он не затронет 0 строк.

INSERT INTO [Measurement conversions]([Type ID], [From Unit ID], [To Unit ID], [From Unit Offset], [Multiplier], [Divizor], [To Unit Offset])
SELECT DISTINCT [From].[Type ID],
                [From].[To Unit ID] AS [From Unit ID],
                [To].[To Unit ID],
                -[From].[To Unit Offset] + (([To].[From Unit Offset]) * [From].[Multiplier] / [From].Divizor) AS [From Unit Offset],
                [From].[Divizor] * [To].[Multiplier] AS Multiplier,
                [From].[Multiplier] * [To].[Divizor] AS Divizor,
                [To].[To Unit Offset] - (([From].[From Unit Offset]) * [To].[Multiplier] / [To].Divizor) AS [To Unit Offset]
FROM [Measurement conversions] AS [From]
CROSS JOIN [Measurement conversions] AS [To]
-- LEFT JOIN Used to assure that we dont try to insert already existing keys.
LEFT JOIN [Measurement conversions] AS [Existing]
ON [From].[To Unit ID] = [Existing].[From Unit ID] AND [To].[To Unit ID] = [Existing].[To Unit ID]
WHERE [Existing].[Type ID] IS NULL
      AND [From].[Type ID] = [To].[Type ID]
      AND [From].[To Unit ID] <> [To].[To Unit ID]
      AND [From].[From Unit ID] = [To].[From Unit ID]

Наконец, чтобы сбросить мультипликаторы и делители, которые компенсируют друг друга:

UPDATE [Measurement conversions] SET [Multiplicand] = 1, [Dividend] = 1 WHERE [Multiplicand] = [Dividend]
1 голос
/ 21 декабря 2010

Я бы использовал таблицу пересчета и поместил бы все комбинации.Поэтому, даже если 5000g -> 5kg -> 1 коробка, я бы поставил и грамм -> преобразования коробки.Примерно так:

create table unit_unit_conv(
   from_unit varchar(10)   not null
  ,to_unit   varchar(10)   not null
  ,rate      decimal(10,6) not null
  ,primary key(from_unit, to_unit)
);

insert into unit_unit_conv values('kilogram', 'kilogram',   1);
insert into unit_unit_conv values('kilogram', 'gram',       1000);
insert into unit_unit_conv values('kilogram', 'box',        0.2);
insert into unit_unit_conv values('gram',     'gram',       1);
insert into unit_unit_conv values('gram',     'kilogram',   0.001);
insert into unit_unit_conv values('gram',     'box',        0.0002);
insert into unit_unit_conv values('box',      'box',        1);
insert into unit_unit_conv values('box',      'kilogram',   5);
insert into unit_unit_conv values('box',      'gram',       5000);

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

create table items(
   item_id        varchar(10) not null
  ,item_qty       int not null
  ,item_qty_unit  varchar(10)
);

insert into items values('chicken', 5,    'kilogram');
insert into items values('babies',  5000, 'gram');
insert into items values('beef',    1,    'box');

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

select i.item_id
      ,i.item_qty    as qty_original
      ,item_qty_unit as qty_unit_original
      ,i.item_qty * c.rate as box_qty
  from items          i
  join unit_unit_conv c on(i.item_qty_unit = c.from_unit)
 where c.to_unit = 'box';

+---------+--------------+-------------------+----------+
| item_id | qty_original | qty_unit_original | box_qty  |
+---------+--------------+-------------------+----------+
| chicken |            5 | kilogram          | 1.000000 |
| babies  |         5000 | gram              | 1.000000 |
| beef    |            1 | box               | 1.000000 |
+---------+--------------+-------------------+----------+
0 голосов
/ 29 декабря 2010

Я думаю, что рекурсивная таблица, которая находит путь от желаемой единицы до нужной единицы, будет работать лучше всего. Примерно так (предполагается, что если есть путь a -> b -> c, то в базе данных также есть путь c -> b -> a. Если нет, его можно изменить для поиска в обоих направлениях) .

select  1001 as itemID
        ,5000 as vendorID
        ,10 as fromUnit
        ,500 as toUnit
        ,cast(1000 as float) as fromQuantity
        ,cast(1 as float) as toQuantity
into #conversionTable
union
select  1001
        ,5000
        ,500
        ,305
        ,1
        ,5
union
select 1001
        ,5000
        ,305
        ,500
        ,5
        ,1
union
select  1001
        ,5000
        ,500
        ,10
        ,1
        ,1000

declare @fromUnit int
        ,@toUnit int
        ,@input int
set @fromUnit = 305 --box
set @toUnit =  10 --gram
set @input = 10

;with recursiveTable as
(
    select  0 as LevelNum
            ,ct.fromUnit
            ,ct.toUnit
            ,ct.toQuantity / ct.fromQuantity as multiplicationFactor
    from #conversionTable ct
    where   ct.fromUnit = @fromUnit

    union all

    select  LevelNum + 1
            ,rt.fromUnit
            ,ct.toUnit
            ,rt.multiplicationFactor * (ct.toQuantity / ct.fromQuantity)
    from #conversionTable ct
    inner join recursiveTable rt on rt.toUnit = ct.fromUnit
)

select @input * r.multiplicationFactor
from
(
    select top 1 * from recursiveTable 
    where (fromUnit = @fromUnit
    and toUnit = @toUnit)
) r
0 голосов
/ 21 декабря 2010

Теперь это хитрый. Похоже, вам понадобится рекурсивный выбор или курсор для его решения. По сути, вы хотите выбрать таблицу преобразования единиц измерения, где itemid = @desiredId и vendorid = @desiredVendor и unit2id = @finalUnitId. Затем вы захотите выполнить тот же запрос, но где unit2id = unit1id из запроса, который вы только что выполнили, - пока unit1id = @originalUnitId. Все это время поддерживаю постоянный коэффициент преобразования.

Итак, начните примерно так:
declare @factor float<br> set @factor = 0<br> select unit1id, quantity1, quantity2 from unitconversion where itemid = 1001 and vendorid = 5000 and unit2id = 305<br> set @factor = @factor + quantity1 / quantity 2

Затем вы захотите проверить, соответствует ли unit1id, выбранный из вышеприведенного, единице, которую вы хотите получить (в вашем примере вы проверяете, если unit1id = 10). Если нет, снова запустите тот же запрос, но ограничьте для unit2id значение 10.

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

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