SQL Server query on multiple tables - sql-server

I have a SQL Server database with a large products table and a category table.
In the product table there is a category column which contains the ID of the corresponding category.
What I have to do is perform a LIKE query on both the product name column and the corresponding category name and return all the matching.
What kind of approach should I use considering I want to minimize the load on the server?
EDIT to improve this question:
Results:
product1 name->red apple catId->3 (cat_id = 3, "best apples")
product2 name->green apple catId->5 (cat_id = 5, "good fruits")
product3 name->green banana catId->8 (cat_id = 8, "apples & bananas")

If i understand correctly, this will help you -
create table products(
ID INT PRIMARY KEY,
[Name] NVARCHAR(MAX),
[CatId] INT
)
CREATE TABLE CATEGORIES(
ID INT PRIMARY KEY,
[Name] NVARCHAR(MAX)
)
insert into CATEGORIES values(1, 'Laptop')
insert into CATEGORIES values(2, 'Fruit')
insert into products values (2, 'Washington Apple', 2)
insert into products values (1, 'Apple mac book pro', 1)
select * from products
SELECT p.[Name] as ProductName, c.[Name] as CategoryName FROM products p LEFT JOIN CATEGORIES c ON p.CatId = c.ID Where p.[Name] like '%apple%'
This code will join the two tables and return the products whose name matches the keyword and return their category names as well. You can modify the definition of products and categories to include other columns you want. You may also want to add FK constraint on catid.

Related

I am trying fetch data from sqlit3 database and haveing this ambiguous column name problem, I don't see any issue, need an explanation

I am Trying to fetch the list of movies where two people are star in the same movies here is the table format:
CREATE TABLE people (
id INTEGER,
name TEXT NOT NULL,
birth NUMERIC,
PRIMARY KEY(id)
);
CREATE TABLE stars (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
FOREIGN KEY(movie_id) REFERENCES movies(id),
FOREIGN KEY(person_id) REFERENCES people(id)
);
CREATE TABLE movies (
id INTEGER,
title TEXT NOT NULL,
year NUMERIC,
PRIMARY KEY(id)
);
Query:
Running the below query is giving me ambiguous column name: movie_id, i don't understand what is the issue here,
select movie_id from (
(select movie_id,person_id from (
select id from people where name = "Johnny Depp") as x
inner join
stars on x.id = stars.person_id) as xx
inner join
(select movie_id,person_id from (
select id from people where name = "Helena Bonham Carter") as y
inner join
stars on y.id = stars.person_id) as yy
on xx.movie_id = yy.movie_id
);
It is easier to group by the movies and select only those groups having the 2 different names from the where clause
select s.movie_id
from people p
join stars s on p.id = s.person_id
where p.name in ('Johnny Depp', 'Helena Bonham Carter')
group by s.movie_id
having count(distinct p.name) = 2
The issue with your query is that when selecting from 2 tables with the same column name you have to tell the DB which column you want to use by adding the table name
select xx.movie_id ...

Select all Main table rows with detail table column constraints with GROUP BY

