How should I index the following MariaDB table? - database

I have this sample table to hold some tenant data and I am not sure how to index it. I've been reading about database compound/composite indexes and came up with the following, based on how I want to query my data:
The test table has 2.396.500 rows and has these columns
tenant_id | UNSIGNED BIGINT
code | UNSIGNED BIGINT
status_a | UNSIGNED TINYINT (0 or 1)
status_b | UNSIGNED TINYINT (0 or 1)
param_a | VARCHAR(100)
param_s | VARCHAR(100)
param_c | VARCHAR(100)
param_b | VARCHAR(100)
value_a | unsigned int
value_b | unsigned int
I need to query a row by it's code, since it's a tenant table I'll query it as follows
select * from myTable where tenant_id = 1 and code = 1;
Besides searching for a specific code, my other filters always includes one of the two status columns "status_a" and "status_b" and "param_a" might also be included. So the query goes as:
select * from mytable where tenant_id = 1 and status_a = 1;
select * from mytable where tenant_id = 1 and status_b = 1;
select * from mytable where tenant_id = 1 and status_a = 1 and param_a = 'stuff';
select * from mytable where tenant_id = 1 and status_b = 1 and param_a = 'stuff';
Columns "param_s", "param_c" and "param_b" are kind of dependent on each other, due to the app rules. If I'm querying for "param_b", "param_c" will always be present. And if I'm querying for param "param_b", "param_s" will always be present. Query should be as follows:
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_b = 'stuff_b' and param_a = 'stuff_a';
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_b = 'stuff_b';
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_a = 'stuff_a';
Columns value_a and value_b are always compared using ">=" or "<=";
Looking for each case individualy, I'd think something as
Compound index on tenant_id and code
I'm not sure columns "status_a" and "status_b" should be indexed at all (only 0 and 1), but... Indexes on ("tenant_id", "status_a", "param_a") and ("tenant_id", "status_b", "param_a").
Index on ("tenant_id", "status_a", "param_s", "param_c", "param_b","param_a") and ("tenant_id", "status_b", "param_s", "param_c", "param_b","param_a")
Individual indexes for value columns
But after setting this up I feel like these are too many indexes (is there such a thing)?

For query #1:
select * from myTable where tenant_id = 1 and code = 1;
you can create the index:
create index ix1 on myTable (tenant_id, code);
For the queries in section #2:
select * from mytable where tenant_id = 1 and status_a = 1;
select * from mytable where tenant_id = 1 and status_b = 1;
select * from mytable where tenant_id = 1 and status_a = 1 and param_a = 'stuff';
select * from mytable where tenant_id = 1 and status_b = 1 and param_a = 'stuff';
you can create the indexes:
create index ix2 on mytable (tenant_id, status_a, param_a);
create index ix3 on mytable (tenant_id, status_b, param_a);
For the queries in section #3:
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_b = 'stuff_b' and param_a = 'stuff_a';
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_b = 'stuff_b';
select * from mytable where tenant_id = 1 and status_a = 1 and param_s = 'stuff_s' and param_c = 'stuff_c' and param_a = 'stuff_a';
I would create the somewhat generic index:
create index ix4 on myTable (tenant_id, status_a, param_s, param_c);
For the inequalities that would depend on the specific query.

Related

How to SUM a column value in MSSQL

I am new to mssql ,Here I need to SUM a column values.
But in my case have some joins between the tables finally I have a column with 2 rows of output .
What I want to do is I want to SUM the final output of my query.
This is my query :
SELECT
SUM(ESCD.ITEM_QTY) * ((SELECT COLOC_PROD_PRICE
FROM LOM_LNK_PROD_COMP
WHERE COLOC_PROD_CODE = ITEM_ID)
/
((SELECT LMUL.UOL_CONV_QTY
FROM LOM_MST_UOM_LINK AS LMUL
JOIN LOM_MST_PRODUCT AS LMP
ON LMUL.UOL_MAIN_UOM_CODE = LMP.PROD_STOCK_UOM
AND LMP.PROD_CODE = ESCD.ITEM_ID)/LMUL.UOL_CONV_QTY )) AS 'TOTAL_AMOUNT'
FROM EC_SHOPPING_CART_DETAIL AS ESCD
JOIN LOM_MST_UOM_LINK AS LMUL
ON LMUL.UOL_MAIN_UOM_CODE = ITEM_PACK_SIZE
WHERE CREATED_BY = 'xyz'
AND CHECK_OUT = 'FALSE'
GROUP BY ITEM_ID,LMUL.UOL_CONV_QTY
Output :
Expected AS :
can anyone help me to solve this .
Try the following:
SELECT
SUM(X.TOTAL_AMOUNT) AS 'TOTAL_AMOUNT'
FROM
(
SELECT
SUM(ESCD.ITEM_QTY) * ((SELECT COLOC_PROD_PRICE
FROM LOM_LNK_PROD_COMP
WHERE COLOC_PROD_CODE = ITEM_ID)
/
((SELECT LMUL.UOL_CONV_QTY
FROM LOM_MST_UOM_LINK AS LMUL
JOIN LOM_MST_PRODUCT AS LMP
ON LMUL.UOL_MAIN_UOM_CODE = LMP.PROD_STOCK_UOM
AND LMP.PROD_CODE = ESCD.ITEM_ID)/LMUL.UOL_CONV_QTY )) AS 'TOTAL_AMOUNT'
FROM EC_SHOPPING_CART_DETAIL AS ESCD
JOIN LOM_MST_UOM_LINK AS LMUL
ON LMUL.UOL_MAIN_UOM_CODE = ITEM_PACK_SIZE
WHERE CREATED_BY = 'xyz'
AND CHECK_OUT = 'FALSE'
GROUP BY ITEM_ID,LMUL.UOL_CONV_QTY
) X;

