I need to create a stored procedure which returns products characteristics with its products count. Input parameters are:
- product category. The query has to return data for this particular category and its child category
- selected product characteristics - table valued parameter. If this parameter contains rows then the query has to calculate products count for each characteristic for products which has this particular characteristics too.
Questions:
- Database contains 500 000+ product rows, so what would be the best solution (t-sql query) in terms of perfomance?
- I've tried to make a query presented below but I think it's ugly and actually doesn't calculate products count correctly. And I need a professional assistance to make a correct query which works as fast as possible
Table scripts and sample data:
--categories
create table tCategory(c_id int identity(1,1) primary key, c_name nvarchar(200), c_parent int)
insert into tCategory(c_name, c_parent) select 'Smartphones', null
insert into tCategory(c_name, c_parent) select 'iPhone 6S', 1
insert into tCategory(c_name, c_parent) select 'iPhone 7', 1
insert into tCategory(c_name, c_parent) select 'iPhone 7 Plus', 1
--products
create table tProduct(p_id int identity(1,1) primary key, p_name nvarchar(200), c_id int)
insert into tProduct(p_name, c_id) select '4.7" Apple iPhone 6S 32 gb gold', 2
insert into tProduct(p_name, c_id) select '4.7" Apple iPhone 6S 32 gb brown', 2
insert into tProduct(p_name, c_id) select '4.7" Apple iPhone 7 32 gb pink', 3
insert into tProduct(p_name, c_id) select '4.7" Apple iPhone 7 32 gb brown', 3
insert into tProduct(p_name, c_id) select '5.5" Apple iPhone 7 Plus 32 gb black', 4
insert into tProduct(p_name, c_id) select '4.7" Apple iPhone 6S 128 gb pink', 2
--characteristics type (color, size etc.)
create table tProductCharItem(pci_id int identity(1,1) primary key, pci_name nvarchar(200))
insert into tProductCharItem(pci_name) select 'Display'
insert into tProductCharItem(pci_name) select 'Color'
insert into tProductCharItem(pci_name) select 'Memory'
--characteristics value (blue, 50х50 etc.)
create table tProductCharItemValue(pciv_id int identity(1,1) primary key, pci_id int, pciv_value nvarchar(50))
insert into tProductCharItemValue(pci_id, pciv_value) select 1, '4.7"'
insert into tProductCharItemValue(pci_id, pciv_value) select 1, '5.5"'
insert into tProductCharItemValue(pci_id, pciv_value) select 2, 'gold'
insert into tProductCharItemValue(pci_id, pciv_value) select 2, 'brown'
insert into tProductCharItemValue(pci_id, pciv_value) select 2, 'pink'
insert into tProductCharItemValue(pci_id, pciv_value) select 2, 'black'
insert into tProductCharItemValue(pci_id, pciv_value) select 3, '32 gb'
insert into tProductCharItemValue(pci_id, pciv_value) select 3, '128 gb'
--products characteristics
create table tProductChar(pc_id int identity(1,1) primary key, p_id int, pciv_id int)
insert into tProductChar(p_id, pciv_id) select 1, 1
insert into tProductChar(p_id, pciv_id) select 1, 7
insert into tProductChar(p_id, pciv_id) select 1, 3
insert into tProductChar(p_id, pciv_id) select 2, 1
insert into tProductChar(p_id, pciv_id) select 2, 4
insert into tProductChar(p_id, pciv_id) select 2, 7
insert into tProductChar(p_id, pciv_id) select 3, 1
insert into tProductChar(p_id, pciv_id) select 3, 5
insert into tProductChar(p_id, pciv_id) select 3, 7
insert into tProductChar(p_id, pciv_id) select 4, 1
insert into tProductChar(p_id, pciv_id) select 4, 4
insert into tProductChar(p_id, pciv_id) select 4, 7
insert into tProductChar(p_id, pciv_id) select 5, 2
insert into tProductChar(p_id, pciv_id) select 5, 6
insert into tProductChar(p_id, pciv_id) select 5, 7
insert into tProductChar(p_id, pciv_id) select 6, 1
insert into tProductChar(p_id, pciv_id) select 6, 5
insert into tProductChar(p_id, pciv_id) select 6, 8
Expected result when user didn't select any filter:
+--------+---------+----------+------------+----------------+
| pci_id | pciv_id | pci_name | pciv_value | products_count |
+--------+---------+----------+------------+----------------+
| 1 | 1 | Display | 4.7" | 5 |
| 2 | 3 | Color | gold | 1 |
| 2 | 4 | Color | brown | 2 |
| 2 | 5 | Color | pink | 2 |
| 2 | 6 | Color | black | 1 |
| 3 | 7 | Memory | 32 gb | 5 |
| 3 | 8 | Memory | 128 gb | 1 |
| 1 | 2 | Display | 5.5" | 1 |
+--------+---------+----------+------------+----------------+
Expected result when user selected filter by color characteristic 'brown'
+--------+---------+----------+------------+----------------+
| pci_id | pciv_id | pci_name | pciv_value | products_count |
+--------+---------+----------+------------+----------------+
| 1 | 1 | Display | 4.7" | 2 |
| 2 | 3 | Color | gold | 0 |
| 2 | 4 | Color | brown | 2 |
| 2 | 5 | Color | pink | 0 |
| 2 | 6 | Color | black | 0 |
| 3 | 7 | Memory | 32 gb | 2 |
| 3 | 8 | Memory | 128 gb | 0 |
| 1 | 2 | Display | 5.5" | 0 |
+--------+---------+----------+------------+----------------+
Here is my try (ugly and doesn't calculate products count properly):
CREATE TYPE integer_list_tbltype AS TABLE (n int NOT NULL PRIMARY KEY)
declare @c_id int = 1 --category id
declare @pciv_ids integer_list_tbltype --list of selected filters (products characteristics)
insert into @pciv_ids(n) select 4
;with cats as
(
select c_id from tCategory where c_id = @c_id
union all
select t.c_id from cats
inner join tCategory t on cats.c_id = t.c_parent
),
groupped_pci as (
select distinct p.c_id, pci.pci_id, pciv.pciv_id
from tProductChar pc
join tProduct p on pc.p_id = p.p_id
join tProductCharItemValue pciv on pc.pciv_id = pciv.pciv_id
join tProductCharItem pci on pciv.pci_id = pci.pci_id),
products_count as (
select count(distinct p.p_id) cnt, pc.pciv_id
from tProduct p join tProductChar pc on p.p_id = pc.p_id
cross apply (select * from tProductChar pc left join @pciv_ids t on pc.pciv_id = t.n where p_id = p.p_id and pc.pciv_id is not null) t
group by pc.pciv_id
)
select pci.pci_id, pciv.pciv_id, pci.pci_name, pciv.pciv_value, pc.cnt products_count
from groupped_pci
join cats on cats.c_id = groupped_pci.c_id
join tProductCharItem pci on groupped_pci.pci_id = pci.pci_id
join tProductCharItemValue pciv on groupped_pci.pciv_id = pciv.pciv_id
left join products_count pc on groupped_pci.pciv_id = pc.pciv_id
