Combine the result of two select queries into one table - sql

I have the following two tables:
STOCK_ON_HAND: This is showing me all of the stock that I have on hand
STOCK_ON_ORDER: This is showing me all of the stock that I have on order
I have the following two queries to summarise the tables:
SELECT STOCK_CODE, SUM(QTY)
FROM STOCK_ON_HAND
GROUP BY STOCK_CODE
HAVING SUM(QTY) <>0;
And
SELECT STOCK_CODE, SUM(ON_ORDER)
FROM STOCK_ON_ORDER
GROUP BY STOCK_CODE
HAVING SUM(STOCK_ON_ORDER) <>0;
I basically want to combine the above with into one table showing the following fields:
STOCK_CODE
STOCK_ON_HAND
STOCK_ON_ORDER
What would the best approach to achieve this?

For this you need a FULL OUTER JOIN which Access does not support directly, but you can simulate it with UNION like this:
SELECT h.STOCK_CODE, h.HQTY AS STOCK_ON_HAND, o.OQTY AS STOCK_ON_ORDER
FROM (
SELECT STOCK_CODE, SUM(QTY) AS HQTY
FROM STOCK_ON_HAND
GROUP BY STOCK_CODE
HAVING SUM(QTY) <> 0
) h LEFT JOIN (
SELECT STOCK_CODE, SUM(ON_ORDER) AS OQTY
FROM STOCK_ON_ORDER
GROUP BY STOCK_CODE
HAVING SUM(ON_ORDER) <> 0
) o ON o.STOCK_CODE = h.STOCK_CODE
UNION
SELECT o.STOCK_CODE, h.HQTY AS STOCK_ON_HAND, o.OQTY AS STOCK_ON_ORDER
FROM (
SELECT STOCK_CODE, SUM(QTY) AS HQTY
FROM STOCK_ON_HAND
GROUP BY STOCK_CODE
HAVING SUM(QTY) <> 0
) h RIGHT JOIN (
SELECT STOCK_CODE, SUM(ON_ORDER) AS OQTY
FROM STOCK_ON_ORDER
GROUP BY STOCK_CODE
HAVING SUM(ON_ORDER) <> 0
) o ON o.STOCK_CODE = h.STOCK_CODE
If you don't want to see nulls in the results (if they exist) but replace them with 0s, use the function Nz(), like this:
SELECT h.STOCK_CODE, Nz(h.HQTY, 0) AS STOCK_ON_HAND, Nz(o.HQTY, 0) AS STOCK_ON_ORDER
Or get the distinct STOCK_CODEs from both tables and LEFT JOIN them to each of the tables:
SELECT c.STOCK_CODE, h.HQTY AS STOCK_ON_HAND, o.OQTY AS STOCK_ON_ORDER
FROM ((
SELECT STOCK_CODE FROM STOCK_ON_HAND
UNION
SELECT STOCK_CODE FROM STOCK_ON_ORDER
) AS c LEFT JOIN (
SELECT STOCK_CODE, SUM(QTY) AS HQTY
FROM STOCK_ON_HAND
GROUP BY STOCK_CODE
HAVING SUM(QTY) <> 0
) h ON h.STOCK_CODE = c.STOCK_CODE )
LEFT JOIN (
SELECT STOCK_CODE, SUM(ON_ORDER) AS OQTY
FROM STOCK_ON_ORDER
GROUP BY STOCK_CODE
HAVING SUM(ON_ORDER) <> 0
) o ON o.STOCK_CODE = c.STOCK_CODE

I would recommend doing a UNION ALL before aggregating:
SELECT STOCK_CODE, SUM(on_hand), SUM(on_order)
FROM (SELECT STOCK_CODE, SUM(QTY) as on_hand, 0 on_order
FROM STOCK_ON_HAND
GROUP BY STOCK_CODE
UNION ALL
SELECT STOCK_CODE, 0, SUM(ON_ORDER) as on_order
FROM STOCK_ON_ORDER
GROUP BY STOCK_CODE
) s
GROUP BY STOCK_CODE
HAVING on_hand <> 0 OR on_order <> 0;
If MS Access does not support UNION ALL in the FROM clause, you can use a view to set that up.

Related

SQL: Select within Select as subquery

