How to speed up select in 3 joint tables - c#

SELECT it.uid,it.Name,COALESCE(sum(i.Qty),0)-COALESCE(sum(s.Qty),0) as stock
FROM items it
left outer join sales_items s on it.uid=s.ItemID
left outer join inventory i on it.uid=i.uid
group by s.ItemID,i.uid,it.UID;
This is my query. This query take 59 seconds. How can I speed up this query?
my tables ->
items
UID Item
5089 JAM100GMXDFRUT
5090 JAM200GMXDFRUT
5091 JAM500GMXDFRUT
5092 JAM800GMXDFRUT
tables ->
sales_items
- slno ItemID Item Qty
- 9 5089 JAM100GMXDFRUT 5
- 10 5090 JAM200GMXDFRUT 2
- 11 5091 JAM500GMXDFRUT 1
tables ->
inventory
- slno uid Itemname Qty
- 102 5089 JAM100GMXDFRUT 10
- 200 5091 JAM500GMXDFRUT 15
- 205 5092 JAM800GMXDFRUT 20
This table has more than 6000 rows

Put indexes on the join columns
sales_items ItemID
inventory uid

If I was designing something like this I would have a query and schema that looks like this. Take note of my Idx1 indexes. I don't know about MySql but Sql Server will make use of those indexes for the sum function and this is called a covered query.
select Item.ItemID, Item.Name, IsNull(sum(inv.Quantity), 0) - IsNull(sum(s.Quantity), 0) as stock
from Item
Left Join Inventory inv
On Item.ItemID = inv.ItemID
Left Join Sales s
On Item.ItemID = s.ItemID
Group by Item.ItemID, Item.Name
Create Table dbo.Location
(
LocationID int not null identity constraint LocationPK primary key,
Name NVarChar(256) not null
)
Create Table dbo.Item
(
ItemID int not null identity constraint ItemPK primary key,
Name NVarChar(256) not null
);
Create Table dbo.Inventory
(
InventoryID int not null identity constraint InventoryPK primary key,
LocationID int not null constraint InventoryLocationFK references dbo.Location(LocationID),
ItemID int not null constraint InventoryItemFK references dbo.Item(ItemID),
Quantity int not null,
Constraint AK1 Unique(LocationID, ItemID)
);
Create Index InventoryIDX1 on dbo.Inventory(ItemID, Quantity);
Create Table dbo.Sales
(
SaleID int not null identity constraint SalesPK primary key,
ItemID int not null constraint SalesItemFK references dbo.Item(ItemID),
Quantity int not null
);
Create Index SalesIDX1 on dbo.Sales(ItemID, Quantity);