T-SQL Query for Vertical Table Structure

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

Using Partition and Join in MS SQL Server

I have the following example set of data in SQL:
I need to select for RequestType = 1 and ToRoleID = 1 the maximum step where there is no record with other ToRoleID.
Example: If I filter by RequestType = 1 and ToRoleID = 1, this should give me only the row with Request_ID = 5.
Can I do it only with partition, JOIN?
Thank you.
This might be what you're wanting.
DECLARE #RequestType INT = 1,
#ToRoleID INT = 1
SELECT *
FROM ( SELECT *,
MAX(ToRoleID) OVER (PARTITION BY Request_ID) MaxRole
FROM myTable
WHERE me.RequestType = #RequestType) mt
WHERE MaxRole = #ToRoleID

Update doesnt work with condition

I am trying to update BudCustomers.ImportedRecord Column and BudCustomers.STATUSID Column. The data for these two columns are 1 and 1. so i am trying to update to 0 for both but that doesnt work with this below query
--Update into BudCustomers from Bulk
Update BudCustomers
set BudCustomers.ImportedRecord = 0
,BudCustomers.VersionID = 1
,BudCustomers.STATUSID = 0
,BudCustomers.LastModifiedUserID = 'Import'
,BudCustomers.LastModifiedDate = GETDATE()
FROM BudCustomers BCUST WITH(NOLOCK)
Where ((BCUST.STATUSID <> 1)
OR (BCUST.ImportedRecord <> 1)) AND BCUST.LegalName = 'Test, LTD. (1000)'
From your description this is probably what you want.
Update BudCustomers
set ImportedRecord = 0
, VersionID = 1
, STATUSID = 0
, LastModifiedUserID = 'Import'
, LastModifiedDate = GETDATE()
Where
(
STATUSID = 1
OR
ImportedRecord = 1
)
AND LegalName = 'Test, LTD. (1000)'

How do I use this condition inside CASE WHEN?

I want if Status column = 1
Check If there are rows in another table return 'Check' and If no rows return 'In DB'
SELECT ID, UserName,
CASE [Status]
WHEN 1 THEN
if ((Select Count(*) From Logs_TB Where Logs_TB.UserName = Users_TB.UserName) > 0)
'Check'
Else
'In DB'
WHEN 2 THEN 'Revision'
WHEN 3 THEN 'Sent'
END AS StatusName
FROM Users_TB CROSS JOIN Logs_TB
Edit 1:
I have Two Tables
in First Table.
I want to get the following result for Column [Status]
if FirstTable.ColumnStatus = 1
if SecondTable.ColumnA = FirstTable.ColumnB Has Rows
'Check'
else
'In DB'
else if FirstTable.ColumnStatus = 2
'Revision'
else if FirstTable.ColumnStatus = 3
'Sent'
Edit 2
This is an example
I want Select All Rows From Employment Table
and I want to parse column Status to Column As "StatusName"
if Status = 1 It has two values
First value Check if QualificationID and SpecializationID Has Rows
in Table 'Vacancies' return 'Check'
and if no rows in Table 'Vacancies' return 'In DB'
if Status = 2 'Revision'
if Status = 3 'Sent'
You need to change your case and add more conditions:
SELECT ID, UserName,
CASE
WHEN [Status] = 1
AND EXISTS (SELECT 1
FROM Logs_TB
WHERE Logs_TB.UserName = Users_TB.UserName) THEN 'Check'
WHEN [Status] = 1 THEN 'In DB'
WHEN [Status] = 2 THEN 'Revision'
WHEN [Status] = 3 THEN 'Sent'
ELSE NULL
END AS StatusName
FROM Users_TB
CROSS JOIN Logs_TB
For performance reason is better to use EXISTS instead of comparing COUNT with 0.
Try this
DECLARE #Cnt INT
SELECT #Cnt = COUNT(*)
FROM Logs_TB
WHERE Logs_TB.UserName = Users_TB.UserName
SELECT
ID
,UserName
,CASE WHEN [Status] = 1
THEN
CASE WHEN #Cnt > 0
THEN 'Check'
ELSE 'In DB'
END
WHEN [Status] = 2 THEN 'Revision'
WHEN [Status] = 3 THEN 'Sent'
END AS StatusName
FROM Users_TB
CROSS JOIN Logs_TB

Resources