I'm working on an e-commerce project. Now I have to build a filter for product listing page.
My tables are below.
Products
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
Specifications
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
Users of e-commerce must be able to filter multiple options at the same time. I mean, a user may want to search for "(Red or Blue) and OLED" TVs.
I tried something but i couldn't write the right stored procedure. I guess, i'm stuck here and i need some help.
EDIT :
After some answers, I need to update some additional information here.
The specifications are dynamic. So filters are also dynamic. I generate filters by using a bit column named allowFilter. So I cant use strongly typed parameters like #color or #display
Users may not use filter. Or they may use one or more filter. You can find the query that i'm working on here:
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
My problem is the part of:
and (#specIds = ''
or (
ps.specificationId in (select ID from fnStringToBigIntTable(#specIds,','))
and ps.value in (#specValues)
)
)
I can totally change of this part and the parameters that used in this part.
Firstly, I have to thank you #alex. I used table valued paramters to solve my problem.
Type:
CREATE TYPE [dbo].[specificationsFilter] AS TABLE(
[specId] [int] NULL,
[specValue] [varchar](50) NULL
)
Stored Procedure:
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
.Net Code to create Data Table paramter:
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;
}
And my query string structure:
?specs=1:Red,Blue;3:LED,OLED
This is a complete solution to filter product specifications in a vertical table sturcture. I used this for an e-commerce project. I hope this solution helps you for similar cases.
You need a way to pass in the specifications and their values. One approach is to use group by and having for the overall query:
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
This version requires adding in the specifications and values as separate variables. There are similar approaches, where you can pass in the value using a temporary variable or values clause. It is unclear what method of passing in the values works best in your particular case.
Update
Table valued parameters should be used in this case.
(See related)
older answer
which appears to be a variation on what was happening in op's original stored procedure.
This is not the best approach, but this should get the job done.
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
This example is very specific to tables you provided in question.
Explanation of how it works
You pass two strings which consist of only zeros and ones (ex.: "0010110"). Your stored procedure will know to interpret 1 at index 0 in string #Color as Red and 1 at index 1 in #Color as Blue. Same thing for LED vs OLED. Your stored procedure will have many IF statements to check for every index in every string and store corresponding values in some temporary table (or temporary table variable if you there are not too many values). Then when you query your tables just put a single WHERE clause which check where value in ProductSpecifications table is present in the temporary table you just created.
How would it work
If you want (red or blue) and LED then #Color = "10" and #Display = "10".
If you want blue and OLED then #Color = "01" and #Display = "01".
If you want all then #Color = "11" and #Display = "11".
Pros
You can achieve that (red or blue) and LED logic effect
Cons
You have to know which index in the passed string corespondent to which value
Logic is "leaking" from stored procedure into code (lack of encapsulation)
Conclusion
This is not a good solution. I personally don't like it, but it would get the job done. If somebody knows how to improve this that would be amazing. I would love to learn a better solution myself.
Also, it appeared to me that you have the need to pass "array" of data as parameter to stored procedure, so I think you may want to look at different ways on how to do that. The one in example I provided is one way of achieving "array passing", but there are many other and better ways.
I think you need FIRST a foreign key to do what you want .
You can add a field to the Products table and call it specification , this will be your foreign key .
After that to do what you want try to use a GROUP BY expression
I think if there is only value parameter this works, or add more search parameters u like
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
Please try below suggested solution, hope it helps!!
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
Related
So we need to pad with zeros in the customer numbering pattern. The length of the numbers in between the dashes should always be 3. So, first it should find the first space and then find the length of the numbers in between the dashes and if it's less than 3 it should pad with zeros
Ex: XYZ 45-678-2
This should be corrected to XYZ 045-678-002.
> **Edit Updated for new patterns**
Declare #YourTable table (ID int,YourField varchar(50))
Insert Into #YourTable values
(1,'XYZ 45-678-2'),
(2,'XYZ 45-678'),
(3,'DA 1-1'),
(4,'XYZ 123-678-9-234')
--Update #YourTable Set YourField -- If Satisfied Remove Comment
Select A.*,NewField -- If Satisfied Remove This Line
= Concat(Pos1
,Format(Try_Convert(int,Pos2),' 000')
,Format(Try_Convert(int,Pos3),'-000')
,Format(Try_Convert(int,Pos4),'-000')
,Format(Try_Convert(int,Pos5),'-000')
,Format(Try_Convert(int,Pos6),'-000')
,Format(Try_Convert(int,Pos7),'-000')
,Format(Try_Convert(int,Pos8),'-000')
,Format(Try_Convert(int,Pos9),'-000')
)
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','varchar(50)')
,Pos2 = xDim.value('/x[2]','varchar(50)')
,Pos3 = xDim.value('/x[3]','varchar(50)')
,Pos4 = xDim.value('/x[4]','varchar(50)')
,Pos5 = xDim.value('/x[5]','varchar(50)')
,Pos6 = xDim.value('/x[6]','varchar(50)')
,Pos7 = xDim.value('/x[7]','varchar(50)')
,Pos8 = xDim.value('/x[8]','varchar(50)')
,Pos9 = xDim.value('/x[9]','varchar(50)')
From (Select Cast('<x>' + Replace(Replace(YourField,' ','-'),'-','</x><x>')+'</x>' as XML) as xDim) A
) B
Where YourField Like '% %-%'
The Updated Results Would Be
ID YourField
1 XYZ 045-678-002
2 XYZ 045-678
3 DA 001-001
4 XYZ 123-678-009-234
I have a problem when running queries with a 'case when'. so I want to create a variable to hold the value of the results of the query 'select' to other tables. like the example below. please help me to get the results I want. thank you.
SELECT
a.field1, a.field2,
a.field3 =
CASE
WHEN a.field1 = 'alfa'
THEN
WHEN
-- I want to declare variable to check get value from another table with query select and condition 'WHERE' with variable declare
var varTest = (SELECT TOP 1 b.field1 FROM Table2 as b WHERE b.field2=a.field2) Then
if varTest = 'actif' then
SELECT (c.field4 * C.field5) as hasil FROM Table2 as c WHERE c.field1=varTest)
ELSE
a.field3
END
FROM
Table1 a(NOLOCK)
WHERE
a.field1 = 'alfa'
This is sample data :
field1 | field2 | field3 | > Table1
alfa idAlfa 0
beta idBeta 0
carlie idCarlie 0
field1 | field2 | field4 | field5 | > Table2
actif idAlfa 80 5
pasif idBeta 50 5
other idCarlie 10 5
Result :
field1 | field2 | field3
alfa idAlfa 400
beta idBeta 250
carlie idCarlie 50
It looks to me like this is what you intended. Hard to be entirely sure. It's also possible that this isn't the most efficient query as it's generally a good idea to avoid subqueries inside case expressions.
a.field3 =
CASE WHEN a.field1 = 'alfa' THEN
CASE WHEN (
SELECT TOP 1 b.field1 FROM Table2 as b
WHERE b.field2 = a.field
) = 'actif'
THEN (
SELECT c.field4 * C.field5 as hasil FROM Table2 as c
WHERE c.field1 = 'actif'
)
ELSE a.field3
END
ELSE null
END
You don't need a variable of any type to accomplish this.
A simple JOIN and CASE will get the active (actif) record, calculate your field4 * field5 result (hasil) or use 0 for all passive (pasif) or other records like so:
SELECT
a.field1,
a.field2,
CASE b.field1 -- check id...
WHEN 'actif'
THEN b.field4 * b.field5 -- if 'actif' record, calculate result
ELSE
a.field3 -- not the 'actif' record, use default value
END AS hasil
FROM Table1 AS a -- join the two tables by id name
LEFT JOIN Table2 AS b
ON a.field2 = b.field2;
If you're using SQL Server 2012, you can condense this CASE statement by using IIF:
SELECT
a.field1,
a.field2,
IIF(b.field1 = 'actif', b.field3 * b.field4, a.field3) AS hasil
FROM Table1 AS a
LEFT JOIN Table2 AS b
ON a.field2 = b.field2;
The results using your sample data from these queries are:
field1 | field2 | hasil
alfa idAlfa 400
beta idBeta 0
charlie idCharlie 0
For future reference, the IF syntax you showed in your sample SQL is invalid. That is used in flow control statements that affect how SQL is executed; usually seen in stored procedures. IF can't be used in expressions. Instead, use CASE or IIF statements.
I need a query to assign teams to a series of users. Data looks like this:
UserId Category Team
1 A null
2 A null
3 B null
4 B null
5 A null
6 B null
8 A null
9 B null
11 B null
Teams should be created by sorting by userid and the first userid becomes the team number and the consecutive A's are part of that team as are the B's that follow. The first A after the Bs starts a new team. There will always be at least one A and one B. So after the update, that data should look like this:
UserId Category Team
1 A 1
2 A 1
3 B 1
4 B 1
5 A 5
6 B 5
8 A 8
9 B 8
11 B 8
EDIT:
Need to add that the user id's will not always increment by 1. I edited the example data to show what I mean. Also, the team ID doesn't strictly have to be the id of the first user, as long as they end up grouped properly. For example, users 1 - 4 could all be on team '1', users 5 and 6 on team '2' and users 8,9 and 11 on team '3'
First you could label each row with an increasing number. Then you can use a left join to find the previous user. If the previous user has category 'B', and the current one category 'A', that means the start of a new team. The team number is then the last UserId that started a new team before the current UserId.
Using SQL Server 2008 syntax:
; with numbered as
(
select row_number() over (order by UserId) rn
, *
from Table1
)
, changes as
(
select cur.UserId
, case
when prev.Category = 'B' and cur.Category = 'A' then cur.UserId
when prev.Category is null then cur.UserId
end as Team
from numbered cur
left join
numbered prev
on cur.rn = prev.rn + 1
)
update t1
set Team = team.Team
from Table1 t1
outer apply
(
select top 1 c.Team
from changes c
where c.UserId <= t1.UserId
and c.Team is not null
order by
c.UserId desc
) as team;
Example at SQL Fiddle.
You can do this with a recursive CTE:
with userCTE as
(
select UserId
, Category
, Team = UserId
from users where UserId = 1
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
from userCTE
inner join users on users.UserId = userCTE.UserId + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
You can update the CTE to get this to go:
with userOrder as
(
select *
, userRank = row_number() over (order by userId)
from users
)
, userCTE as
(
select UserId
, Category
, Team = UserId
, userRank
from userOrder where UserId = (select min(UserId) from users)
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
, users.userRank
from userCTE
inner join userOrder users on users.userRank = userCTE.userRank + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
For larger datasets you'll need to add the maxrecursion query hint; I've edited the previous queries to show this. From Books Online:
Specifies the maximum number of recursions allowed for this query.
number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied.
In this case I've set it to 0, i.e. not limit on recursion.
Query Hints.
I actually ended up going with the following. It finished on all 3 million+ rows in a half an hour.
declare #userid int
declare #team int
declare #category char(1)
declare #lastcategory char(1)
set #userid = 1
set #lastcategory='B'
set #team=0
while #userid is not null
begin
select #category = category from users where userid = #userid
if #category = 'A' and #lastcategory = 'B'
begin
set #team = #userid
end
update users set team = #team where userid = #userid
set #lastcategory = #category
select #userid = MIN(userid) from users where userid > #userid
End
I've run into a tricky issue with recursive searching in an eCommerce shop stored procedure. Basically this single procedure will return all products factoring in basic filters and paging, and using a parent/child category table to perform recursive checks down the hierarchy. This is works beautifully and the CTE's run extremely fast, however the recent addition of a keyword search which needs to search across the Category Name, Product Name, and Style Number has caused dramas.
This seemed quite trivial at first as the 1st CTE already generates a table of all relevant categories in the hierarchy based on the supplied #categoryid and then joins onto the rest of the Product specific tables for all filtering. The Product Name and Style Number search works fine, but I cannot for the life of me get a Category Name search to work because it needs to search the category tree for any matches down the hierarchy tree starting from the top.
EDIT: I'm now thinking it may just be a lot easier to add a "tag" table against Products which stores all keyword related tags such as category name, product name and style etc and search directly against the tags.
For example a subset of the Category hierarchy looks like this:
Mens
- Polos
- Jerseys
- Pants
Womens
- Pants
- Shirts
- Polos
Supporters
- State Of Origin
- Mens
- Womens
- Kids
- Bulldogs
- Jerserys
- Pants
- Shirts
- Caps
- Warratahs
In my sample code below i am passing a search term of "origin mens" which should return all products within the "State of Origin" category that are also within the "Mens" category. The only thing it matches on is Product Names that start with "Origin" and nothing else because the category at the product level is not "State of Origin" as this is the parent. Any help here would be fantastic!
-- Variable Declarations
DECLARE #categoryid int
DECLARE #minprice int
DECLARE #maxprice int
DECLARE #sizefilter int
DECLARE #colourfilter int
DECLARE #searchstring varchar(255)
DECLARE #totalrows int
-- Variables values for testing
SET #categoryid = 0
SET #minprice = 0
SET #maxprice = 0
SET #sizefilter = 0
SET #colourfilter = 0
SET #searchstring = 'origin mens'
-- Setup paging table
DECLARE #indextable table (rownum int identity(1,1), recordid int);
BEGIN
-- First run CTE recursively over all categories in hierarchy
;WITH categoryCTE AS (
SELECT cat.id as CategoryId, cat.name as CategoryName
FROM dbo.shopcategory AS cat
WHERE (#categoryid = 0 OR cat.id = #categoryid)
AND cat.isenabled = 1
UNION ALL
SELECT child.id as CategoryId, child.name as CategoryName
FROM dbo.ShopCategory AS child
INNER JOIN categoryCTE AS parent
ON child.parentid = parent.CategoryId
WHERE child.isenabled = 1
),
-- Now join CTE onto products tables via linker product_shopcategory
productsCTE AS (
SELECT p.id, ppc.shopcategoryid, ppc.listorder as catlistorder
FROM categoryCTE as cat
INNER JOIN product_shopcategory ppc ON ppc.shopcategoryid = cat.CategoryId
INNER JOIN product p ON ppc.productid = p.id
INNER JOIN productlocality pl ON pl.productid = p.id
-- ** SEARCH - Join List to Table function of keywords
INNER JOIN dbo.udf_parseList(#searchString, ' ') s
ON (cat.CategoryName + p.Name + p.stylenumber LIKE '%' + s.array_Value + '%')
LEFT JOIN product_quantity pq ON pq.productid = p.id AND pq.localityid = #localityid
LEFT JOIN productcolour pc ON pc.productid = p.id
LEFT JOIN productcolourswatch pcs ON pc.productcolourswatchid = pcs.id
LEFT JOIN product_productsize pps ON pps.productid = p.id
LEFT JOIN productsize ps ON pps.productsizeid = ps.id
WHERE p.isenabled = 1
AND pq.quantity > 1
AND (pc.isenabled IS NULL OR pc.isenabled = 1)
AND (#minprice = 0 OR pl.price >= #minprice)
AND (#maxprice = 0 OR pl.price <= #maxprice)
-- Colour Group Filters
AND (#colourfilter = 0
OR
(pcs.swatchgroupid = #colourfilter AND (pq.productcolourid = pc.id AND pq.quantity > 0))
)
-- Size Group Filters
AND (#sizefilter = 0
OR
(ps.sizegroupid = #sizefilter AND (pq.productsizeid = pps.productsizeid AND pq.quantity > 0))
)
)
-- Create Paging table of results and strip out duplicates with group by
INSERT INTO #indextable (recordid)
SELECT DISTINCT id
FROM productsCTE
GROUP BY id
ORDER BY id;
Finally solved it! I almost went down the path of creating a full tag table structure so that i could search directly against keyword tags rather than the direct data, however in trying to script a product tags table containing a nesting of the category hierarchy I found the solution which was quite simple.
In the solution procedure below i've created a new column in the CategoryCTE to hold a comma delimited list of category names that is built recursively and this then tracks the full tree for the supplied CategoryId. Now that i have a comma delimited list of Category names, I can then factor this into my 2nd CTE and perform a standard LIKE clause factoring in Product Name, Style Number, and Category Names. Finally in order to make this search a little smarter i made the keyword search inclusive of all keywords so that "mens origin" will only return products matching both of these keywords as oppose to any matches, and this was done using the NOT EXISTS clause.
Hope this helps someone else it performs very fast as well!
-- Variable Declarations
DECLARE #categoryid int
DECLARE #minprice int
DECLARE #maxprice int
DECLARE #sizefilter int
DECLARE #colourfilter int
DECLARE #searchstring varchar(255)
DECLARE #totalrows int
-- Variables values for testing
SET #categoryid = 0
SET #minprice = 0
SET #maxprice = 0
SET #sizefilter = 0
SET #colourfilter = 0
SET #searchstring = 'origin mens'
-- Setup paging table
DECLARE #indextable table (rownum int identity(1,1), recordid int);
BEGIN
-- First run CTE recursively over all categories in hierarchy inclusive of supplied categoryId
;WITH categoryCTE AS (
SELECT cat.id as CategoryId, cat.name as CategoryName,
CONVERT(varchar(255),cat.name) AS Tags
FROM dbo.shopcategory AS cat
WHERE (#categoryid = 0 OR cat.id = #categoryid)
AND cat.isenabled = 1
UNION ALL
SELECT child.id as CategoryId, child.name as CategoryName, CONVERT(varchar(255),
parent.Tags + CONVERT(varchar(32),',' + child.name)) AS Tags
FROM dbo.ShopCategory AS child
INNER JOIN categoryCTE AS parent
ON child.parentid = parent.CategoryId
WHERE child.isenabled = 1
),
-- Now join CTE onto products tables via linker product_shopcategory
productsCTE AS (
SELECT p.id, ppc.shopcategoryid, ppc.listorder as catlistorder
FROM categoryCTE as cat
INNER JOIN product_shopcategory ppc ON ppc.shopcategoryid = cat.CategoryId
INNER JOIN product p ON ppc.productid = p.id
INNER JOIN productlocality pl ON pl.productid = p.id
LEFT JOIN product_quantity pq ON pq.productid = p.id AND pq.localityid = #localityid
LEFT JOIN productcolour pc ON pc.productid = p.id
LEFT JOIN productcolourswatch pcs ON pc.productcolourswatchid = pcs.id
LEFT JOIN product_productsize pps ON pps.productid = p.id
LEFT JOIN productsize ps ON pps.productsizeid = ps.id
WHERE p.isenabled = 1
AND pq.quantity > 1
AND (pc.isenabled IS NULL OR pc.isenabled = 1)
AND pl.localityid = #localityid
AND (#minprice = 0 OR pl.price >= #minprice)
AND (#maxprice = 0 OR pl.price <= #maxprice)
-- Keyword Search filter
AND (#searchstring = '' OR NOT EXISTS
(
SELECT NULL
FROM dbo.udf_parseList(#searchString, ' ')
WHERE cat.Tags + p.Name + p.stylenumber + pc.stylenumber NOT LIKE '%' + array_Value + '%'
)
)
-- Colour Group Filters
AND (#colourfilter = 0
OR
(pcs.swatchgroupid = #colourfilter AND (pq.productcolourid = pc.id AND pq.quantity > 0))
)
-- Size Group Filters
AND (#sizefilter = 0
OR
(ps.sizegroupid = #sizefilter AND (pq.productsizeid = pps.productsizeid AND pq.quantity > 0))
)
)
-- Create Paging table of results and strip out duplicates with group by
INSERT INTO #indextable (recordid)
SELECT DISTINCT id
FROM productsCTE
GROUP BY id
ORDER BY id;
The query is:
CREATE TABLE logfile (
sequence_number BIGINT,
start_time DECIMAL,
finish_time DECIMAL,
irp_major VARCHAR(100),
offset BIGINT,
length BIGINT,
filename VARCHAR(2000),
is_seq BIT
);
//bulk insert 120 000 rows
CREATE CLUSTERED INDEX IX_logfile ON logfile(sequence_number);
UPDATE dbo.logfile SET is_seq = (
SELECT CASE WHEN a.offset = (b.offset + b.length) THEN 1 ELSE 0 END AS seq
FROM dbo.logfile a
LEFT JOIN dbo.logfile b
ON a.sequence_number = b.sequence_number + 1
WHERE a.sequence_number = logfile.sequence_number)
Is there anyway I can improve the performance of the update query?
MERGE
INTO logfile l
USING (
SELECT sequence_number + 1 AS pseq,
p.*
FROM logfile p
) pl
ON l.sequence_number = pseq
WHEN MATCHED THEN
UPDATE
SET is_seq = CASE WHEN l.offset = (pl.offset + pl.length) THEN 1 ELSE 0 END
Perhaps this update would be faster
UPDATE dbo.LogFile SET is_seq = 0
UPDATE dbo.LogFile SET is_seq = 1
FROM dbo.LogFile f
INNER JOIN (
SELECT f2.sequence_number
FROM dbo.LogFile f1
INNER JOIN dbo.LogFile f2 ON f2.sequence_number = f1.sequence_number + 1
WHERE f1.Offset <> f2.Offset + f2.Length
) f2 ON f2.sequence_number = f.sequence_number