存储一对多关系的最佳方法 - 实践范例/困境

时间:2010-02-08 14:35:08

标签: sql

我需要对一个可以分解的想法进行建模,并考虑如下:

  1. BookDetails
  2. BookPrices
  3. 这里的问题是你可以有很多书价格,这些价格可能会有所变化。这是一个例子

    BookDetails:
    -----------------
    ID  Name
    1   Harry Potter…
    

    这很容易。

    更有意思的是,对于这本书,我当天可能会有十种不同的价格,例如:

    BookPrices:
    ------------------------------------
    Book_Details_Id  Kind          Price
    1                SpecialOffer     10
    1                BulkPurchase     20
    1                Normal           30
    

    我需要在列中提供一系列书籍及其所有价格 - 例如:

    BookName         SpecialOffer   BulkPurchase      Normal    
    Harry Potter…              10             20          30
    

    我的问题是:图书价格表是否应该将所有不同的价格类型作为列?对我来说,这是丑陋的,更好的想法是将每个价格作为一行

    如果我使用这种方法,我想不出一个SQL查询来生成结果集。我整个上午一直在想这个。

    编辑:我没有计算价格的余地 - 他们必须存储起来。

    编辑:这基本上是我能想到的1-n appraoch(感谢下面的评论) - 它实际上是我的想法

    SELECT book.bookid,bp1.price,bp2.price FROM book JOIN bookprice bp1 JOIN bookprice bp2 ON bp1.bookid = book.bookid AND bp1.pricetype = 1 AND bp2.bookid = book.bookid AND bp2.pricetype = 2 ...

    问题是10个价格会加入10次,这很糟糕!

10 个答案:

答案 0 :(得分:5)

这个怎么样

BookDetails
BookID BookName

BookPrice
BookID PriceID PriceTypeID

BookPriceType
PriceTypeID DEscription

答案 1 :(得分:4)

这个答案是特定于t-SQL的,可以使用一点改进,但​​它适用于SQL 2005。

取消注释评论的行,它会像MAGIC一样更新! (好吧,不是魔术,而是狡猾的hacky)

DROP TABLE Books
DROP TABLE Prices
DROP TABLE bookpricing

CREATE TABLE Books ( id INT, title VARCHAR(20) )
CREATE TABLE Prices ( id INT, [desc] VARCHAR(20), pricingchange VARCHAR(20))
CREATE TABLE bookpricing ( id INT, bookid INT, priceid INT, bookprice MONEY )

INSERT INTO Books VALUES (1, 'Hi Mom')
--INSERT INTO Books Values (2, 'This is another book') 
INSERT INTO Prices VALUES (1, 'Standard', '1')
INSERT INTO Prices  VALUES (2, 'Discount', '.5')
INSERT INTO Prices VALUES(3, 'HyperMarkup', '1.5')
INSERT INTO prices VALUES(4, 'Remaindered', '.1')

INSERT INTO BookPricing VALUES (1,1,1,20.00)
INSERT INTO BookPricing VALUES (2,1,2,10.00)
INSERT INTO BookPricing VALUES (3,1,3,30.00)
--INSERT INTO BookPricing VALUES (4,2,1,30.00)
--INSERT INTO BookPricing VALUES (5,2,2,15.00)
--INSERT INTO BookPricing VALUES (6,2,4,3.00)

SELECT * FROM bookpricing 

/ **此位从http://www.tsqltutorials.com/pivot.php ** /

被盗
DECLARE @columns VARCHAR(max)

SELECT @columns = COALESCE(@columns + ',[' + cast(id as varchar) + ']',
'[' + cast(id as varchar)+ ']')
FROM prices


DECLARE @query VARCHAR(max)

SET @query = '
SELECT * FROM (SELECT BookID, PriceID, BookPrice FROM BookPricing) AS BookTable PIVOT (SUM(bookprice) FOR priceid 
IN (' + @columns + ')
)
AS p'

EXECUTE(@query)

答案 2 :(得分:2)

正如其他人所提到的,如果您将数据存储在表格中的行中,那么在需要更多价格类型时,您将拥有更大的灵活性。但是,使用这种方法很可能需要您透视数据,以便输出每本书一行的结果,同一行包含所有不同的定价选项(如您所示的示例)。根据您使用的SQL Server版本,实现起来可能相对简单,或者更具挑战性。有关详细信息,请参阅此SO question

如果价格类型是静态的,那么您可能更好地将数据存储为表的列而不是行。这使得以您正在寻找的形式返回数据变得微不足道,但是如果规则发生变化则需要更多工作。

答案 3 :(得分:1)

我多次遇到过类似的问题。我看待它的方式是,你可以明确地为一本书建模。例如,您有10种类型的价格。这些类型是静态的吗?他们有变化吗?如果他们没有改变,那么我更喜欢将它们作为列,在定价中(不会称之为细节)。这将包括有效的开始日期和可选的结束日期。这可以让您提前处理分期价格变化。