I've got the following statement:
select
product_name as ShortestLength = (select top 1 product_name, len(fact_name) Value_Length
from table
order by Value_Length, fact_name ASC)
Which returns this output:
shortestlength
PS
I'd like to add this outcome to another select statement:
select
'Product' as Column_Name,
avg(case when product is null then 1.000 else 0 end) * 100 as PctMissing,
count(product) as TotalCount,
count(distinct product) as UniqueCount
from
table
so the result will be:
column_name
pctmissing
totalcount
uniquecount
shortestlength
Product
5.100
181186
15
PS
What should I add to my initial select statement?
You can use conditional aggregation:
select 'Product' as Column_Name,
avg(case when t.product is null then 1.000 else 0 end)*100 as PctMissing,
count(t.product) as TotalCount,
count(distinct t.product) as UniqueCount,
max(case when seqnum = 1 then product_name end) as shortest_length
from (select t.*,
row_number() over (order by len(fact_name), fact_name) as seqnum
from table t
) t
This assumes that the two table references are really the same table.
You can just use first query as subquery in place of a column in your select statement:
select
'Product' as Column_Name,
avg(case when product is null then 1.000 else 0 end)*100 as PctMissing,
count(product) as TotalCount,
count(distinct product) as UniqueCount,
(select top 1 product_name from table order by Value_Length, fact_name ASC) as ShortestLength
from table

SQL Aggregate most frequent value with GROUP BY

I have a table like this:
PARTNUMBER | QUANTITY | DESCRIPTION
'foo' 2 'a'
'foo' 2 'a1'
'bar' 2 'b'
'bar' 2 'b'
'bar' 2 'b1'
'bizz' 2 'c'
I'm trying to group by PARTNUMBER, aggregate by QUANTITY, and aggregate DESCRIPTION by most-frequent appearance.
I tried using a sub-query to aggregate DESCRIPTION by its most frequent occurrence, but I'm having some trouble getting it right, especially with GROUP BY.
Here is what I have:
SELECT SUM(QUANTITY) AS QUANTITY, PARTNNUMBER,
(SELECT TOP(1) [DESCRIPTION]
FROM [PBJobDB].[dbo].[DEVICES]
/*WHERE DESCRIPTION = t1.PARTNO ?? */
GROUP BY [DESCRIPTION], PARTNNUMBER
ORDER BY COUNT([DESCRIPTION]) DESC) as [DESCRIPTION]
FROM `database.table`
GROUP BY PARTNUMBER, [DESCRIPTION]
The subquery is not getting the most frequent DESCRIPTION by PARTNUMBER, and instead gives the most frequent DESCRIPTION in the whole table.
I would like the output to look like this:
PARTNUMBER | QUANTITY | DESCRIPTION
'foo' 4 'a'
'bar' 6 'b'
'bizz' 2 'c'
I tried below one, please check whether its working for you,
SELECT PARTNUMBER,SUM(QUANTITY) AS QUANTITY,
(
SELECT TOP 1 DESCP FROM
(SELECT [DESCRIPTION]'DESCP',COUNT(*)'CNT'
FROM testtable
WHERE PARTNUMBER = t1.PARTNUMBER
GROUP BY [DESCRIPTION]) A
GROUP BY DESCP,CNT HAVING CNT=MAX(CNT)
)as [DESCRIPTION]
FROM testtable T1
GROUP BY PARTNUMBER
This is what ended up working for me...
select distinct t1.PARTNUMBER , sum(t1.QUANTITY) AS QUANTITY, (
select TOP(1) [DESCRIPTION]
from [PBJobDB].[dbo].[DEVICES] AS t2
where t2.PARTNUMBER = t1.PARTNUMBER
group by [DESCRIPTION]
order by count(*) desc ) as [DESCRIPTION]
from `database.table` AS t1
/* WHERE `column` IS NULL AND `other_column` = 'some_value' */
GROUP BY t1.PARTNUMBER
You are looking for the mode. I would use two levels of aggregation:
select partnumber, sum(quantity) as total_quantity,
max(case when seqnum = 1 then description end) as description
from (select partnumber, description, sum(quantity) as quantity,
row_number() over (partition by partnumber order by sum(quantity) desc, description) as seqnum
from t
group by partnumber, description
) pd
group by partnumber;
I would use Sum() over to get total quantity. Below is the example that worked for me.
SELECT PARTNUMBER, QUANTITY, DESCRIPTION
FROM (
SELECT PARTNUMBER,SUM(Quantity) OVER (PARTITION BY PARTNUMBER ORDER BY CAST(PARTNUMBER AS VARCHAR(30)) ) Quantity,DESCRIPTION,R
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY PARTNUMBER ORDER BY COUNT(DESCRIPTION) DESC) R,
SUM(Quantity) Quantity, --OVER (PARTITION BY PARTNUMBER ORDER BY CAST(PARTNUMBER AS VARCHAR(30)) ) Quantity,
PARTNUMBER,
DESCRIPTION
FROM #Temp
GROUP BY PARTNUMBER,DESCRIPTION
) AS S
) AS S
WHERE R = 1