Aside from indexes on the tables to optimize joins, you are also doing a group by of the S.ItemID instead of just using the IT.UID since that is the join basis, and part of the main FROM table of the query... if that is an available index on the items table, use that and you are done. No need to reference the sales_items or inventory column names in the group by.
Now, that being said, another problem you will run into the way you have it is a Cartesian result if you have more than one record for the same "item id" you are summing from sales_items and inventory as I have extremely simplified an example for you via
CREATE TABLE items (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(5) NOT NULL,
PRIMARY KEY (`uid`)
);
CREATE TABLE sales_items (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`itemid` int(11),
`qty` int(5) NOT NULL,
PRIMARY KEY (`sid`),
KEY byItemAndQty (`itemid`,`qty`)
);
CREATE TABLE inventory (
`iid` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) NOT NULL,
`qty` int(5) NOT NULL,
PRIMARY KEY (`iid`),
KEY byItemAndQty (`itemid`,`qty`)
);
insert into items ( uid, name ) values ( 1, 'test' );
INSERT INTO sales_items ( sid, itemid, qty ) VALUES ( 1, 1, 1 );
INSERT INTO sales_items ( sid, itemid, qty ) VALUES ( 2, 1, 2 );
INSERT INTO inventory ( iid, uid, qty ) VALUES ( 1, 1, 13 );
INSERT INTO inventory ( iid, uid, qty ) VALUES ( 2, 1, 35 );
Simple 1 item,
Sales items 2 records for Item 1.. Qty of 1 and 2, total = 3
Inventory 2 records for Item 1.. Qty of 13 and 35, total 38
SELECT
it.uid,
it.Name,
sum(i.Qty) as iQty,
sum(s.Qty) as sQty,
COALESCE( sum(i.Qty),0) - COALESCE(sum(s.Qty),0) as stock
FROM
items it
left outer join sales_items s
on it.uid = s.ItemID
left outer join inventory i
on it.uid = i.uid
group by
it.uid
So, the result of the query you MIGHT EXPECT the Stock to be
uid name iQty sQty stock
1 test 48 3 45
but in reality becomes
1 test 96 6 90
NOW... PLEASE TAKE NOTE OF MY ASSUMPTION, but see similar sum()s or count()s from multiple tables like this. I am assuming the ITEMS table is one record per item.
The Sales_Items actually has more columns than provided (such as sales details and every date/sales count could be tracked) and MAY CONTAIN Multiple sales record quantities for a given item id (thus matching my sample).. Finally, the Inventory table similarly could have more than one record per same item, such as purchases of incoming inventory tracked by date and thus multiple records per a given item id (also matching my example).
To prevent this type of Cartesian result, and can also increase speed, I would do pre-aggregates per secondary table and join to that.
SELECT
it.uid,
it.Name,
i.iQty,
s.sQty,
COALESCE( i.iQty,0) - COALESCE(s.sQty,0) as stock
FROM
items it
left join ( select itemid, sum( qty ) as SQty
from sales_items
group by itemid ) s
on it.uid = s.ItemID
left join ( select uid, sum( qty ) as IQty
from inventory
group by uid ) i
on it.uid = i.uid
group by
it.uid
And you get the correct values of
uid name iQty sQty stock
1 test 48 3 45
Yes, this was only for a single item ID to prove the point, but still applies to as many inventory items as you have and respective sales/inventory records that may (or not) exist for certain items.

Related

Assigning even number of FK's in a table based on an even number

I have two tables where I have a situation like this:
Table1
column1
column2
column3
column4
column5
Table 2 structure:
Table2
Col1
Col2
Col3
Table1FK
My table 1 currently has 3 records inside it, and the table 2 shall contain more records than table 1 (one to many relationship between these two). I want to assign an even number of records from Table 2 to table 1 FK's.
For example:
If table 2 has 20 records and if table 1 has 3 records
I will divide these two and get an even number, 6.66 in this case.
So the table1 PK's should be assigned like
6-6-8
or
7 7 6 (this one is more even)
And then table 1 PK under identity let's say 1500 would have 7 of it's corresponding FK's in table 2 , 7 for Identity 1501 , and 6 for identity 1502
Starting point is that I should divide these:
var evenAmountOfFKs = table2.Count()/table1.Count();
What would be the next step here, and how could I achieve this ?
Can someone help me out?
No matter how weird the requirement seems, the math was fun.
At first, we have to determine the number of parents:
DECLARE #NoParents int = (SELECT COUNT(*) FROM Table1);
Both parents and children can be numbered starting with 0, and then a child can be assigned to parent x with x = ChildNo % #NoParents:
DECLARE #NoParents int = (SELECT COUNT(*) FROM Table1);
WITH
Parents AS (
SELECT column1, column2, column3, column4, column5,
ROW_NUMBER() OVER(ORDER BY column1)-1 AS ParentNo
FROM Table1
),
Children AS (
SELECT Col1, Col1, Col1,
(ROW_NUMBER() OVER(ORDER BY Col1)-1) % #NoParents AS ParentNo
FROM Table2
)
SELECT p.column1, p.column2, p.column3, p.column4, p.column5, c.Col1, c.Col1
FROM Parents p
INNER JOIN Children c ON p.ParentNo = c.ParentNo;
This will produce an "even" assignment.

ASP.Net C# - Datalist Not Complex Enough - Adding Item Data From Secondary Tables

