Subquery returned more than 1 value - MS SQL - sql

select Id,Prayaseeid, name,Gender,
(select name from tb_Category where id = Category) AS Category,
ideadescription,Domain,ProjectTerms,ProjectStartDate,Amountsanctioned,
(select Amount from tb_innovatorDisbursement where tbid ='TBINO1111A' and
applyingforcycle='1') AS AmountDisbursed,
projectstatus,projectoutcome
from tb_innovator
where tbid='TBINO1111A 'and applyingforcycle='1'

Use TOP to Limit rows to 1,
The Select queries in parenthesis resulting in more than one record
select Id,Prayaseeid, name,Gender,
(select TOP(1) name from tb_Category where id = Category) AS Category,
ideadescription,Domain,ProjectTerms,ProjectStartDate,Amountsanctioned,
(select TOP(1) Amount
from tb_innovatorDisbursement where tbid ='TBINO1111A' and applyingforcycle='1') AS AmountDisbursed,
projectstatus,projectoutcome from tb_innovator
where tbid='TBINO1111A 'and applyingforcycle='1'

You need to test your sub queries for the where clause to make sure the values you are searching do not have multiple records. I would use (Top 1 ) for any column other than the Id column assuming the Id column must be unique as the name suggests.

Update query
select ti.Id,
ti.Prayaseeid,
ti.name,
ti.Gender,
(select TOP(1) name from tb_Category where id = ti.Category) AS Category,
ti.ideadescription,
ti.Domain,
ti.ProjectTerms,
ti.ProjectStartDate,
ti.Amountsanctioned,
tid.Amount AS AmountDisbursed,
ti.projectstatus,
ti.projectoutcome
from tb_innovator ti
INNER JOIN tb_innovatorDisbursement tid ON tid.Tbid = ti.tbid
AND tid.applyingforcycle='1'
where ti.tbid='TBINO1111A 'and ti.applyingforcycle='1'

Perhaps you should use JOIN:
select i.Id, i.Prayaseeid, i.name, i.Gender, c.Category,
i.ideadescription, i.Domain, i.ProjectTerms, i.ProjectStartDate, i.Amountsanctioned,
id.Amount from tb_innovatorDisbursement
i.projectstatus, i.projectoutcome
from tb_innovator i left join
tb_Category c
on i.category = c.id left join
tb_innovatorDisbursement id
on id.tbid = i.dbid and id.applyingforcycle = '1'
where i.tbid = 'TBINO1111A 'and i.applyingforcycle = '1';
Note that all column names are qualified as well, indicating what table they come from.

Related

SQL Combining Two Totally seperate tables to one