Need to get difference between these two as output

REQUIRED QUERY :
select all memberid from query 1 having higher count than query 2
the aim is to display all memberid who were leader/organiser/helper more times than they participate in event
how do i join these two select statement and view the required output
SELECT memberID, COUNT(*) AS cnt
FROM
(
SELECT memberID FROM leader UNION ALL
SELECT memberID FROM organiser UNION ALL
SELECT memberID FROM helper
) t
GROUP BY memberID;
=============
select memberid,count(*) as cnt2
from eventmember group by memberid;
=======
another, "all_in_one" solution
select
memberid,
count(*) as total_cnt,
sum(case when type = 'eventmember' then 1 else 0 end) as eventmember_cnt,
sum(case when type = 'eventmember' then 0 else 1 end) as other_cnt
from
( select memberid, 'leader' as type
from leader union all
select memberid, 'organiser' as type
from organiser union all
select memberid, 'helper' as type
from helper union all
select memberid, 'eventmember' as type
from eventmember
) t
group by
memberid
having
sum(case when type = 'eventmember' then 1 else 0 end)
< sum(case when type = 'eventmember' then 0 else 1 end)
try like below using cte
with cte as
(
SELECT memberID, COUNT(*) AS cnt
FROM
(
SELECT memberID FROM leader UNION ALL
SELECT memberID FROM organiser UNION ALL
SELECT memberID FROM helper
) t
GROUP BY memberID
),cte1 as
(
select memberid,count(*) as cnt2
from eventmember group by memberid
) select cte.memberID,cnt,cnt2 from cte join ct1 on cte.memberID=cte1.memberid

Intersect Select Statements on Specific Columns