I have a datalist that is generated from a database table with the query that is something like this.
SELECT Product_Type, Unit_Type, Image FROM Products WHERE ProductID = 1 Or ProductID = 2 OR ProductID = 3... etc.
I want to display a pie graph for each item in the datalist that would rely on data from another table for the chart. The data in this secondary table can be selected with ProductID as well.
My issue is that the datalist seems to be run from a single SELECT to a table. Is there a way for me to instruct the datalist to retrieve supplemental data from a secondary table for EACH item in the datalist?
I think you need to read up on how to join tables, here is an article to get you started: Join Syntax
Below is a simple example of a join to get more information on a product. We really don't know much about the table with the other data, but hopefully this will get you started. In this example you can see how to get the sales amount based on the foreign key in the prodcut sales table:
CREATE TABLE #product
(
productId INT PRIMARY KEY,
description VARCHAR(50) NOT NULL
)
CREATE TABLE #productSales
(
salesId INT PRIMARY KEY,
productId INT NOT NULL,
amount INT NOT NULL
)
INSERT INTO #product VALUES(1, 'Boat')
INSERT INTO #product VALUES(2, 'Raft')
INSERT INTO #product VALUES(3, 'Canoe')
INSERT INTO #productSales VALUES(1, 1, 10)
INSERT INTO #productSales VALUES(2, 1, 89)
INSERT INTO #productSales VALUES(3, 2, 410)
INSERT INTO #productSales VALUES(4, 2, 10997)
INSERT INTO #productSales VALUES(5, 2, 3)
INSERT INTO #productSales VALUES(6, 2, 98)
INSERT INTO #productSales VALUES(7, 3, 14)
SELECT p.productId, p.description, ps.amount from #product p
INNER JOIN #productSales ps on
p.productId = ps.productId
WHERE p.productId IN (1,2,3)
OUTPUT:
1 Boat 10
1 Boat 89
2 Raft 410
2 Raft 10997
2 Raft 3
2 Raft 98
3 Canoe 14

How do I update a value in a table based on some date interval?

So I have a Product table with a price.
I just want to update that price in a date interval.
Example:
apple is 10
And from 2018-02-02 to 2018-03-02 I want to update the price to 12.
I mention that I create a second table PriceInterval to insert the date interval.
enter code herePseudocode for my sqlscript
Hopefully I have understood the question correctly. A possible solution to what you're describing will mean changing the schema slightly. Having a new field in the product intervals for a new price between two dates allows you still keep the default price, when the interval is over. And using a query to select the price based on the date.
DROP TABLE IF EXISTS product_intervals;
DROP TABLE IF EXISTS products;
CREATE TABLE products (
ID INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(50) NOT NULL,
Price DECIMAL(18,1) NOT NULL,
CONSTRAINT pkProductId PRIMARY KEY (ID)
);
CREATE TABLE product_intervals (
ID INT NOT NULL AUTO_INCREMENT,
ProductId INT NOT NULL,
DateTo DATETIME NOT NULL,
DateFrom DATETIME NOT NULL,
NewPrice DECIMAL(18,1) NOT NULL,
CONSTRAINT pkProductIntervalId PRIMARY KEY (ID),
CONSTRAINT fkProductId FOREIGN KEY (ProductId) REFERENCES `products`(`ID`) ON DELETE CASCADE
);
INSERT INTO products (`Name`, Price)
SELECT 'Apple', 0.10
UNION ALL SELECT 'Banana', 0.20;
INSERT INTO product_intervals (ProductId, DateTo, DateFrom, NewPrice)
SELECT 2, STR_TO_DATE('2018-02-10T00:00:00','%Y-%m-%dT%H:%i:%s'), STR_TO_DATE('2018-02-15T00:00:00','%Y-%m-%dT%H:%i:%s'), 0.30;
select p.`Name`, CASE WHEN pi.NewPrice IS NOT NULL THEN pi.NewPrice ELSE p.Price END AS Price
FROM products p
left join product_intervals pi ON p.ID = pi.ProductId AND DateFrom > NOW() AND DateTo < NOW();
The key part to this is the left join and the CASE statement in the select query. This code hasnt been fully tested, so it might need a bit of tweaking but hopefully will give you a good indication of what a solution could be.

How to insert Duplicated data only into an Event Log?