I am VERY new to SQL and self taught. I have two SQL that I stuggled through but got working. Now I need to combine them into one and I'm lost.
SELECT
s.lastfirst,
s.student_number,
SUM(tr.howmany)
FROM
students s
JOIN
truancies tr ON s.id = tr.studentid
WHERE
s.enroll_status = 0 AND
s.schoolid = ~(curschoolid)
GROUP BY
s.lastfirst, s.student_number
HAVING
SUM(tr.howmany) > 0
ORDER BY
s.lastfirst
And this table:
SELECT
S.DCID as DCID,
S.LASTFIRST as LASTFIRST,
S.STUDENT_NUMBER as STUDENT_NUMBER,
S2.FC_SRVC_HRS_DUE as FC_SRVC_HRS_DUE,
CASE
WHEN S2.FC_SRVC_HRS_COMPLETED IS NULL
THEN '0'
ELSE S2.FC_SRVC_HRS_COMPLETED
END AS FC_SRVC_HRS_COMPLETED,
S2.FC_SRVC_HRS_BUYOUT as FC_SRVC_HRS_BUYOUT,
CASE
WHEN S2.FC_SRVC_HRS_COMPLETED IS NULL
THEN S2.FC_SRVC_HRS_DUE * S2.FC_SRVC_HRS_BUYOUT
ELSE ((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT)
END as Balance_Due
FROM
STUDENTS S, U_STUDENTSUSERFIELDS S2
WHERE
S.DCID = S2.STUDENTSDCID AND
s.enroll_status = 0 AND
s.schoolid = ~(curschoolid) AND
(((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT) > 0 OR
((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT) IS NULL) AND
S2.FC_SRVC_HRS_DUE >.1
ORDER BY
s.lastfirst
What I am really looking for are the totals of both of these. I want the SUM(tr.howmany) from the first table and the balance due of the second BUT I need the filters that are in there. This would be sorted by student. I hope I am making sense. Any assistance would be appreciated.
You can join together 2 separate SQL select statements:
Eg:
Select A.id, A.value, B.value
From (select id, count(*) as value from TableA ...) AS A
join (select id, sum(field) as value from TableB ...) AS B
on A.id = B.id
order by A.id
As long as you have a common field to join on this would work. In your case the student_number looks like a good candidate. You will have to do the ordering outside of your subqueries.

Only return value that matches the ID on table 1

I have tried all possible joins and sub-queries but I cant get the data to only return one value from table 2 that exactly matches the vendor ID. If I dont have the address included in the query, I get one hit for the vendor ID. How can I make it so that when I add the address, I only want the one vendor that I get prior to adding the address.
The vendor from table one must be VEN-CLASS IS NOT NULL.
This was my last attempt using subquery:
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENMAST.VENDOR_CONTCT,
APVENMAST.TAX_ID,
Subquery.ADDR1
FROM (TEST.dbo.APVENMAST APVENMAST
INNER JOIN
(SELECT APVENADDR.ADDR1,
APVENADDR.VENDOR_GROUP,
APVENADDR.VENDOR,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENADDR APVENADDR
INNER JOIN TEST.dbo.APVENMAST APVENMAST
ON (APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP)
AND (APVENADDR.VENDOR = APVENMAST.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)) Subquery
ON (APVENMAST.VENDOR_GROUP = Subquery.VENDOR_GROUP)
AND (APVENMAST.VENDOR = Subquery.VENDOR))
INNER JOIN TEST.dbo.APVENLOC APVENLOC
ON (APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENLOC.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)
Try this:
SELECT APVENMAST.VENDOR_GROUP
, APVENMAST.VENDOR
, APVENMAST.VENDOR_VNAME
, APVENMAST.VENDOR_CONTCT
, APVENMAST.TAX_ID
, APVENADDR.ADDR1
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN (
select VENDOR_GROUP, VENDOR, ADDR1
, row_number() over (partition by VENDOR_GROUP, VENDOR order by ADDR1) r
from TEST.dbo.APVENADDR
) APVENADDR
ON APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP
AND APVENADDR.VENDOR = APVENMAST.VENDOR
AND APVENADDR.r = 1
--do you need this table; you're not using it...
--INNER JOIN TEST.dbo.APVENLOC APVENLOC
--ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
--AND APVENMAST.VENDOR = APVENLOC.VENDOR
WHERE APVENMAST.VEN_CLASS IS NOT NULL
--if the above inner join was to filter results, you can do this instead:
and exists (
select top 1 1
from TEST.dbo.APVENLOC APVENLOC
ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
AND APVENMAST.VENDOR = APVENLOC.VENDOR
)
I found another column in the APVENLOC table that I can filter on to get the unique vendor. Turns out if the vendor address is for the main office, the vendor location is set blank.
Easier than I thought it would be!
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENADDR.ADDR1,
APVENMAST.VENDOR_SNAME,
APVENADDR.LOCATION_CODE,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN TEST.dbo.APVENADDR APVENADDR
ON (APVENMAST.VENDOR_GROUP = APVENADDR.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENADDR.VENDOR)
WHERE (APVENADDR.LOCATION_CODE = ' ')
Shaji

Select statement to show the corresponding user with the lowest/highest amount?

I want to write a select statement output that, among other things, has both a lowest_bid and highest_bid column. I know how to do that bit, but want I also want is to show the user (user_firstname and user_lastname combined into their own column) as lowest_bidder and highest_bidder. What I have so far is:
select item_name, item_reserve, count(bid_id) as number_of_bids,
min(bid_amount) as lowest_bid, ???, max(big_amount) as highest_bid,
???
from vb_items
join vb_bids on item_id=bid_item_id
join vb_users on item_seller_user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
sort by item_reserve
(The ???'s are where the columns should go, once I figure out what to put there!)
This seems like good use of window functions. I've assumed a column vb_bids.bid_user_id. If there's no link between a bid and a user, you can't answer this question
With x as (
Select
b.bid_item_id,
count(*) over (partition by b.bid_item_id) as number_of_bids,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount desc
) as high_row,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount
) as low_row,
b.bid_amount,
u.user_firstname + ' ' + u.user_lastname username
From
vb_bids b
inner join
vb_users u
on b.bid_user_id = u.user_id
Where
b.bid_status = 'ok'
)
Select
i.item_name,
i.item_reserve,
min(x.number_of_bids) number_of_bids,
min(case when x.low_row = 1 then x.bid_amount end) lowest_bid,
min(case when x.low_row = 1 then x.username end) low_bidder,
min(case when x.high_row = 1 then x.bid_amount end) highest_bid,
min(case when x.high_row = 1 then x.username end) high_bidder
From
vb_items i
inner join
x
on i.item_id = x.bid_item_id
Where
i.item_sold = 'no'
Group By
i.item_name,
i.item_reserve
Order By
i.item_reserve
Example Fiddle
In order to get the users, I broke out the aggregates into their own tables, joined them by the item_id and filtered them by a derived value that is either the min or max of bid_amount. I could have joined to vb_bids for a third time, and kept the aggregate functions, but that would've been redundant.
This will fail if you have two low bids of the exact same amount for the same item, since the join is on bid_amount. If you use this, then you'd want to created an index on vb_bids covering bid_amount.
select item_name, item_reserve, count(bid_id) as number_of_bids,
low_bid.bid_amount as lowest_bid, low_user.first_name + ' ' + low_user.last_name,
high_bid.bid_amount as highest_bid, high_user.first_name + ' ' + high_user.last_name
from vb_items
join vb_bids AS low_bid on item_id = low_bid.bid_item_id
AND low_bid.bid_amount = (
SELECT MIN(bid_amount)
FROM vb_bids
WHERE bid_item_id = low_bid.bid_item_id)
join vb_bids AS high_bid on item_id = high_bid.bid_item_id
AND high_bid.bid_amount = (
SELECT MAX(bid_amount)
FROM vb_bids
WHERE bid_item_id = high_bid.bid_item_id)
join vb_users AS low_user on low_bid.user_id=user_id
join vb_users AS high_user on high_bid.user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
group by item_name, item_reserve,
low_bid.bid_amount, low_user.first_name, low_user.last_name,
high_bid.bid_amount, high_user.first_name, high_user.last_name
order by item_reserve
I am a big fan of using Common Table Expressions (CTEs) for situations like this, because of the following advantages:
Separating different parts of the logic, adding to readability, and
Reducing complexity (for example, the need to GROUP BY a large number of fields, or to repeat the same join multiple times.)
So, my suggested approach would be something like this:
-- semi-colon must precede CTE
;
-- collect bid info
WITH item_bids AS (
SELECT
i.item_id, i.item_name, i.item_reserve, b.bid_id, b.bid_amount,
(u.first_name + ' ' + u.last_name) AS bid_user_name
FROM vb_items i
JOIN vb_bids b ON i.item_id = b.bid_item_id
JOIN vb_users u ON b.user_id = u.user_id
WHERE b.bid_status = 'ok'
AND i.item_sold = 'no'
),
-- group bid info
item_bid_info AS (
SELECT item_id, item_name, item_reserve
COUNT(bid_id) AS number_of_bids, MIN(bid_amount) AS lowest_bid, MAX(bid_amount) AS highest_bid
FROM item_bids
GROUP BY item_id, item_name, item_reserve
)
-- assemble final result
SELECT
bi.item_name, bi.item_reserve, bi.number_of_bids,
bi.low_bid, low_bid.bid_user_name AS low_bid_user,
bi.high_bid, high_bid.bid_user_name AS high_bid_user
FROM item_bid_info bi
JOIN item_bids AS low_bid ON bi.lowest_bid = low_bid.bid_amount AND bi.item_id = low_bid.bid_item_id
JOIN item_bids AS high_bid ON bi.lowest_bid = high_bid.bid_amount AND bi.item_id = high_bid.bid_item_id
ORDER BY bi.item_reserve;
Note that the entire SQL statement (from the starting WITH all the way down to the final semi-colon after the ORDER BY) is a single statement, and is evaluated by the optimizer as such. (Some people think each part is evaluated separately, like temp tables, and then all the rows are joined together at the end in a final step. That's not how it works. CTEs are just as efficient as sub-queries.)
Also note that this approach does a JOIN on the bid amount, so if there are identical bids for a single item, it will fail. (Seems like that should be an invalid state anyway, though, right?) Also you may have efficiency concerns depending on:
The size of your table
Whether the lookup can use an index
You could address both issues by including a unique constraint (which has the added advantage of indexing the foreign key bid_item_id as well; always a good practice):
ALTER TABLE [dbo].[vb_bids] ADD CONSTRAINT [UK_vbBids_item_amount]
UNIQUE NONCLUSTERED (bid_item_id, bid_amount)
GO
Hope that helps!

SQL - Derived tables issue

I have the following SQL query:
SELECT VehicleRegistrations.ID, VehicleRegistrations.VehicleReg,
VehicleRegistrations.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations
INNER JOIN VehicleType ON VehicleRegistrations.VehicleType = VehicleType.ID
LEFT OUTER JOIN (SELECT TOP (1) ID, VehicleRegID, DateEffective, IVehHire,
FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts
WHERE (DateEffective <= GETDATE())
ORDER BY DateEffective DESC) AS dt
ON dt.VehicleRegID = VehicleRegistrations.ID
What I basically want to do is always select the top 1 record from the 'VehicleFixedCosts' table, where the VehicleRegID matches the one in the main query. What is happening here is that it's selecting the top row before the join, so if the vehicle registration of the top row doesn't match the one we're joining to it returns nothing.
Any ideas? I really don't want to have use subselects for each of the columns I need to return
Try this:
SELECT vr.ID, vr.VehicleReg,
vr.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations vr
INNER JOIN VehicleType ON vr.VehicleType = VehicleType.ID
LEFT OUTER JOIN (
SELECT ID, VehicleRegID, DateEffective, IVehHire, FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts vfc
JOIN (
select VehicleRegID, max(DateEffective) as DateEffective
from VehicleFixedCosts
where DateEffective <= getdate()
group by VehicleRegID
) t ON vfc.VehicleRegID = t.VehicleRegID and vfc.DateEffective = t.DateEffective
) AS dt
ON dt.VehicleRegID = vr.ID
Subquery underneath dt might need some grouping but without schema (and maybe sample data) it's hard to say which column should be involved in that.

Return only one row when multiple rows exist

I am working with a database that tracks field trip information for schools. The query will run on a MS SQL 2005 Server. There are a few cases when my query will return multiple rows for the same Field Trip. So, what I want to do is filter my results so that if more than one row per TripID is returned, display only the row with the MIN StartDateTime.
I know there's something I can do with the PARTITION and MIN functions but I'm not sure how to go about it.
Here is my code:
SELECT DISTINCT
dbo.Trip_TripInformation.RecordID AS TripID,
dbo.Trip_TripInformation.TripDate,
Origin.LocationName AS Origin,
dbo.Trip_TripInformation.OriginDepartureTime AS StartDateTime,
dbo.Trip_TripInformation.OriginReturnTime AS ReturnDateTime,
ISNULL(dbo.Trip_TripInformation.NoOfStudents, 0) AS NumberOfStudents,
ISNULL(dbo.Trip_TripInformation.NoOfAdults, 0) AS NumberOfAdults,
ISNULL(dbo.Trip_TripInformation.NoOfStudents, 0) + ISNULL(dbo.Trip_TripInformation.NoOfAdults, 0) AS NumberOfPassengers,
Destination.LocationName AS Destination,
dbo.Vehicles.Vehicle,
Driver.LastName + ', ' + Driver.FirstName AS Driver
FROM dbo.Trip_TripInformation
LEFT JOIN dbo.Trip_Location AS Origin ON Origin.RecordID = dbo.Trip_TripInformation.OriginLocationID
LEFT JOIN dbo.Trip_TripDestinations ON dbo.Trip_TripInformation.RecordID = dbo.Trip_TripDestinations.TripID
LEFT JOIN dbo.Trip_Location AS Destination ON Destination.RecordID = dbo.Trip_TripDestinations.LocationID
LEFT JOIN dbo.Trip_TripDriverVehicle ON dbo.Trip_TripInformation.RecordID = dbo.Trip_TripDriverVehicle.TripID
AND dbo.Trip_TripDriverVehicle.DestinationID = dbo.Trip_TripDestinations.RecordID
LEFT JOIN dbo.Vehicles ON dbo.Vehicles.RecordID = dbo.Trip_TripDriverVehicle.VehicleID
LEFT JOIN dbo.Employees AS Driver ON dbo.Trip_TripDriverVehicle.DriverID = Driver.RecordID
ORDER BY TripID
Order by StartDate and then select the TOP(1)
Try adding a row number to your select and selecting your data into a temp table (or use a CTE):
ROW_NUMBER() OVER ( PARTITION BY dbo.Trip_TripInformation.RecordID
ORDER BY dbo.Trip_TripInformation.OriginDepartureTime asc
) as RowNum
Then you can just select from that where RowNum = 1
You need to GROUP them.
Happy coding
You need to join to a derived table which extracts the unique TripId and earliest departure time for each trip:
SELECT DISTINCT
...
FROM dbo.Trip_TripInformation
INNER JOIN (
SELECT TripID, MIN(OriginDepartureTime) as EarliestDepartureTime
FROM Trip_TripInformation
GROUP BY TripID
) EarliestTripOnly
ON
Trip_TripInformation.TripID = EarliestTripOnly.TripId
AND
Trip_TripInformation.OriginDepartureTime
= EarliestTripOnly.EarliestDepartureTime
LEFT JOIN dbo.Trip_Location AS Origin ON Origin.RecordID =
dbo.Trip_TripInformation.OriginLocationID
...
You can use the Row_Number function to number each start date within each TripID. In addition, I encapsulated the query into a common-table expression so that I could then fitler on only those Trips where their row numbering was 1 which will represent the earliest date should a trip return multiple rows.
With TripInfo As
(
Select TInfo.RecordID As TripID
, TInfo.TripDate
, Origin.LocationName As Origin
, TInfo.OriginDepartureTime As StartDateTime
, TInfo.OriginReturnTime As ReturnDateTime
, Coalesce( TInfo.NoOfStudents, 0 ) As NumberOfStudents
, Coalesce( TInfo.NoOfAdults, 0) As NumberOfAdults
, Coalesce( TInfo.NoOfStudents, 0) + Coalesce(TInfo.NoOfAdults, 0) As NumberOfPassengers
, Dest.LocationName As Destination
, V.Vehicle
, Driver.LastName + ', ' + Driver.FirstName As Driver
, Row_Number() Over ( Partition By TInfo.RecordId
Order By TInfo.OriginDepartureTime ) As TripDateRnk
From dbo.Trip_TripInformation As TInfo
Left Join dbo.Trip_Location AS Origin
On Origin.RecordID = TInfo.OriginLocationID
Left Join dbo.Trip_TripDestinations As TDest
On TInfo.RecordID = TDest.TripID
Left Join dbo.Trip_Location AS Destination
On Destination.RecordID = TDest.LocationID
Left Join dbo.Trip_TripDriverVehicle As TripV
On TInfo.RecordID = TripV.TripID
And TripV.DestinationID = TDest.RecordID
Left Join dbo.Vehicles As V
ON dbo.Vehicles.RecordID = TripV.VehicleID
Left Join dbo.Employees AS Driver
On dbo.Trip_TripDriverVehicle.DriverID = Driver.RecordID
)
Select TripID, TripDate, Origin, StartDateTime, ReturnDateTime
, NumberOfStudents, NumberOfAdults, NumberOfPassengers
, Destination, Vehicle, Driver
From TripInfo
Where TripDateRank = 1
Order By TripID
A couple of other observations:
I notice that every table is using a Left Join. Is it really the case that all the columns in question are nullable? For example, is it really the case that the VehicleID and the DriverID columns in the Trip_TripDriverVehicle table are nullable? You can designate a trip driver vehicle without the vehicle and without a driver?
I would recommend using Coalesce function instead of the awfully named SQL Server-specific function IsNull. They operate pretty much the same but Coalesce is standard and allows for more than two parameters whereas IsNull is restricted to two. It won't make any significant difference with respect to your code or performance. It's just a style improvement.
SELECT * FROM
(SELECT DISTINCT RowNum = ROW_NUMBER() OVER (PARTITION BY TI.RecordID ORDER BY Trip_TripDestinations.DestinationArrivalTime),
TI.RecordID AS TripID,
TI.TripDate,
Origin.LocationName AS Origin,
TI.OriginDepartureTime AS StartDateTime,
TI.OriginReturnTime AS ReturnDateTime,
ISNULL(TI.NoOfStudents, 0) AS NumberOfStudents,
ISNULL(TI.NoOfAdults, 0) AS NumberOfAdults,
ISNULL(TI.NoOfStudents, 0) + ISNULL(TI.NoOfAdults, 0) AS NumberOfPassengers,
Destination.LocationName AS Destination,
Trip_TripDestinations.DestinationArrivalTime AS DestinationArrivalDateTime,
Vehicles.Vehicle,
Driver.LastName + ', ' + Driver.FirstName AS Driver
FROM Trip_TripInformation TI
LEFT OUTER JOIN Trip_Location AS Origin ON Origin.RecordID = TI.OriginLocationID
/*More Joins... */
LEFT OUTER JOIN Employees AS Driver ON Trip_TripDriverVehicle.DriverID = Driver.RecordID) Q1
WHERE Q1.RowNum = 1 and (Q1.TripDate BETWEEN '2010/12/13 00:00:00' AND '2010/12/17 00:00:00')
ORDER BY Q1.DestinationArrivalDateTime