I've 2 tables tblMain and tblDetail on SQL Server that are linked with tblMain.id=tblDetail.OrderID for orders usage. I've not found exactly the same situation in StackOverflow.
Here below is the sample table design:
/* create and populate tblMain: */
CREATE TABLE tblMain (
ID int IDENTITY(1,1) NOT NULL,
DateOrder datetime NULL,
CONSTRAINT PK_tblMain PRIMARY KEY
(
ID ASC
)
)
GO
INSERT INTO tblMain (DateOrder) VALUES('2021-05-20T12:12:10');
INSERT INTO tblMain (DateOrder) VALUES('2021-05-21T09:13:13');
INSERT INTO tblMain (DateOrder) VALUES('2021-05-22T21:30:28');
GO
/* create and populate tblDetail: */
CREATE TABLE tblDetail (
ID int IDENTITY(1,1) NOT NULL,
OrderID int NULL,
Gencod VARCHAR(255),
Quantity float,
Price float,
CONSTRAINT PK_tblDetail PRIMARY KEY
(
ID ASC
)
)
GO
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(1, '1234567890123', 8, 12.30);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(1, '5825867890321', 2, 2.88);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '7788997890333', 1, 1.77);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '9882254656215', 3, 5.66);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '9665464654654', 4, 10.64);
GO
Here is my SELECT with grouping:
SELECT tblMain.id,SUM(tblDetail.Quantity*tblDetail.Price) AS TotalPrice
FROM tblMain LEFT JOIN tblDetail ON tblMain.id=tblDetail.orderid
WHERE (tblDetail.Quantity<>0) GROUP BY tblMain.id;
GO
This gives:
The wished output:
We see that id=2 is not shown even with LEFT JOIN, as there is no records with OrderID=2 in tblDetail.
How to design a new query to show tblMain.id = 2? Mean while I must keep WHERE (tblDetail.Quantity<>0) constraints. Many thanks.
EDIT:
The above query serves as CTE (Common Table Expression) for a main query that takes into account payments table tblPayments again.
After testing, both solutions work.
In my case, the main table has 15K records, while detail table has some millions. With (tblDetail.Quantity<>0 OR tblDetail.Quantity IS NULL) AND tblDetail.IsActive=1 added on JOIN ON clause it takes 37s to run, while the first solution of #pwilcox, the condition being added on the where clause, it ends up on 29s. So a gain of time of 20%.
tblDetail.IsActive column permits me ignore detail rows that is temporarily ignored by setting it to false.
So the for me it's ( #pwilcox's answer).
where (tblDetail.quantity <> 0 or tblDetail.quantity is null)
Change
WHERE (tblDetail.Quantity<>0)
to
where (tblDetail.quantity <> 0 or tblDetail.quantity is null)
as the former will omit id = 2 because the corresponding quantity would be null in a left join.
And as HABO mentions, you can also make the condition a part of your join logic as opposed to your where statement, avoiding the need for the 'or' condition.
select m.id,
totalPrice = sum(d.quantity * d.price)
from tblMain m
left join tblDetail d
on m.id = d.orderid
and d.quantity <> 0
group by m.id;

combine tables into one table with TSQL

We have three tables, Agencies, Regions and Countries which we wish to combine into a new Countries table.
The old schema is
oldSchema.Agencies
AgentId PK,int
TaxName varchar(3)
TaxRate decimal(18,3)
oldSchema.Regions
RegionCode PK,tinyint
AgentId int
oldSchema.Countries
CountryCode PK,varchar(2)
CountryName varchar(50)
RegionCode tinyint
Our organisation no longer users agencies and regions so we want to combine the agency data into a newSchema.Countries table.
The new schema is
newSchema.Countries
CountryCode PK,varchar(2)
CountryName varchar(50)
TaxName varchar(3)
TaxRate decimal(18,3)
The following update query is incorrect in that it inserts identical data into every row
INSERT INTO newSchema.Countries (
CountryCode
,CountryName
,TaxName
,TaxRate
)
SELECT OldSchema.Countries.CountryCode
,OldSchema.Countries.CountryName
,OldSchema.Agencies.TaxName
,OldSchema.Agencies.TaxRate
FROM OldSchema.Agencies
INNER JOIN (
OldSchema.Regions INNER JOIN OldSchema.Countries ON OldSchema.Regions.RegionCode = OldSchema.Countries.RegionCode
) ON OldSchema.Agencies.AgentId = OldSchema.Regions.AgentId
WHERE NewSchema.Countries.CountryCode = OldSchema.Countries.CountryCode
How do we insert the TaxName and TaxRate for each agency into the new Countries table such that each country gets the correct tax as it was applied from the Agencies table?
I am not sure about the problem you are facing but I think you have multiple regions with the same AgentId resulting in repeated data for a specific country.
If that is your problem try the below query, otherwise provide more details for helping you.
INSERT INTO newSchema.Countries
(CountryCode,CountryName,TaxName,TaxRate)
select distinct
c.CountryCode,
c.CountryName,
a.TaxName,
a.TaxRate
from oldSchema.Agencies a
inner join oldSchema.Regions r on a.AgentId = r.AgentId
inner join oldSchema.Countries c on r.RegionCode = c.RegionCode
Also check this fiddle with sample data that I've created to demostration purposes.

How can i model groups of persons?

