combine tables into one table with TSQL - sql-server

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.

Related

SQL Server : how to union 3 tables with different number of rows

I have 3 tables with different data and I need to return one table that includes all 3 tables:
Customer
Contacts
Addresses
I want to get the data in only one table and one record with all details like CustomerNumber, Name, City, officeNumber...., like Union
SELECT CU.[Name]
FROM Customers CU
WHERE CustomerNumber = #CustomerId
AND IsDeleted = 0
SELECT AD.City, AD.Street
FROM [Addresses] AD
WHERE CustomerId = #CustomerId
AND IsDeleted = 0
SELECT CO.FullName, CO.OfficeNumber, CO.Email
FROM [Contacts] CO
WHERE CustomerId = #CustomerId
AND IsDeleted = 0
Result:
Number of rows doesn't matter for a union number of columns do.
What do you expect with a union?
I suppose you want you want all information (name address, contact) of customer having Id= #CustomerId
If I am right, you should make 2 join between the 3 tables using CustomerId as join clause
Edit:
https://www.db-fiddle.com/f/uGhtAZAVk64KzgThGekKk6/11
You need to JOIN the tables not UNION them. Please research SQL Server JOIN as there multiple types of joins you can do. In this case you're looking for an INNER JOIN this type of join will only return records when you have matching keys in all the tables. The key in your case is the customer id. You JOIN the tables ON customer id. Your query should look something like this:
SELECT CU.[Name], AD.City, AD.Street, CO.FullName, CO.OfficeNumber, CO.Email
FROM Customers CU
INNER JOIN [Addresses] AD
ON CU.CustomerNumber = AD.CustomerId
INNER JOIN [Contacts] CO
ON CO.CustomerId = CU.CustomerNumber
WHERE CU.CustomerNumber = #CustomerId

Cross apply vs CTE in Sql Server

I have two tables
the first table is named: tblprovince
create table (
provinceid int not null primary key (1,1) ,
provinceNme nvarchar(max),
description nvarchar(max))
the second table is named tblcity:
create table tblcity(
cityid int identity (1,1),
CityName nvarchar(max),
population int,
provinceid int foreign key references tblprovince(provinceid)
);
I need to list all provinces that have at least two large cities. A large city is defined as having a population of at least one million residents. The query must return the following columns:
tblProvince.ProvinceId
tblProvince.ProvinceName
a derived column named LargeCityCount that presents the total count of large cities for the province
 
select p.provinceId, p.provincename, citysummary.LargeCityCount
from tblprovince p
cross apply (
select count(*) as LargeCityCount from tblcity c
where c.population >= 1000000 and c.provinceid=p.provinceid
) citysummary
where citysummary.LargeCityCount
Is this query correct?
Are there other methods that allow me to achieve my goal?
SELECT tp.provinceid, tp.provinceNme, COUNT(tc.cityid) AS largecitycount
FROM tblprovince tp INNER JOIN
tblcity tc ON tc.provinceid=tp.provinceid
WHERE tc.population>=1000000
GROUP BY tp.provinceid, tp.provinceNme
HAVING COUNT(tc.cityid)>1

SQL Server query on multiple tables

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.

System Versioned (Temporal) tables in a view