I need help regarding a SQL query problem. I have a query where I am able to delete the duplicates but I also need to create records of the duplicated data being deleted into a EventLog in which I am clueless about it. Below is an example of my Student Table. From the table below, you can see only Alpha and Bravo are duplicated
id Name Age Group
-----------------------
1 Alpha 11 A
2 Bravo 12 A
3 Alpha 11 B
4 Bravo 12 B
5 Delta 11 B
As I am copying data from Group A to Group B, I need to find & delete the duplicated data in group B. Below is my query on deleting duplicates from Group B.
DELETE Student WHERE id
IN (SELECT tb.id
FROM Student AS ta
JOIN Student AS tb ON ta.name=tb.name AND ta.age=tb.age
WHERE ta.GroupName='A' AND tb.GroupName='B')
Here is an example of my eventlog and how I want the query that I execute to like.
id Name Age Group Status
------------------------------------------
1 Alpha 11 B Delete
2 Bravo 11 B Delete
Instead of inserting the entire Group B data into the eventlog, is there any query that can just insert the Duplicated Data into the event log?
If we are speaking about Microsoft sql, key is output clause, more details here https://msdn.microsoft.com/en-us/library/ms177564.aspx
declare #Student table
( id int, name nvarchar(20), age int,"groupname" char(1))
insert into #student values (1, 'Alpha' , 11, 'A' ),
(2, 'Bravo' , 12, 'A'),
(3 ,'Alpha' , 11 , 'B'),
(4 ,'Bravo' ,12 , 'B'),
(5 ,'Delta' ,11 , 'B')
declare #Event table
( id int, name nvarchar(20), age int,"groupname" char(1),"Status" nvarchar(20))
select * from #Student
DELETE #Student
output deleted.*, 'Deleted' into #Event
WHERE id
IN (SELECT tb.id
FROM #Student AS ta
JOIN #Student AS tb ON ta.name=tb.name AND ta.age=tb.age
WHERE ta.GroupName='A' AND tb.GroupName='B')
select * from #event
Run this before the Delete above. Not sure how you decide what one is the duplicate but you can use Row_Number to list them with the non duplicate at as 1 and and then insert everything with a row_Number > 1
; WITH cte AS
(
SELECT Name
,Age
,[Group]
,STATUS = 'Delete'
,RID = ROW_NUMBER ( ) OVER ( PARTITION BY Name,Age ORDER BY Name)
FROM Student AS ta
JOIN Student AS tb ON ta.name=tb.name AND ta.age=tb.age
)
INSERT INTO EventLog
SELECT Name,Age,[Group],'Delete'
FROM cte
WHERE RID > 1
you need to create basic trigger after delete in student table, this query will be executed after any deletion process in student table and will insert deleted record into log_table
create trigger deleted_records
on student_table
after delete
as
begin
insert into log_table
select d.id, d.Name, d.Age, d.Group, 'DELETED'
from DELETED d;
end

Recursively update values based on rows parent id SQL Server

I have the following table structure
| id | parentID | count1 |
2 -1 1
3 2 1
4 2 0
5 3 1
6 5 0
I increase count values from my source code, but i also need the increase in value to bubble up to each parent id row until the parent id is -1.
eg. If I were to increase count1 on row ID #6 by 1, row ID #5 would increase by 1, ID #3 would increase by 1, and ID #2 would increase by 1.
Rows also get deleted, and the opposite would need to happen, basically subtracting the row to be deleted' value from each parent.
Thanks in advance for your insight.
I'm using SQL Server 2008, and C# asp.net.
If you really want to just update counts, you could want to write stored procedure to do so:
create procedure usp_temp_update
(
#id int,
#value int = 1
)
as
begin
with cte as (
-- Take record
select t.id, t.parentid from temp as t where t.id = #id
union all
-- And all parents recursively
select t.id, t.parentid
from cte as c
inner join temp as t on t.id = c.parentid
)
update temp set
cnt = cnt + #value
where id in (select id from cte)
end
SQL FIDDLE EXAMPLE
So you could call it after you insert and delete rows. But if your count field are depends just on your table, I would suggest to make a triggers which will recalculate your values
You want to use a recursive CTE for this:
with cte as (
select id, id as parentid, 1 as level
from t
union all
select cte.id, t.parentid, cte.level + 1
from t join
cte
on t.id = cte.parentid
where cte.parentid <> -1
) --select parentid from cte where id = 6
update t
set count1 = count1 + 1
where id in (select parentid from cte where id = 6);
Here is the SQL Fiddle.

Categories

Resources