垂直表结构的T-SQL查询

时间:2017-06-16 13:58:35

标签: sql-server tsql stored-procedures

我正在开展一个电子商务项目。现在我必须为产品列表页面构建一个过滤器。 我的桌子在下面。

产品

id title      | description           | Etc.
-- ---------- | --------------------- | -----------
1  Product  1 | Product 1 description | xxx
2  Product  2 | Product 2 description | xxx
3  Product  3 | Product 3 description | xxx
4  Product  4 | Product 4 description | xxx
5  Product  5 | Product 5 description | xxx

规格

id title      | Etc.
-- ---------- | ------
1  Color      | xxx
2  Display    | xxx

ProductSpecifications

id          | productId   | specificationId | value
----------- | ----------- | --------------- | -----
1           | 1           | 1               | Red
2           | 1           | 2               | LED
3           | 2           | 1               | Red
4           | 2           | 2               | OLED
5           | 3           | 1               | Blue
6           | 3           | 2               | LED
7           | 4           | 1               | Blue
8           | 4           | 2               | OLED

电子商务用户必须能够同时过滤多个选项。我的意思是,用户可能想要搜索“(红色或蓝色)和OLED”电视。

我尝试了一些东西,但我无法编写正确的存储过程。我想,我被困在这里,我需要一些帮助。

编辑:

经过一些回答后,我需要在此处更新一些其他信息。

规格是动态的。因此过滤器也是动态的。我使用名为allowFilter的位列生成过滤器。所以我无法使用强类型参数,例如@color@display

用户不得使用过滤器。或者他们可以使用一个或多个过滤器。你可以在这里找到我正在处理的查询:

ALTER PROCEDURE [dbo].[ProductsGetAll]
@categoryId int,
@brandIds varchar(max),
@specIds varchar(max),
@specValues varchar(max),
@pageNo int,
@pageSize int,
@status smallint,
@search varchar(255),
@sortOrder smallint
as
/*
TODO: Modify query to use sortOrder
*/
select * into #products
from
(
    select ROW_NUMBER() OVER (order by p.sortOrder) as rowId,p.*
    from Products p left join ProductSpecifications ps on ps.productId = p.id
    where
    (@status = -1
        or (@status = -2 and (p.status = 0 or p.status = 1))
        or (p.status = @status)
    )
    and (@categoryId = -1 or p.categoryId = @categoryId)
    and (@brandIds = '' or p.brandId in (select ID from fnStringToBigIntTable(@brandIds,',')))
    and (
        @search = ''
        or p.title like '%' + @search + '%'
        or p.description like '%' + @search + '%'
        or p.detail like '%' + @search + '%'
    )
    and (@specIds = ''
        or (
            ps.specificationId in (select ID from fnStringToBigIntTable(@specIds,','))
            and ps.value in (@specValues)
        )
    )
) x
where
(rowId > @pageSize * (@pageNo - 1) and rowId <= @pageSize * @pageNo)