I have a number of joined "system versioned" tables, e.g. Person, PhoneNumber and EmailAddress
The Person will only have one PhoneNumber and one EmailAddress at a time.
The PhoneNumber and EmailAddress will not usually be updated outside of a transaction that updates all 3 at once. (But they can be updated independently, just not in the normal scenario)
E.g. if I change the phone number, then all 3 records will be issued an update in the same transaction, hence giving them all the same "start time" in the history table.
Let's say I insert a person and then change the person's name, email address and phone number in 2 transactions:
DECLARE #Id TABLE(ID INT)
DECLARE #PersonId INT
-- Initial insert
BEGIN TRANSACTION
INSERT INTO Person (Name) OUTPUT inserted.PersonId INTO #Id VALUES ('Homer')
SELECT #PersonId = Id FROM #Id
INSERT INTO EmailAddress (Address, PersonId) VALUES ('homer#fake', #PersonId)
INSERT INTO PhoneNumber (Number, PersonId) VALUES ('999', #PersonId)
COMMIT TRANSACTION
-- Update
WAITFOR DELAY '00:00:02'
BEGIN TRANSACTION
UPDATE Person SET Name = 'Kwyjibo' WHERE PersonID = #PersonId
UPDATE EmailAddress SET Address = 'kwyjibo#fake' WHERE PersonID = #PersonId
UPDATE PhoneNumber SET Number = '000' WHERE PersonID = #PersonId
COMMIT TRANSACTION
Now I select from the view (just an inner join of the tables) using a temporal query:
SELECT * FROM vwPerson FOR SYSTEM_TIME ALL
WHERE PersonId = #PersonId
ORDER BY SysStartTime DESC
And I get returned a row for every combination of edit!
How can I query this view (if at all possible) to only return 1 row for the updates that were made in the same transaction?
I could add a WHERE clause to match all the SysStartTimes, however that would exclude those cases where a table was updated independently of the other 2.
Because of the independent updates, you actually first have to "reconstruct" a timeline, onto which you can join the data. A "sketch" of this follows, obviously don't have your actual table defns so untested:
;WITH AllTimes as (
SELECT PersonId,SysStartTime as ATime FROM Person
UNION
SELECT PersonId,SysEndTime FROM Person
UNION
SELECT PersonId,SysStartTime FROM EmailAddress
UNION
SELECT PersonId,SysEndTime FROM EmailAddress
UNION
SELECT PersonId,SysStartTime FROM PhoneNumber
UNION
SELECT PersonId,SysEndTime FROM PhoneNumber
), Ordered as (
SELECT
PersonId, ATime, ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY Atime) rn
FROM
AllTimes
), Intervals as (
SELECT
p1.PersonId,
o1.ATime as StartTime,
o2.ATime as EndTime
FROM
Ordered o1
inner join
Ordered o2
on
o1.PersonId = o2.PersonId and
o1.rn = o2.rn - 1
)
SELECT
* --TODO - Columns
FROM
Intervals i
inner join
Person p
on
i.PersonId = p.PersonId and
i.StartTime < p.SysEndTime and
p.SysStartTime < i.EndTime
inner join
Email e
on
i.PersonId = e.PersonId and
i.StartTime < e.SysEndTime and
e.SysStartTime < i.EndTime
inner join
PhoneNumber pn
on
i.PersonId = pn.PersonId and
i.StartTime < pn.SysEndTime and
pn.SysStartTime < i.EndTime
With appropriate filters if you just want one persons details, the optimizer will hopefully work it out. There may be additional filters for the joins that I've missed out also.
Hopefully you can see how the 3 CTEs construct the timeline. We take advantage of UNION eliminating duplicates in the first one.

How can I get this query to return 0 instead of null?

I have this query:
SELECT (SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)) AS TenantBalance, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
But there's a problem with it; there are other TenantID's that don't have transactions and I want to get those too.
For example, the transaction table has 3 rows for bob, 2 row for john and none for jane. I want it to return the sum for bob and john AND return 0 for jane. (or possibly null if there's no other way)
How can I do this?
Tables are like this:
Tenants
ID
Other Data
Transactions
ID
TenantID (fk to Tenants)
Other Data
(You didn't state your sql engine, so I'm going to link to the MySQL documentation).
This is pretty much exactly what the COALESCE() function is meant for. You can feed it a list, and it'll return the first non-null value in the list. You would use this in your query as follows:
SELECT COALESCE((SUM(tr.AmountPaid) - SUM(tr.AmountCharged)), 0) AS TenantBalance, te.ID
FROM tblTenant AS te
LEFT JOIN tblTransaction AS tr ON (tr.TenantID = te.ID)
GROUP BY te.ID;
That way, if the SUM() result would be NULL, it's replaced with zero.
Edited: I rewrote the query using a LEFT JOIN as well as the COALESCE(), I think this is the key of what you were missing originally. If you only select from the Transactions table, there is no way to get information about things not in the table. However, by using a left join from the Tenants table, you should get a row for every existing tenant.
Below is a full walkthrough of the problem. The function isnull has also been included to ensure that a balance of zero (rather than null) is returned for Tenants with no transactions.
create table tblTenant
(
ID int identity(1,1) primary key not null,
Name varchar(100)
);
create table tblTransaction
(
ID int identity(1,1) primary key not null,
tblTenantID int,
AmountPaid money,
AmountCharged money
);
insert into tblTenant(Name)
select 'bob' union all select 'Jane' union all select 'john';
insert into tblTransaction(tblTenantID,AmountPaid, AmountCharged)
select 1,5.00,10.00
union all
select 1,10.00,10.00
union all
select 1,10.00,10.00
union all
select 2,10.00,15.00
union all
select 2,15.00,15.00
select * from tblTenant
select * from tblTransaction
SELECT
tenant.ID,
tenant.Name,
isnull(SUM(Trans.AmountPaid) - SUM(Trans.AmountCharged),0) AS Balance
FROM tblTenant tenant
LEFT JOIN tblTransaction Trans ON
tenant.ID = Trans.tblTenantID
GROUP BY tenant.ID, tenant.Name;
drop table tblTenant;
drop table tblTransaction;
Select Tenants.ID, ISNULL((SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)), 0) AS TenantBalance
From Tenants
Left Outer Join Transactions Tenants.ID = Transactions.TenantID
Group By Tenents.ID
I didn't syntax check it but it is close enough.
SELECT (SUM(ISNULL(tblTransaction.AmountPaid, 0))
- SUM(ISNULL(tblTransaction.AmountCharged, 0))) AS TenantBalance
, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
I only added this because if you're intention is to take into account for one of the parts being null you'll need to do the ISNULL separately
Actually, I found an answer:
SELECT tenant.ID, ISNULL(SUM(trans.AmountPaid) - SUM(trans.AmountCharged),0) AS Balance FROM tblTenant tenant
LEFT JOIN tblTransaction trans
ON tenant.ID = trans.TenantID
GROUP BY tenant.ID

Resources