I've a table of SalesDetails, looking like this:
InvoiceID, LineID, Product
1,1,Apple
1,2,Banana
2,1,Apple
2,2,Mango
3,1,Apple
3,2,Banana
3,3,Mango
My requirement is to return rows where an Invoice contained sales of both: Apple AND Banana, but if there are other products on such an invoice, I don't want those.
So the result should be:
1,1,Apple
1,2,Banana
3,1,Apple
3,2,Banana
I tried the following:
Select * from SalesDetails where Product = 'Apple'
Intersect
Select * from SalesDetails where Product = 'Banana'
Didn't work, because it seems Intersect needs to match all the columns.
What I'm hoping to do is:
Select * from SalesDetails where Product = 'Apple'
Intersect ----On InvoiceID-----
Select * from SalesDetails where Product = 'Banana'
Is there a way to do this?
Or do I have to first Intersect on InvoiceIDs only using my criteria, then select the rows of those InvoiceIDs where the criteria is matched again, I.e.:
Select * From SalesDetails
Where Product In ('Apple', 'Banana') And InvoiceID In
(
Select InvoiceID from SalesDetails where Product = 'Apple'
Intersect
Select InvoiceID from SalesDetails where Product = 'Banana'
)
Which seems somewhat wasteful as it's examining the criteria twice.
Okay this time I've managed to get reuse of the Apple/Banana info by using a CTE.
with sd as (
Select * from SalesDetails
where (Product in ('Apple', 'Banana'))
)
Select * from sd where invoiceid in (Select invoiceid from
sd group by invoiceid having Count(distinct product) = 2)
SQL Fiddle
Do it with conditional aggregation:
select *
from SalesDetails
where product in ('apple', 'banana') and invoiceid in(
select invoiceid
from SalesDetails
group by invoiceid
having sum(case when product in('apple', 'banana') then 1 else 0 end) >= 2)
I think OP's suggestion is about the best one can do. The following might be faster, although I expect the difference to be slight and I have not done any benchmarking.
Select * From SalesDetails
Where Product ='Apple' And InvoiceID In
(
Select InvoiceID from SalesDetails where Product = 'Banana'
)
union all
select * from SalesDetails
Where Product ='Banana' And InvoiceID In
(
Select InvoiceID from SalesDetails where Product = 'Apple'
)
A self-join will solve the problem.
SELECT T1.*
FROM SalesDetails T1
INNER JOIN SalesDetails T2 ON T1.InvoiceId = T2.InvoiceId
AND (T1.Product = 'Apple' AND T2.Product = 'Banana'
OR T1.Product = 'Banana' AND t2.Product = 'Apple')
declare #t table (Id int,val int,name varchar(10))
insert into #t (id,val,name)values
(1,1,'Apple'),
(1,2,'Banana'),
(2,1,'Apple'),
(2,2,'Mango'),
(3,1,'Apple'),
(3,2,'Banana'),
(3,3,'Mango')
;with cte as (
select ID,val,name,ROW_NUMBER()OVER (PARTITION BY id ORDER BY val)RN from #t)
,cte2 AS(
select TOP 1 c.Id,c.val,c.name,C.RN from cte c
WHERE RN = 1
UNION ALL
select c.Id,c.val,c.name,C.RN from cte c
WHERE c.Id <> c.val)
select Id,val,name from (
select Id,val,name,COUNT(RN)OVER (PARTITION BY Id )R from cte2 )R
WHERE R = 2
Other was is to do PIVOT like this:
DECLARE #DataSource TABLE
(
[InvoiceID] TINYINT
,[LineID] TINYINT
,[Product] VARCHAR(12)
);
INSERT INTO #DataSource ([InvoiceID], [LineID], [Product])
VALUES (1,1,'Apple')
,(1,2,'Banana')
,(2,1,'Apple')
,(2,2,'Mango')
,(3,1,'Apple')
,(3,2,'Banana')
,(3,3,'Mango');
SELECT *
FROM #DataSource
PIVOT
(
MAX([LineID]) FOR [Product] IN ([Apple], [Banana])
) PVT
WHERE [Apple] IS NOT NULL
AND [Banana] IS NOT NULL;
It will give you the results in this format, but you are able to UNVPIVOT them if you want:
Or you can use window function like this:
;WITH DataSource AS
(
SELECT *
,SUM(1) OVER (PARTITION BY [InvoiceID]) AS [Match]
FROM #DataSource
WHERE [Product] = 'Apple' OR [Product] = 'Banana'
)
SELECT *
FROM DataSource
WHERE [Match] =2
First, you want to COUNT the number of rows per InvoiceID that matched the criteria Product = 'Apple' or 'Banana'. Then do a SELF-JOIN and filter the rows such that the COUNT must be >= 2, or the number of Products in your critera.
SQL Fiddle
SELECT sd.*
FROM (
SELECT InvoiceID, CC = COUNT(*)
FROM SalesDetails
WHERE Product IN('Apple', 'Banana')
GROUP BY InvoiceID
)t
INNER JOIN SalesDetails sd
ON sd.InvoiceID = t.InvoiceID
WHERE
t.CC >= 2
AND sd.Product IN('Apple', 'Banana')
WITH cte
AS
(
SELECT *
FROM [dbo].[SalesDetails]
WHERE [Product]='banana')
,cte1
AS
(SELECT *
FROM [dbo].[SalesDetails]
WHERE [Product]='apple')
SELECT *
FROM cte c INNER JOIN cte1 c1
ON c.[InvoiceID]=c1.[InvoiceID]
Here is a method using window functions:
select sd.*
from (select sd.*,
max(case when product = 'Apple' then 1 else 0 end) over (partition by invoiceid) as HasApple,
max(case when product = 'Banana' then 1 else 0 end) over (partition by invoiceid) as HasBanana
from salesdetails sd
) sd
where (product = 'Apple' and HasBanana > 0) or
(product = 'Banana' and HasApple > 0);
If you only want to write the condition once and are sure that each Product will only be once in any Order, you can use this:
SELECT * FROM (
SELECT InvoiceID, Product
,COUNT(*) OVER (PARTITION BY InvoiceID) matchcount
FROM SalesDetails
WHERE Product IN ('Apple','Banana') ) WHERE matchcount = 2;
This is what I ended up using, inspired by #Leon Bambrick:
(Expanded a little to support multiple products in the criteria)
WITH cteUnionBase AS
(SELECT * FROM SalesDetails
WHERE Product IN ('Apple Red','Apple Yellow','Apple Green','Banana Small','Banana Large')),
cteBanana AS
(SELECT * FROM cteUnionBase
WHERE Product IN ('Banana Small','Banana Large')),
cteApple AS
(SELECT * FROM cteUnionBase
WHERE Product IN ('Apple Red','Apple Yellow','Apple Green')),
cteIntersect AS
(
SELECT InvoiceID FROM cteApple
Intersect
SELECT InvoiceID FROM cteBanana
)
SELECT cteUnionBase.*
FROM cteUnionBase INNER JOIN cteIntersect
on cteUnionBase.InvoiceID = cteIntersect.InvoiceID

Cumulative sum across two tables

I have two tables for Bill & Payment. I have show the balance sheet from these two tables.
The Data in the tables are:
tblBill
tblPayment
My current output is:
The query I'm trying to use is:
select Particulars,Date,BillAmount,0'PaidAmount' from tblBill
union
select Particulars,Date,0'BillAmount',PaidAmount from tblPayment
order by Date
However, I need my output in this format:
Is it possible to get the required format?
There you go:
Assuming there is only one transaction in a day....
With Tb1 as
(select Date,Particulars,BillAmount,0'PaidAmount' from tblBill
union
select Date,Particulars,0'BillAmount',PaidAmount from tblPayment
)
SELECT T1.Particulars,T1.[Date],T1.[BillAmount],T1.[PaidAmount],(Sum(T2.BillAmount) - Sum(T2.PaidAmount)) as Balance FROM Tb1 as T1
INNER JOIN
Tb1 as T2
ON T1.[date] >= T2.[date]
Group By T1.Particulars,T1.[Date],T1.[BillAmount],T1.[PaidAmount]
Order by [Date]
In case of more than one transactions in a day....
WITH Tb0 as
( SELECT [Date],Particulars,BillAmount,0'PaidAmount' from tblBill
UNION
SELECT [Date],Particulars,0'BillAmount',PaidAmount from tblPayment
)
, Tb1 as
(
SELECT Date,Particulars,BillAmount,PaidAmount,Row_Number() over (order by [Date] asc) as [OrderId]
FROM
Tb0
)
SELECT T1.Particulars,T1.[Date],T1.[BillAmount],T1.[PaidAmount],(Sum(T2.BillAmount) - Sum(T2.PaidAmount)) as Balance FROM Tb1 as T1
INNER JOIN
Tb1 as T2
ON T1.[OrderId] >= T2.[OrderId]
Group By T1.Particulars,T1.[Date],T1.[BillAmount],T1.[PaidAmount]
Order by [Date]
You will need to JOIN the two tables. First, there must be a link between the two tables (say customerid existing to show which customer is involved).
Then, you can do.
CREATE VIEW vwTransactionHistory as
SELECT customerid, Particulars, [DATE], BillAmount, PaidAmount,
(SELECT SUM(BillAmount) FROM tblBill x WHERE x.customerid=temp1.customerid and x.date<=temp1.date) as bill2date, (SELECT SUM(PaidAmount) FROM tblPayment y WHERE y.customerid = temp1.customerid and y.date<=temp1.date) as Pay2Date
FROM
(
select customerid, Particulars,[Date],BillAmount,0 AS 'PaidAmount' from tblBill
union
select customerid,Particulars,[Date],0 AS 'BillAmount',PaidAmount from tblPayment
) AS temp1
GROUP BY customerid, Particulars,[Date],BillAmount,PaidAmount
Then you can do
SELECT TOP 1000 [customerid]
,[Particulars]
,[DATE]
,[BillAmount],[PaidAmount], isnull(bill2date,0) - isnull(pay2date,0) as Balance
FROM [vwTransactionHistory]
Remember that you don't need to create a View. I use views for clarity and abstraction of complex Queries.
Check this query
select * from
(
select Particulars,Date,BillAmount,0'PaidAmount' , BillAmount as Balance from tblBill
union
select Particulars,Date,0'BillAmount',PaidAmount, BillAmount - PaidAmount as Balance
from tblPayment p
inner join tblBill b where p.Particulars = p.Particulars
) a
order by Date
You can use this query
SELECT Particulars,Date,BillAmount,PaidAmount,BillAmount-PaidAmount as Balance
FROM(
select Particulars,Date,BillAmount,0'PaidAmount' from tblBill
union
select Particulars,Date,0'BillAmount',PaidAmount from tblPayment
order by Date
)
ORDER BY Date;