select * from #products
select * from Categories where id in (select categoryId from #products)
select * from Brands where id in (select brandId from #products)

select count(p.id)
from Products p left join ProductSpecifications ps on ps.productId = p.id
where 
(@status = -1
    or (@status = -2 and (p.status = 0 or p.status = 1))
    or (p.status = @status)
)
and (@categoryId = -1 or p.categoryId = @categoryId)
and (@brandIds = '' or p.brandId in (select ID from fnStringToBigIntTable(@brandIds,',')))
and (
    @search = ''
    or p.title like '%' + @search + '%'
    or p.description like '%' + @search + '%'
    or p.detail like '%' + @search + '%'
)
and (@specIds = ''
    or (
        ps.specificationId in (select ID from fnStringToBigIntTable(@specIds,','))
        and ps.value in (@specValues)
    )
)

drop table #products

我的问题是:

and (@specIds = ''
        or (
            ps.specificationId in (select ID from fnStringToBigIntTable(@specIds,','))
            and ps.value in (@specValues)
        )
    )

我完全可以更改此部分以及此部分中使用的参数。

6 个答案:

答案 0 :(得分:3)

首先,我要感谢你@alex。我使用表值参数来解决我的问题。

类型:

function myfunction($template)
{
    $template['selected'] = 'null';
    return $template;
}

存储过程:

CREATE TYPE [dbo].[specificationsFilter] AS TABLE(
    [specId] [int] NULL,
    [specValue] [varchar](50) NULL
)

.Net Code创建数据表参数:

ALTER PROCEDURE [dbo].[ProductsGetAll]
@categoryId int,
@brandIds varchar(max),
@specifications specificationsFilter readonly,
@pageNo int,
@pageSize int,
@status smallint,
@search varchar(255),
@sortOrder smallint
as
declare @filterCount int
set @filterCount = (select count(distinct specId) from @specifications)
/*
ORDER BY
    TODO: Modify query to use sortOrder
*/
select * into #products
from
(
    select ROW_NUMBER() OVER (order by p.sortOrder) as rowId,p.*
    from Products p
    where
    (@status = -1
        or (@status = -2 and (p.status = 0 or p.status = 1))
        or (p.status = @status)
    )
    and (@categoryId = -1 or p.categoryId = @categoryId)
    and (@brandIds = '' or p.brandId in (select ID from fnStringToBigIntTable(@brandIds,',')))
    and (
        @search = ''
        or p.title like '%' + @search + '%'
        or p.description like '%' + @search + '%'
        or p.detail like '%' + @search + '%'
    )
    and (@filterCount = 0
        or (
            p.id in (
                select productId
                from ProductSpecifications ps, @specifications s
                where
                ps.specificationId = s.specId
                and ps.value = s.specValue
                group by productId
                having sum(1) >= @filterCount
            )
        )
    )
) x
where
(rowId > @pageSize * (@pageNo - 1) and rowId <= @pageSize * @pageNo)

select * from #products
select * from Categories where id in (select categoryId from #products)
select * from Brands where id in (select brandId from #products)

select count(p.id)
from Products p
where 
(@status = -1
    or (@status = -2 and (p.status = 0 or p.status = 1))
    or (p.status = @status)
)
and (@categoryId = -1 or p.categoryId = @categoryId)
and (@brandIds = '' or p.brandId in (select ID from fnStringToBigIntTable(@brandIds,',')))
and (
    @search = ''
    or p.title like '%' + @search + '%'
    or p.description like '%' + @search + '%'
    or p.detail like '%' + @search + '%'
)
and (@filterCount = 0
    or (
        p.id in (
            select productId
            from ProductSpecifications ps, @specifications s
            where
            ps.specificationId = s.specId
            and ps.value = s.specValue
            group by productId
            having sum(1) >= @filterCount
        )
    )
)

drop table #products

我的查询字符串结构:

    private DataTable GetSpecificationFilter(string specificationFilter)
    {
        DataTable table = new DataTable();
        table.Columns.Add("specId", typeof(Int32));
        table.Columns.Add("specValue", typeof(string));

        string[] specifications = specificationFilter.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
        foreach(string specification in specifications)
        {
            string[] specificationParams = specification.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
            int specificationId = Convert.ToInt32(specificationParams[0]);
            string[] specificationValues = specificationParams[1].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            foreach(string value in specificationValues)
            {
                table.Rows.Add(specificationId, value);
            }
        }
        return table;
    }

这是在垂直表结构中过滤产品规格的完整解决方案。我用它来做电子商务项目。我希望这个解决方案可以帮助您解决类似问题。

答案 1 :(得分:1)

您需要一种方法来传递规范及其值。一种方法是使用group byhaving进行整体查询:

select ps.product_id
from product_specifications ps join
     specifications s
     on ps.specification_id = s.specification_id
where (s.name = @title1 and ps.value = @value1) or
      (s.name = @title2 and ps.value = @value2)
having count(*) = 2;  -- "2" is the number of specifications you are checking

此版本要求将规范和值添加为单独的变量。有类似的方法,您可以使用临时变量或values子句传入值。目前还不清楚传递值的方法在您的特定情况下最有效。

答案 2 :(得分:1)

更新

在这种情况下应该使用

Table valued parameters。 (见related

<小时/> 旧回答
似乎是对原始存储过程中发生的事情的变化。

这不是最好的方法,但这应该可以完成工作。

CREATE PROCEDURE GetData
    @Color CHAR(2) -- "10" is only red, "01" is only green, "11" is both red and green
,   @Display CHAR(2) -- "10" is LED, "01" is OLED, "11" is both LED and OLED
AS
BEGIN
    DECLARE @Values TABLE (Value NVARCHAR(10))

    IF SUBSTRING(@Color, 1, 1) = '1'   BEGIN INSERT INTO @Values (Value) VALUES ('Red') END
    IF SUBSTRING(@Color, 2, 1) = '1'   BEGIN INSERT INTO @Values (Value) VALUES ('Green') END   
    IF SUBSTRING(@Display, 1, 1) = '1' BEGIN INSERT INTO @Values (Value) VALUES ('LED') END
    IF SUBSTRING(@Display, 2, 1) = '1' BEGIN INSERT INTO @Values (Value) VALUES ('OLED') END

    SELECT      *
    FROM        productspecifications ps
    INNER JOIN  products p
    ON          p.id = ps.productid
    INNER JOIN  specifications s
    ON          ps.specificationid = s.id
    WHERE       ps.Value IN (SELECT * FROM @Values)
END

此示例非常特定于您提供的表格。

说明其工作原理

你传递两个只包含0和1的字符串(例如:&#34; 0010110&#34;)。您的存储过程将知道将字符串@Color中索引0处的1解释为Red,将@Color中索引1处的1解释为Blue。 LED与OLED相同。您的存储过程将有许多IF语句来检查每个字符串中的每个索引,并将相应的值存储在某个临时表中(如果没有太多值,则存储临时表变量)。然后,当您查询表时,只需放置一个WHERE子句,该子句检查您刚刚创建的临时表中ProductSpecifications表中的值。

如何运作

如果您想要(red or blue) and LED,那么@Color = "10"@Display = "10" 如果您想要blue and OLED,那么@Color = "01"@Display = "01"
如果您想要所有@Color = "11"@Display = "11"

赞成

  • 您可以实现(red or blue) and LED逻辑效果

缺点

  • 您必须知道传递的字符串中的哪个索引与哪个值相对应
  • 逻辑是&#34;泄漏&#34;从存储过程到代码(缺少封装)

结论

这不是一个好的解决方案。我个人不喜欢它,但它会完成工作。如果有人知道如何改进这将是惊人的。我很想自己学习更好的解决方案。
另外,在我看来,你需要通过&#34; array&#34;数据作为存储过程的参数,所以我想你可能想看看如何做到这一点的不同方式。我提供的示例中的一个是实现&#34;数组传递&#34;的一种方式,但还有许多其他更好的方法。

答案 3 :(得分:0)

我认为你需要第一次 外键来做你想做的事。

您可以在产品表中添加字段并将其命名为规范,这将是您的外键。

之后做你想做的事情尝试使用 GROUP BY 表达式

答案 4 :(得分:0)

我认为如果只有值参数可以使用,或者添加更多搜索参数,比如

CREATE PROCEDURE usp_ProductSpecifications (@value)
    AS
    BEGIN
        SELECT p.id
            ,p.NAME
            ,s.etc
            ,ps.value
            ,p.etc
        FROM productspecifications ps
        INNER JOIN products p
            ON p.id = ps.productid
        INNER JOIN specifications s
            ON ps.specificationid = s.id
        WHERE ps.value = @value
    END

答案 5 :(得分:0)

请尝试以下建议的解决方案,希望它有帮助!

Create Procedure SearchByCriteria
       @Color  VARCHAR(100) = NULL,
       @Display VARCHAR(100) = NULL
       AS  
       BEGIN
        IF @Color IS NOT NULL
        SET @Color = '%' + REPLACE (@Color,',','% OR ') + '%'
       SELECT 
       fROM PRoduct p
       INNER JOIN ProductSpecification ps ON ps.ProductId = p.productID
       LEFT OUTER JOIN specification scolor ON scolor.ID = ps.SpecificationID
                         and scolor.Id = 1
       LEFT OUTER JOIN specification sDisplay ON sdisplay.ID = ps.SpecificationID
                         and sdisplay.Id = 2
       WHERE (@Color IS NULL OR  scolor.etc like @Color)
       AND (@Display IS NULL OR  Sdisplay like @Display)

       END 
       GO