I am summing amounts shipped by location. There are 3 total locations. Today I'm only getting results for one location as there are $0 for the other two. I want to show those locations with $0. The pic shows the location with shipments and I would like it to show the other two below it or on top with $0.
Included a snipped of the code
SELECT [Facility_Table].facility 'Facility', isnull(sum(exten),0) 'Shipped'
FROM dtorder
Here is some example DDL/DML
DECLARE #Locations TABLE (LocationID BIGINT IDENTITY, LocationName NVARCHAR(50))
DECLARE #Shipments TABLE (ShipmentID BIGINT IDENTITY, LocationID BIGINT, ShipmentAmount DECIMAL(10,4))
INSERT INTO #Locations (LocationName) VALUES
('Main Warehouse'),('New York Warehouse'),('Los Angeles Warehouse')
INSERT INTO #Shipments (LocationID, ShipmentAmount)
SELECT TOP 50 ROUND(((2 - 1) * Rnd1 + 1), 0), ROUND(((500 - 1) * Rnd1 + 1), 4)
FROM (
VALUES(
RAND(CONVERT(VARBINARY,NEWID(),1)),
RAND(CONVERT(VARBINARY,NEWID(),1))
)
) a(Rnd1,Rnd2)
CROSS APPLY sys.sysobjects
As Larnu suggests, you will need to LEFT OUTER JOIN from your locations table to the shipments table. Using a LEFT OUTER JOIN returns rows from the dataset even when there is no match to the joined table:
SELECT l.LocationName, SUM(COALESCE(s.ShipmentAmount,0)) AS TotalShipments
FROM #Locations l
LEFT OUTER JOIN #Shipments s
ON l.LocationID = s.LocationID
GROUP BY l.LocationName
LocationName
TotalShipments
Los Angeles Warehouse
0.0000
Main Warehouse
3392.0912
New York Warehouse
8722.8945
It's then just a case of aggregating over which ever columns you need.
Related
Assume I have those tables:
CREATE TABLE Employee (ID int, EmployeeIdentifier varchar(100),ManagerIdentifier varchar(100))
CREATE TABLE EmployeeManager (ID int, EmployeeID varchar(100))
INSERT Employee
VALUES
(1,'apple','apple'),
(2,'banana','apple'),
(3,'citrus','apple'),
(4,'grape','grape'),
(5,'grape','grape'),
(6,'grape','grape')
INSERT EmployeeManager
VALUES
(1,1),
(2,1),
(3,1),
(4,4),
(5,5),
(6,5)
For Employee.ID IN (1,2,3), records in EmployeeManager look fine.
But in Employee.ID IN (4,5,6) we can see many duplicates. We are not allowed to delete any records from Employee table. But we are free to assign EmpoyeeManager.EmployeeID value. Since there is only one Actual record for Grape and the rest is duplicate, I want to assign EmpoyeeManager.EmployeeID to a minimum value Employee.ID from all duplicated grape records in Employee table, aka to 4.
I have this query,
UPDATE d SET EmployeeID = l.ID
FROM dbo.EmployeeManager d
INNER JOIN Employee s on d.ID=s.ID
OUTER APPLY (
SELECT ID
FROM Employee l
WHERE s.ManagerIdentifier=l.EmployeeIdentifier
) l
WHERE
EXISTS (
SELECT d.EmployeeID
EXCEPT
SELECT l.ID
)
If you keep running it you will see that EmployeeManager.EmployeeID values for ID (4,5,6) will keep changing.
How I can I update above update statement to assign to the lowest value of Employee.ID for all EmployeeManager.ID (4,5,6), aka to 4?
We are not allowed to run one time fix script, because corrupted data to above table can keep coming.
Desired output after running above update statement should be
You need TOP (1) and ORDER BY in the subquery to pick out a specific row
UPDATE d SET EmployeeID = l.ID
FROM dbo.EmployeeManager d
INNER JOIN Employee s on d.ID=s.ID
OUTER APPLY (
SELECT TOP (1) ID
FROM Employee l
WHERE s.ManagerIdentifier = l.EmployeeIdentifier
ORDER BY ID
) l
WHERE
EXISTS (
SELECT d.EmployeeID
EXCEPT
SELECT l.ID
)
You appear to have a normalization issue, as the Manager is defined in two places
I suggest you use better aliases for your tables, they are not very memorable
You can change your OUTER to CROSS, and then you can use a standard <> instead of the EXISTS/EXCEPT
CROSS APPLY (
SELECT TOP (1) ID
FROM Employee l
WHERE s.ManagerIdentifier = l.EmployeeIdentifier
ORDER BY ID
) l
WHERE d.EmployeeID <> l.ID
I have 2 tables employees and Medical leave, which are related by the Employee ID, and basically the Medical leave table will have multiple data of a single employee who takes multiple leaves, I want to filter out the data by the month of the medical leave, and include the employees whose medical leave doesnt occur on the filtered month as a null value.
EMPLOYEES MEDICAL
|employee|ID| |ID|DateOfLeave|
A 1 1 2019/1/3
B 2 1 2019/4/15
C 3 2 2019/5/16
D 4
The sql statement i came up with filters the specific month of the leave and counts the number of times they took a leave on that month such as January, it also includes employees who doesnt have any leave as a '0', however the employees who have medical leaves which doesnt occur on January doesnt show up in the result set at all, how can i show them to have 0 medical leaves in the month of january?
select employees.employee, employees.ID,
count(medical.DateOfLeave) as NumberOfLeaves
from employees
left outer join medical on employees.ID = medical.ID
where (MONTH(DateOfLeave) = 1) or (medical.DateOfLeave is null)
group by employees.employee,employees.ID
RESULT SET
|Employee|ID|NumberOfLeaves|
A 1 1
C 3 0
D 4 0
As you can see B disappears,but i want it to show in the result set as a '0' like employee C and D
I know its because employee B's medical data doesnt meet the condition of the where clause, but how do i write a statement that includes employees who have medical leaves which doesnt occur on january in the result set as a 0??
Your query is only showing results for employees that have leave in January or do not have leave at all.
Instead, you only want to join to records in your medical table if there are records for the specified month, then you'll group your results and get the counts from there.
In order to do this, you need to change the condition for your join to include your Month filter.
Here's a working example using table variables
DECLARE #Employees TABLE (ID INT, Employee CHAR(1))
DECLARE #Medical TABLE (ID INT, DateOfLeave DATE)
INSERT INTO #Employees
VALUES (1, 'A'), (2,'B'), (3,'C'), (4, 'D')
INSERT INTO #Medical
VALUES (1, '2019-01-03'), (1, '2019-04-15'), (2, '2019-05-16')
SELECT e.employee,
e.ID,
count(m.DateOfLeave) AS NumberOfLeaves
FROM #Employees e
LEFT OUTER JOIN #Medical m ON e.ID = m.ID AND MONTH(DateOfLeave) = 1
GROUP BY e.employee,e.ID
left outer join will first join the table and then filter. in your case B is left out in the join itself so thats why the filter is not working. you can try this
select employees.employee, employees.ID,
nvl(count(medical.DateOfLeave),0) as NumberOfLeaves
from employees
left outer join (select * from medical
where (MONTH(DateOfLeave) = 1))
on employees.ID = medical.ID
group by employees.employee,employees.ID
or you can also try this
select e.id, employee, count(dateofleave)
from employee e, medical m
where e.id = m.id
and month(m.DateOfLeave) = 1
group by e.id, employee
UNION ALL
select id, employee, 0
from employee e
where not exists(select 1 from medical m
where e.id = m.id
and month(m.DateOfLeave) = 1
I have 3 tables that are inter-linked between each other. The design of the tables are as below.
First (PK:FirstID, vchar:Name, int:Year)
Second (PK:SecondID, FK:FirstID, int:Day, int:Month)
Third (PK:ThirdID, FK:SecondID, int:Speed, vchar:Remark)
I'm trying to copy records from 3 inter-linked tables from Database A to Database B. So my Transact-SQL looks something like this:
INSERT INTO First
(Name, Year)
SELECT Name, Year
FROM DB_A.dbo.First
WHERE Year >= 1992
INSERT INTO Second
(FirstID, Day, Month)
SELECT FirstID, Day, Month
FROM DB_A.dbo.Second S INNER JOIN
DB_A.dbo.First F ON S.FirstID = F.FirstID
WHERE Month > 6
INSERT INTO Third
(SecondID, Speed, Remark)
SELECT SecondID, Speed, Remark
FROM DB_A.dbo.Third T INNER JOIN
DB_A.dbo.Second S ON T.SecondID = S.SecondID INNER JOIN
DB_A.dbo.First F ON F.FirstID = S.FirstID
WHERE Remark <> NULL
These statements works all well and fine until the starting position of First.FirstID in Database A and B becomes not the same due to the three tables in Database B being empty. Hence, the constraint on foreign_key error is produced.
Possible Solutions
Reuse old First.FirstID One of the solution I have figured out is to use reuse the old First.FirstID from Database A. This can be done by setting SET IDENTITY_INSERT TableName ON just before the insert into TableName and including the TableName.TableNameID into the insert statement. However, I'm advised against doing this by my colleagues.
Overwrite Second.FirstID with new First.FirstID and subsequently, Third.SecondID with the new Second.SecondID I'm trying to apply this solution using OUTPUT and TABLE variable by outputting all First.FirstID into a temporary table variable and associate them with table Second similar to this answer However, I'm stuck on how to associate and replace the Second.FirstIDs with the correct IDs in the temporary table. An answer on how to do this would also be accepted as the answer for this question.
Using solution No. 1 and Update the primary and foreign keys using UPDATE CASCADE. I just got this idea but I have a feeling it will be very tedious. More research needs to be done but if there's an answer that shows how to implement this successfully, then I'll accept that answer.
So how do I copy records from 3 inter-linked tables to another 3 similar tables but different primary keys? Are there any better solutions than the ones proposed above?
You can use OUTPUT Clause.
CREATE TABLE #First (NewId INT PRIMARY KEY, OldId INT)
INSERT INTO First
(
Name,
Year,
OldId -- Added new column
)
OUTPUT Inserted.FirstID, Inserted.OldId INTO #First
SELECT
Name,
Year,
FirstID -- Old Id to OldId Column
FROM
DB_A.dbo.First
WHERE
Year >= 1992
Second Table
CREATE TABLE #Second (NewId INT PRIMARY KEY, OldId INT)
INSERT INTO Second
(
FirstID,
Day,
Month,
OldId -- Added new column
)
OUTPUT Inserted.SecondID, Inserted.OldId INTO #Second
SELECT
OF.NewId, --FirstID
Day,
Month,
SecondID
FROM
DB_A.dbo.Second S INNER JOIN
DB_A.dbo.First F ON S.FirstID = F.FirstID INNER JOIN
#First OF ON F.FirstId = OF.OldId -- Old ids here
WHERE
Month > 6
Last one
INSERT INTO Third
(
SecondID,
Speed,
Remark
)
SELECT
OS.NewId, -- SecondID
Speed,
Remark
FROM
DB_A.dbo.Third T INNER JOIN
DB_A.dbo.Second S ON T.SecondID = S.SecondID INNER JOIN
DB_A.dbo.First F ON F.FirstID = S.FirstID INNER JOIN
#Second OS ON S.SecondID = OS.OldId
WHERE Remark <> NULL
First Solution
Using MERGE and OUTPUT together
OUTPUT combined with MERGE function has the ability to retrieve the old primary keys before inserting into the table.
Second Solution
NOTE: This solution only works if you are sure that you have another column that has its values unique in the table besides the table's primary key.
You may use this column as a link between the table in the source database and its sister table in the target database. The code below is an example taking into account that First.Name has unique values when month > 6.
-- no changes to insert code in First table
INSERT INTO First
(Name, Year)
SELECT Name, Year
FROM DB_A.dbo.First
WHERE Year >= 1992
INSERT INTO Second
(FirstID, Day, Month)
SELECT CurrentF.FirstID, Day, Month -- 2. Use the FirstID that has been input in First table
FROM DB_A.dbo.Second S INNER JOIN
DB_A.dbo.First F ON S.FirstID = F.FirstID INNER JOIN
First CurrentF ON CurrentF.Name = F.Name -- 1. Join Name as a link
WHERE Month > 6
INSERT INTO Third
(SecondID, Speed, Remark)
SELECT CurrentS.SecondID, Speed, Remark --5. Get the proper SecondID
FROM DB_A.dbo.Third T INNER JOIN
DB_A.dbo.Second S ON T.SecondID = S.SecondID INNER JOIN
DB_A.dbo.First F ON F.FirstID = S.FirstID INNER JOIN
First CurrentF ON CurrentF.Name = F.Name INNER JOIN -- 3. Join using Name as Link
Second CurrentS ON CurrentS.FirstID= CurrentF.FirstID -- 4. Link Second and First table to get the proper SecondID.
WHERE Remark <> NULL
My question is can we join a table A to resultant table of inner join of table A and B without using subquery, CTE or temp tables ?
I am using SQL Server.
I will explain the situation with an example
The are two tables GoaLScorers and GoalScoredDetails.
GoaLScorers
gid Name
-----------
1 A
2 B
3 A
GoalScoredDetails
DetailId gid stadium goals Cards
---------------------------------------------
1 1 X 2 1
2 2 Y 5 2
3 3 Y 2 1
The result I am expecting is if I select a stadium 'X' (or 'Y')
I should get name of all who may or may not have scored there, also aggregate total number of goals,total cards.
Null value is acceptable for names if no goals or no cards.
I can get the result I am expecting with the below query
SELECT
gs.name,
SUM(goal) as TotalGoals,
SUM(cards) as TotalCards
FROM
(SELECT
gid, stadium, goal, cards
FROM
GoalScoredDetails
WHERE
stadium = 'Y') AS vtable
RIGHT OUTER JOIN
GoalScorers AS gs ON vtable.gid = gs.gid
GROUP BY
gs.name
My question is can we get the above result without using a subquery or CTE or temp table ?
Basically what we need to do is OUTER JOIN GoalScorers to resultant virtual table of INNER JOIN OF GoalScorers and GoalScoredDetails.
But I am always faced with ambiguous column name error as "gid" column is present in GoalScorers and also in resultant table. Error persists even if I try to use alias for column names.
I have created a sql fiddle for this her: http://sqlfiddle.com/#!3/40162/8
SELECT gs.name, SUM(gsd.goal) AS totalGoals, SUM(gsd.cards) AS totalCards
FROM GoalScorers gs
LEFT JOIN GoalScoredDetails gsd ON gsd.gid = gs.gid AND
gsd.Stadium = 'Y'
GROUP BY gs.name;
IOW, you could push your where criteria onto joining expression.
The error Ambiguous column name 'ColumnName' occurs when SQL Server encounters two or more columns with the same and it hasn't been told which to use. You can avoid the error by prefixing your column names with either the full table name, or an alias if provided. For the examples below use the following data:
Sample Data
DECLARE #GoalScorers TABLE
(
gid INT,
Name VARCHAR(1)
)
;
DECLARE #GoalScoredDetails TABLE
(
DetailId INT,
gid INT,
stadium VARCHAR(1),
goals INT,
Cards INT
)
;
INSERT INTO #GoalScorers
(
gid,
Name
)
VALUES
(1, 'A'),
(2, 'B'),
(3, 'A')
;
INSERT INTO #GoalScoredDetails
(
DetailId,
gid,
stadium,
goals,
Cards
)
VALUES
(1, 1, 'x', 2, 1),
(2, 2, 'y', 5, 2),
(3, 3, 'y', 2, 1)
;
In this first example we recieve the error. Why? Because there is more than one column called gid it cannot tell which to use.
Failed Example
SELECT
gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
This example works because we explicitly tell SQL which gid to return:
Working Example
SELECT
gs.gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
You can, of course, return both:
Example
SELECT
gs.gid,
gsd.gid
FROM
#GoalScoredDetails AS gsd
RIGHT OUTER JOIN #GoalScorers as gs ON gs.gid = gsd.gid
;
In multi table queries I would always recommend prefixing every column name with a table/alias name. This makes the query easier to follow, and reduces the likelihood of this sort of error.
BookID Title ReleaseYear
1 The Hobbit 1937
2 Atlas Shrugged 1957
BookID Cost BookPrinterID
1 12 38
BookID Charge BookPublisherID
1 39 148
2 45 151
That's the book publishing tables data I have.
Books table, Cost table and Charge table.
I would like to see cost information if present else charge data from single query. i.e.
BookID Cost
1 12
2 45
This is my query
select Books.BookID, ISNULL(Cost.Cost, Charge.Charge) AS Cost
from Books
left join Cost on Books.BookID = Cost.BookID
left join Charge on Book.BookID = Charge.BookID
That works, but problem is if there are more tables to join or more columns to retrieve, having ISNULL condition for every column will become a big blob of text, especially if you have to parse xml.
ISNULL(Cost.Xchange.value('/Partner[1]/#Sales[1]', 'nvarchar(500)'), Charge.Xchange.value('/Partner[1]/#Sales[1]', 'nvarchar(500)')) AS Xchange
My question is, is there a neater way to write this query?
Using an INNER join on both tables, will omit the row for bookid=2 as there is no record in the cost table for it. You likely want to use LEFT OUTER JOIN instead of INNER JOIN for both tables to show all rows.
That said, what you want is some sort of intermediate table that is a combination of cost/charge but only when there is no record in cost.
You could do something like this, where you populate a new table with all the cost records, and then all the charge records (where no cost is found in the new table)... Although, there would undoubtedly be a performance hit by creating/writing to a new table on the fly. I wouldn't want to do this in a large library.
Here's a sql fiddle too.
create table #books (bookid int, title varchar(50), releaseyear int)
create table #charge (bookid int, charge money, publisherid int)
create table #cost (bookid int, cost money, bookprinterid int)
insert into #books
select 1, 'The Hobbit', 1937 union
select 2, 'Atlas Shrugged', 1957
insert into #cost
select 1, 12, 38
insert into #charge
select 1, 39, 148 union
select 2, 45, 151
create table #allcosts (bookid int, cost money)
insert into #allcosts
select c1.bookid, c1.cost from #cost c1
insert into #allcosts
select c2.bookid, c2.charge
from #charge c2
left outer join #allcosts c3 on c2.bookid=c3.bookid
where c3.bookid is null
select b.BookID, c.cost AS Cost
from #Books b
inner join #allcosts c on b.bookid = c.bookid
If all rows in Books are not in Cost you can't use inner joins on the 3 tables. Otherwise in your example BookID=2 will never be returned. There is no way around using isNull if you are going to join more tables and retrieve more columns.
select bookid, isNull(cost, charge)
from Books b
inner join Charge c
on bt.bookid = c.bookid
left join Cost co
on co.bookid = bt.bookid