现在,如果您不知道您处理的价格类型。一个例子是最终用户(最有可能是管理员用户)定义书籍和各种类型的价格的系统。这是一个复杂得多的系统,因为您还需要让用户定义何时使用哪个价格的规则。

<强>更新

以下是使用动态数据透视查询的示例:

create table #BookPrice(bookId int,Price money,PriceType nvarchar(30)) 插入#BookPrice 值(1,10.55,'列表')

插入#BookPrice 值(1,9.50, '费用') 插入#bookPrice 值(2,10.22,'列表')

/ 找出你需要的价格......最好不要查询表格本身 / 声明@priceQuery varchar(max) 选择@priceQuery = IsNull(@priceQuery,'')+     '['+ cast(PriceType as varchar(32))+'],' 从 (从中选择不同的PriceType  #BookPrice )P

- 删除最后一个逗号 设置@priceQuery = left(@ priceQuery,len(@priceQuery)-1)

声明@dynquery varchar(max) 设置@dynquery ='select bookId,*'+     '来自#BookPrice'+     'pivot('+     'max([Price])'+     'for [PriceType]'+     'in('+ @priceQuery +'))as pivotTable'

exec(@dynquery)

drop table #bookPrice

<强>更新

更新了以上示例,以说明如果您的图书缺少价格类型,该图书将在查询中显示为null

答案 4 :(得分:1)

这里的问题是:

Wil的图书价格类型列表是静态的吗?或是价格的类型(哥伦布日价格,特别超级碗XXXVIII价格,因为我喜欢星期二的价格,经常出现和消失?

如果列表是静态的(比如5或6种不同类型的价格,则将它们分别放在产品表的附加列中。

如果列表可以更改(即使只是轻微和/或非常偶然),那么为价格添加一个单独的表。

  Create Table Prices (
     ProductId Integer Not Null,
     PriceTypeId Integer Not Null,
     Price Decimal(16,4) Not Null,
     Primary Key Constraint PricePK (ProductId, PriceTypeId)
     )

答案 5 :(得分:0)

如果您想静态存储价格(而不是计算价格),请使用1对n关系:

表1包含行: ID 标题 出版者 等

表2包含行: 的BookID 价

表2中的“BookID”将指向表1中的“ID”,这样您就可以拥有任意数量的价格。使用JOIN(请参阅SQL文档)检索特定书籍的所有价格。

如果您提供特别折扣(例如批量购买10%折扣,但如果客户一次购买超过100件商品则可享受20%折扣),我宁愿计算价格而不是静态存储。

答案 6 :(得分:0)

这是桥接表的基本用例,您可以:

1个书籍信息表
1张价格信息表
1个桥接表将每个实例的书和价格加在一起。

答案 7 :(得分:0)

编辑:很抱歉误读了这个问题

您可以在此处使用三个表来表示您的模型,一个用于书籍:

CREATE TABLE Books (
BookId Int,
Name Varchar
)

一个价格:

CREATE TABLE Prices (
PriceId Int,
Amount decimal,
PriceName varchar
)

和一个将它们加在一起(这有一个标志显示当前价格):

CREATE TABLE BookPrices (
BookId Int,
PriceId Int,
Current bit
)

PriceName为数据透视查询提供了一个钩子,它会将您的行转换为列。在我的测试中,我使用了名称Shelf and Online

SELECT * 
FROM (
    SELECT Book.Name BookName, Price.Amount Amount, Price.Name PriceName
    FROM Books Book
    JOIN BookPrices bp
        ON Book.BookId = bp.BookId
    JOIN Prices Price
        ON Price.PriceId = bp.PriceId
) DataTable
PIVOT(
    SUM(Amount)
    FOR PriceName IN ([Shelf],[Online])
) PivotTable

答案 8 :(得分:0)

我想我有类似的问题 我用t-sql构造'pivot'解决了这个问题 (丑陋:你必须明确说明查询中的列)

我们正在使用T-SQL 我不知道你正在使用的sql方言,所以它可能不适用。

http://www.tsqltutorials.com/pivot.php

答案 9 :(得分:0)

这也许可以按照你的意愿行事。

SELECT D.ID, D.[NAME]
, SUM(D.SpecialOffer) AS SpecialOffer
, SUM(D.BulkPrice) AS BulkPrice
, SUM(D.Normal) AS Normal
FROM (
SELECT BD.ID AS ID, BD.NAME AS [NAME]
    , CASE WHEN BP.Kind LIKE N'%SpecialOffer%' THEN BP.Price ELSE NULL END AS SpecialOffer
    , CASE WHEN BP.Kind LIKE N'%BulkPrice%' THEN BP.Price ELSE NULL END AS BulkPrice
    , CASE WHEN BP.Kind LIKE N'%Normal%' THEN BP.Price ELSE NULL END AS Normal
FROM BookDetails BD
INNER JOIN BookPrices BP ON BP.ID_BOOK_DETAILS = BD.ID) D
GROUP BY D.ID, D.[NAME]

只需确保列名适合您的名称即可。此外,它是ANSI,因此这不是DBMS特定的SQL DQL代码,可以在ORACLE,SQL SERVER等上使用。