Ik heb een database waarin profielen staan. Deze worden opgeslagen in tabel 1. Vervolgens heb ik een 2e tabel waarin eigenschappen van deze profielen staan (tabel 2). Bij eigenschappen moet je denken aan gegevens welke de leden kunnen instellen op hun profiel.
Hieronder de definitie van de tabellen:
tbdProfile:
Mijn probleem
Bezoekers kunnen op de site zoeken in de profielen. Er kan gezocht worden in de vaste velden van een profiel (geslacht, leeftijd, nickname etc) en in de eigenschappen van een profiel.
Wanneer je de 2 tabellen bij elkaar joint krijg je een dataset waarin alle benodigde gegevens zitten om in te zoeken: je hebt de vaste gegevens/kolommen van een profiel en daarnaast voor elke eigenschap een nieuw record met de naam en value.
Voorbeeld:
Wil je nu zoeken op bijvoorbeeld geslacht ='man' en op minimaal 2 eigenschappen loop je tegen het probleem aan dat de vaste gegevens in elk record terug komt, echter de eigenschappen in aparte records zitten. Ga ik zoeken op "alle velden" (AND) lukt dit mij niet omdat je op meerdere records wilt filteren.
Ik heb dit nu opgelost door in een Stored Procedure een Crosstab (dynamisch) aan te maken van de eigenschappen tabel, waarbij elke eigenschap een kolom wordt en de Value van die eigenschap de waarde van die kolom is.
Hierbij loop ik tegen de beperkingen aan van de maximale lengte van een varchar in SQL (8000 karakters) aangezien de site een aantal soorten profielen heeft met elk zijn eigen eigenschappen. Ook het aantal eigenschappen begint langzaam aan te groeien waardoor de 8000 karakters bijna gehaald worden.
Ik wil dit dus aan gaan passen, maar weet zo even niet hoe ik dit het beste kan oplossen. Ik heb nu een nieuwe variant gemaakt, waarbij er (wederom) dynamisch een query wordt gemaakt in een Stored Procedure waarin de verschillende eigenschappen waarop gezocht moet worden in de where wordt geplaatst, vervolgens wordt er gezocht op de vaste eigenschappen van het profiel en daarna wordt er gecontroleerd (indien gezocht op alles/AND) of de eigenschappen inderdaad allemaal gevonden zijn. Echt optimaal is dit volgens mij niet en volgens mij kan dit beter, echter weet ik even niet hoe.
Hieronder de SP zoals deze nu gebruikt wordt.
De SP zoals ik deze aangepast hebt.
De aanroep van de eerste SP is:
De 2e is:
Zijn er betere manieren om te zoeken in een results waarin je wilt filteren op meerdere rijen?
Ik hoop dat bovenstaande een beetje duidelijk is.
Hieronder de definitie van de tabellen:
tbdProfile:
- ID (guid)
- Naam (varchar)
- NickName (varchar)
- Geslacht (varchar)
- Leeftijd (int)
- ProfileType (guid) verwijst naar tbdProfileType)
- ...
- ID (guid)
- ProfielID (guid)
- SettingName (guid) (verwijst naar tbdSectionItem)
- SettingValue (varchar)
Mijn probleem
Bezoekers kunnen op de site zoeken in de profielen. Er kan gezocht worden in de vaste velden van een profiel (geslacht, leeftijd, nickname etc) en in de eigenschappen van een profiel.
Wanneer je de 2 tabellen bij elkaar joint krijg je een dataset waarin alle benodigde gegevens zitten om in te zoeken: je hebt de vaste gegevens/kolommen van een profiel en daarnaast voor elke eigenschap een nieuw record met de naam en value.
Voorbeeld:
| ID Profiel | Naam | Geslacht | Nickname | SettingName | SettingValue |
| (guid) | Nummer1 | Man | Nr1 | eigenschap1 | waarde1 |
| (guid) | Nummer1 | Man | Nr1 | eigenschap2 | waarde2 |
| (guid) | Nummer2 | Vrouw | Nr2 | eigenschap1 | waarde3 |
Wil je nu zoeken op bijvoorbeeld geslacht ='man' en op minimaal 2 eigenschappen loop je tegen het probleem aan dat de vaste gegevens in elk record terug komt, echter de eigenschappen in aparte records zitten. Ga ik zoeken op "alle velden" (AND) lukt dit mij niet omdat je op meerdere records wilt filteren.
Ik heb dit nu opgelost door in een Stored Procedure een Crosstab (dynamisch) aan te maken van de eigenschappen tabel, waarbij elke eigenschap een kolom wordt en de Value van die eigenschap de waarde van die kolom is.
Hierbij loop ik tegen de beperkingen aan van de maximale lengte van een varchar in SQL (8000 karakters) aangezien de site een aantal soorten profielen heeft met elk zijn eigen eigenschappen. Ook het aantal eigenschappen begint langzaam aan te groeien waardoor de 8000 karakters bijna gehaald worden.
Ik wil dit dus aan gaan passen, maar weet zo even niet hoe ik dit het beste kan oplossen. Ik heb nu een nieuwe variant gemaakt, waarbij er (wederom) dynamisch een query wordt gemaakt in een Stored Procedure waarin de verschillende eigenschappen waarop gezocht moet worden in de where wordt geplaatst, vervolgens wordt er gezocht op de vaste eigenschappen van het profiel en daarna wordt er gecontroleerd (indien gezocht op alles/AND) of de eigenschappen inderdaad allemaal gevonden zijn. Echt optimaal is dit volgens mij niet en volgens mij kan dit beter, echter weet ik even niet hoe.
Hieronder de SP zoals deze nu gebruikt wordt.
SQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
| CREATE PROCEDURE usp_SearchProfilesV2 @intCount int =-1, @strWhere varchar(4000) = '', @strOrder varchar(300) = ' fldNickname ', @intProfileType int = 2 AS declare @strField varchar(100) declare @strSQL varchar(8000) declare curFields cursor for SELECT tbdSectionItem.fldID FROM tbdSectionItem INNER JOIN tbdSection ON tbdSection.fldID = tbdSectionItem.fldSectionID INNER JOIN tbdProfileType ON tbdProfileType.fldID = tbdSection.fldProfileTypeID WHERE tbdSectionItem.fldDeleted = 0 AND tbdProfileType.fldType = @intProfileType /* SELECT fldID FROM tbdSectionItem WHERE fldDeleted = 0 */ set @strSQL = '' open curFields fetch next from curFields into @strField while @@FETCH_STATUS = 0 begin set @strSQL = @strSQL + ' MAX ( CASE WHEN ''' + LOWER(@strField) + ''' = fldSectionItemID THEN fldAllValue ELSE '''' END) AS ''K' + REPLACE(LOWER(@strField), '-', '') + ''', ' fetch next from curFields into @strField end close curFields deallocate curFields if substring(@strSQL, len(@strSQL), 5) = ', ' begin set @strSQL = substring(@strSQL, 0, len(@strSQL)) end if len(@strSQL) > 0 set @strSQL = ', ' + @strSQL IF OBJECT_ID('tempdb..##tmpSearch') IS NOT NULL DROP TABLE ##tmpSearch set @strSQL = 'SELECT fldID, fldAge ' + @strSQL + ' INTO ##tmpSearch FROM vwProfileListSearchInfo GROUP BY fldID, fldAge, fldUnique, fldNickname ORDER BY fldUnique' print @strSQL exec (@strSQL) if @intCount > -1 set @strSQL = 'SELECT TOP ' + CAST(@intCount AS varchar(10)) else set @strSQL = 'SELECT ' set @strSQL = @strSQL + ' tbdProfile.fldID, ' + @strOrder + ' FROM ##tmpSearch INNER JOIN tbdProfile ON tbdProfile.fldID = ##tmpSearch.fldID INNER JOIN tbdProfileType ON tbdProfileType.fldID = tbdProfile.fldProfileTypeID ' set @strSQL = @strSQL + ' WHERE fldNeedUpdate = 0 ' if @intProfileType > 0 set @strSQL = @strSQL + ' AND tbdProfileType.fldType = ' + CAST(@intProfileType AS varchar(10)) if len(@strWhere) > 0 set @strSQL = @strSQL + ' AND (' + @strWhere + ') ' -- AND fldProfileType = ' + CAST(@intProfileType AS varchar(10)) /* if @intProfileType = 0 set @strSQL = @strSQL + ' WHERE 1=1 ' if @intProfileType > 0 set @strSQL = @strSQL + ' WHERE tbdProfileType.fldType = ' + CAST(@intProfileType AS varchar(10)) if len(@strWhere) > 0 set @strSQL = @strSQL + ' AND (' + @strWhere + ') ' -- AND fldProfileType = ' + CAST(@intProfileType AS varchar(10)) */ set @strSQL = @strSQL + ' GROUP BY tbdProfile.fldID, ' + @strOrder set @strSQL = @strSQL + ' ORDER BY ' + @strOrder exec (@strSQL) drop table ##tmpSearch |
De SP zoals ik deze aangepast hebt.
SQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
| CREATE PROCEDURE usp_SearchProfilesV3 @intCount int =-1, @strWhereProfile varchar(4000) = '', @strWhereProfileItem varchar(4000) = '', @strOrder varchar(300) = ' fldNickname ', @intProfileType int = 2, @intSearchType int = 1, @intColumnCount int = 1 AS DECLARE @strSQL varchar(8000) DECLARE @strSQLWhere varchar(8000) -- Import voorbeeld --SET @strWhereProfile = ' ( fldSex = ''sex_female'' ) ' --SET @strWhereProfileItem = ' (fldSectionItemID = ''6fb324cd-7da9-4df2-bd13-45c99b7fe8a0'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''0e22021f-5145-492e-a9ef-710689ca6c00'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''6255ee85-afed-40a2-85e4-f53a6692c0f8'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''600930dd-bce5-4963-9b2a-79f106d83806'' AND fldAllValue = ''1'') ' --SET @intColumnCount = 4 --SET @intSearchType = 1 --SET @strOrder = ' fldNickName ' --SET @intProfileType = 2 SET @strSQLWhere = '' -- Step 1: Collect information in temporary table CREATE TABLE #tmpProfileSearchIDs ( fldID uniqueidentifier ) SET @strSQL = ' INSERT INTO #tmpProfileSearchIDs SELECT fldID FROM vwProfileListSearchInfo ' IF LEN(@strWhereProfile) > 0 OR LEN(@strWhereProfileItem) > 0 BEGIN SET @strSQLWhere = ' ( ' END IF LEN(@strWhereProfileItem) > 0 BEGIN SET @strSQLWhere = @strSQLWhere + ' ( ' + @strWhereProfileItem + ' ) ' END IF LEN(@strWhereProfile) > 0 BEGIN IF @intSearchType = 1 AND LEN(@strWhereProfileItem) > 0 -- AND BEGIN SET @strSQLWhere = @strSQLWhere + ' AND ' END IF @intSearchType = 2 AND LEN(@strWhereProfileItem) > 0 -- OR BEGIN SET @strSQLWhere = @strSQLWhere + ' OR ' END SET @strSQLWhere = @strSQLWhere + ' ( ' + @strWhereProfile + ' ) ' END IF LEN(@strWhereProfile) > 0 OR LEN(@strWhereProfileItem) > 0 BEGIN SET @strSQLWhere = @strSQLWhere + ') ' END IF @intProfileType > 0 BEGIN IF LEN(@strSQLWhere) > 0 BEGIN SET @strSQLWhere = ' ( ' + @strSQLWhere + ' ) AND ' END SET @strSQLWhere = @strSQLWhere + ' ( fldProfileType = ' + CAST(@intProfileType AS VARCHAR(2)) + ' ) ' END IF LEN(@strSQLWhere) > 0 BEGIN SET @strSQLWhere = ' WHERE ' + @strSQLWhere END SET @strSQL = @strSQL + @strSQLWhere + ' GROUP BY fldID' IF @intSearchType = 1 AND LEN(@strWhereProfileItem) > 0 -- AND BEGIN SET @strSQL = @strSQL + ' HAVING COUNT(fldNickName) = ' + CAST(@intColumnCount AS VARCHAR(10)) END EXEC (@strSQL) if @intCount > -1 set @strSQL = 'SELECT TOP ' + CAST(@intCount AS varchar(10)) else set @strSQL = 'SELECT ' SET @strSQL = @strSQL + ' tbdProfile.* FROM tbdProfile INNER JOIN #tmpProfileSearchIDs ON #tmpProfileSearchIDs.fldID = tbdProfile.fldID WHERE fldNeedUpdate = 0 ' IF LEN(@strOrder) > 0 BEGIN SET @strSQL = @strSQL + ' ORDER BY ' + @strOrder END EXEC (@strSQL) DROP TABLE #tmpProfileSearchIDs |
De aanroep van de eerste SP is:
SQL:
1
| EXEC usp_SearchProfilesV2 @strWhere='KF2BC799FC20E4562B9CB486C4286D6ED = 1 AND K77F8F50C0683449DA31D572A1E750554 = 1' |
De 2e is:
SQL:
1
2
3
4
| EXEC usp_SearchProfilesV3 @intCount = 1000000, @strWhereProfile = ' ( fldSex = ''sex_female'' ) ', @strWhereProfileItem = ' (fldSectionItemID = ''6fb324cd-7da9-4df2-bd13-45c99b7fe8a0'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''0e22021f-5145-492e-a9ef-710689ca6c00'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''6255ee85-afed-40a2-85e4-f53a6692c0f8'' AND fldAllValue = ''1'') OR (fldSectionItemID = ''600930dd-bce5-4963-9b2a-79f106d83806'' AND fldAllValue = ''1'') ', @intColumnCount = 4 |
Zijn er betere manieren om te zoeken in een results waarin je wilt filteren op meerdere rijen?
Ik hoop dat bovenstaande een beetje duidelijk is.