I need to model groups of persons and I can't find a way to design tabels to do it efficiently.
Groups can be thought as sets, unordered collections of one or more persons, each group should be uniquely identified by its components.
Edit: and a person can be part of more than one group.
My first attempt looks like this.
A table which contains all "persons" managed by the system.
table Persons(
id int,
name varchar,
(other data...)
)
a table that contains groups and all group properties:
table Groups(
group_id int,
group_name varchar,
(other data...)
)
and a table with the association between persons and groups
table gropus_persons (
person_id int,
group_id in
)
This design doesn't fit well with this requirements because it is hard to write the query to retrieve the group id from a list of components.
The only query I could come up to find the group composed by persons (1, 2, 3) looks like this:
select *
from groups g
where
g.group_id in (select group_id from gropus_persons where person_id = 1)
and g.group_id in (select group_id from gropus_persons where person_id = 2)
and g.group_id in (select group_id from gropus_persons where person_id = 3)
and not exists (select 1 from gropus_persons where group_id = g.group_id and person_id not in (1,2,3))
the problem is that the number of components is variable so I can only use a dynamically generated query and add a subquery for each component each time I need to find a new group.
Is there a better solution?
Thank you in advice for the help!
You need to group by the "group" and count how many hits you receive. For this, you only need the intersection table:
select GroupID, count(*) as MemberCount
from GroupsPersons
where PersonID in( 1, 2, 3 )
group by GroupID
having count(*) = 3;
The problem comes with making this query suitable for a varying list of person id values. As you seem to already realize this will require dynamic SQL, the pseudo-code will look something like this:
stmt := 'select GroupID, count(*) as MemberCount '
|| 'from GroupsPersons '
|| 'where PersonID in( ' || CSVList || ' ) '
|| 'group by GroupID '
|| 'having count(*) = ' || length( CSVList );
The one potential bug you have to be wary of is if the same id repeats in the list. For example: CSVList := '1, 2, 3, 2';
This will generate a correct count(*) value of 3, but the having clause will be looking for 4.
Another solution to consider is to pivot/xpath the set of person IDs in alpha sequence and store it in your groups table and compare that string with your target.
For your example, you'd use Select group_id from groups where personIDs = '1,2,3,'
How about this, I think the schema is the same as yours, not sure:
create table Groups(
group_id int primary key,
group_name varchar(100)
);
create table Persons(
person_id int primary key,
name varchar
);
create table Membership(
group_id int REFERENCES Groups (group_id),
person_id int REFERENCES Persons (person_id)
);
INSERT INTO Persons
VALUES (1, 'p1'),
(2, 'p2'),
(3, 'p2'),
(4, 'p2');
INSERT INTO Groups
VALUES (1, 'group1'),
(2, 'group2');
INSERT INTO Membership
VALUES (1, 1),
(1, 2),
(2, 2),
(1, 3);
Then select:
select p.name, g.group_name
from Persons as p
join Membership as m on p.person_id = m.person_id
join Groups as g on g.group_id = m.group_id
where m.group_id in (1, 2);
Obviously data would need to be adjusted to suit yours.

TSQL to insert a set of rows and dependent rows

I have 2 tables:
Order (with a identity order id field)
OrderItems (with a foreign key to order id)
In a stored proc, I have a list of orders that I need to duplicate. Is there a good way to do this in a stored proc without a cursor?
Edit:
This is on SQL Server 2008.
A sample spec for the table might be:
CREATE TABLE Order (
OrderID INT IDENTITY(1,1),
CustomerName VARCHAR(100),
CONSTRAINT PK_Order PRIMARY KEY (OrderID)
)
CREATE TABLE OrderItem (
OrderID INT,
LineNumber INT,
Price money,
Notes VARCHAR(100),
CONSTRAINT PK_OrderItem PRIMARY KEY (OrderID, LineNumber),
CONSTRAINT FK_OrderItem_Order FOREIGN KEY (OrderID) REFERENCES Order(OrderID)
)
The stored proc is passed a customerName of 'fred', so its trying to clone all orders where CustomerName = 'fred'.
To give a more concrete example:
Fred happens to have 2 orders:
Order 1 has line numbers 1,2,3
Order 2 has line numbers 1,2,4,6.
If the next identity in the table was 123, then I would want to create:
Order 123 with lines 1,2,3
Order 124 with lines 1,2,4,6
On SQL Server 2008 you can use MERGE and the OUTPUT clause to get the mappings between the original and cloned id values from the insert into Orders then join onto that to clone the OrderItems.
DECLARE #IdMappings TABLE(
New_OrderId INT,
Old_OrderId INT)
;WITH SourceOrders AS
(
SELECT *
FROM Orders
WHERE CustomerName = 'fred'
)
MERGE Orders AS T
USING SourceOrders AS S
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (CustomerName )
VALUES (CustomerName )
OUTPUT inserted.OrderId,
S.OrderId INTO #IdMappings;
INSERT INTO OrderItems
SELECT New_OrderId,
LineNumber,
Price,
Notes
FROM OrderItems OI
JOIN #IdMappings IDM
ON IDM.Old_OrderId = OI.OrderID

Resources