Combining SQL join with count - sql

I have two tables registered and attended, each with two columns: AttendentId and SessionId. I would like to query the count of AttendantId from these two tables individually for a particular session id.
Example:
registered
AttendantId SessionId
ID1 SN1
ID2 SN2
ID3 SN1
ID4 SN3
Attended
AttendantId SessionId
ID1 SN1
ID4 SN3
And I want to obtain the following output:
Count(Registered) Count(Attended) Session ID
2 1 SN1
1 0 SN2
1 1 SN3

You could use a FULL OUTER JOIN:
select
coalesce(a.sessionid, r.sessionid) sessionid,
count(r.AttendantId) countRegistered,
count(a.AttendantId) countAttended
from registered r
full outer join attended a
on r.sessionid = a.sessionid
and r.AttendantId = a.AttendantId
group by coalesce(a.sessionid, r.sessionid);
See SQL Fiddle with Demo

Try:
select count(distinct registered),
count(distinct attended),
SessionId
from (select AttendantId registered, null attended, SessionId
from registered
union all
select null registered, AttendantId attended, SessionId
from Attended) sq
group by SessionId

SELECT
ISNULL(ACount, 0),
ISNULL(RCount, 0),
X.SessionId
FROM
(
SELECT SessionId FROM Registered
UNION -- implies DISTINCT
SELECT SessionId FROM Attended
) X
LEFT JOIN
(SELECT COUNT(*) AS RCount, SessionId
FROM Registered
GROUP BY SessionId) R ON X.SessionId = R.SessionId
LEFT JOIN
(SELECT COUNT(*) AS ACount, SessionId
FROM Attended
GROUP BY SessionId) A ON X.SessionId = A.SessionId
SQLFiddle

Try this query
select
a.sessionId,
case when aCnt is null then 0 else acnt end,
case when bCnt is null then 0 else bcnt end
from
(select
sessionId,
count(*) aCNt
from
tbl1
group by
sessionid) a
full join
(select
sessionId,
count(*) bCnt
from
tbl2
group by
sessionid) b
on
a.sessionId = b.sessionid
SQL FIDDLE:
| SESSIONID | COLUMN_1 | COLUMN_2 |
-----------------------------------
| SN1 | 2 | 1 |
| SN2 | 1 | 0 |
| SN3 | 1 | 1 |
Hope this helps....

Assuming all session ID's exist in registered table
SELECT Sum(CASE
WHEN a.attendentid IS NOT NULL THEN 1
ELSE 0
end) AS Count(registered),
Sum(CASE
WHEN b.attendentid IS NOT NULL THEN 1
ELSE 0
end) AS Count(attended),
a.sessionid
FROM registered a
INNER JOIN attended b
ON a.sessionid = b.sessionid
GROUP BY a.sessionid

Related

Create table with values from one column and another column without intersection

I have a table like so:
userid | clothesid
-------|-----------
1 | 1
1 | 3
2 | 1
2 | 4
2 | 5
What I want from this table is a table like so:
userid | clothesid
-------|-----------
1 | 4
1 | 5
2 | 3
How can I do this?
I've tried it with one entry as:
select distinct r.clothesid from table r where r.clothes not in (select r1.clothes from table r1 where r1.userid=1);
and this returns 4,5, but I'm not sure where to proceed from here
You can cross join the list of userids and the list of clothesid to generate all combinations, and then use not exists on the original table to identify the missing rows:
select u.userid, c.clothesid
from (select distinct userid from mytable) u
cross join (select distinct clothesid from mytable) c
where not exists(
select 1 from mytable t on t.userid = u.userid and t.clothesid = c.clothesid
)
I think you want:
select (case when t1.clothesid is not null then 2 else 1 end),
coalesce(t1.clothesid, t2.clothesid)
from (select t.*
from t
where t.userid = 1
) t1 full join
(select t.*
from t
where t.userid = 2
) t2
on t1.clothesid = t2.clothesid
where t1.clothesid is null or t2.clothesid is null;
Actually, I think I have a simpler solution:
select (case when min(t.userid) = 1 then 2 else 1 end), clothesid
from t
group by clothesid
having count(*) = 1;
Here is a db<>fiddle.
Left join all the combinations of userid and clothesid to the table and return only the unmatched rows:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
left join tablename t on t.userid = t1.userid and t.clothesid = t2.clothesid
where t.userid is null
Or with the operator EXCEPT:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
except
select userid, clothesid
from tablename
See the demo.
Results:
> userid | clothesid
> -----: | --------:
> 1 | 4
> 1 | 5
> 2 | 3

SQL Select Distinct Records From Two Tables

I am trying to write a SQL statement that will return a set of Distinct set of CompanyNames from a table based on the most recent SaleDate withing a specified date range from another table.
T01 = Account
T02 = TransHeader
The fields of importance are:
T01.ID, T01.CompanyName
T02.AccountID, T02.SaleDate
T01.ID = T02.AccountID
What I want to return is the Max SaleDate for each CompanyName without any duplicate CompanyNames and only the Max(SaleDate) as LastSale. I will be using a Where Clause to limit the SaleDate range.
I tried the following but it returns all the records for all SalesDates in the range. This results in the same company being listed multiple times.
Current MS-SQL Query
SELECT T01.CompanyName, T02.LastSale
FROM
(SELECT DISTINCT ID, IsActive, ClassTypeID, CompanyName FROM Account) T01
FULL OUTER JOIN
(SELECT DISTINCT AccountID, TransactionType, MAX(SaleDate) LastSale FROM TransHeader group by AccountID, TransactionType, SaleDate) T02
ON T01.ID = T02.AccountID
WHERE ( ( T01.IsActive = 1 )AND
( (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL)
BETWEEN '01/01/2016' AND '12/31/2018 23:59:00' AND (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL) IS NOT NULL
)
)
ORDER BY T01.CompanyName
I thought the FULL OUTER JOIN was the ticket but it did not work and I am stuck.
Sample data Account Table (T01)
ID CompanyName IsActive ClassTypeID
1 ABC123 1 1
2 CDE456 1 1
3 EFG789 1 1
4 Test123 0 1
5 Test456 1 1
6 Test789 0 1
Sample data Transheader table (T02)
AccountID TransactionType SaleDate
1 1 02/03/2012
2 1 03/04/2013
3 1 04/05/2014
4 1 05/06/2014
5 1 06/07/2014
6 1 07/08/2015
1 1 08/09/2016
1 1 01/15/2016
2 1 03/20/2017
2 1 03/21/2017
3 1 03/04/2017
3 1 04/05/2018
3 1 05/27/2018
4 1 06/01/2018
5 1 07/08/2018
5 1 08/01/2018
5 1 10/11/2018
6 1 11/30/2018
Desired Results
CompanyName LastSale (Notes note returned in the result)
ABC123 01/15/2016 (Max(SaleDate) LastSale for ID=1)
CDE456 03/21/2017 (Max(SaleDate) LastSale for ID=2)
EFG789 05/27/2018 (Max(SaleDate) LastSale for ID=3)
Testing456 10/11/2018 (Max(SaleDate) LastSale for ID=5)
ID=4 & ID=6 are note returned because IsActive = 0 for these records.
One option is to select the maximum date in the select clause.
select
a.*,
(
select max(th.saledate)
from transheader th
where th.accountid = a.id
and th.saledate >= '2016-01-01'
and th.saledate < '2019-01-01'
) as max_date
from account a
where a.isactive = 1
order by a.id;
If you only want to show transaction headers with sales dates in the given date range, then you can just inner join the maximum dates with the accounts. In order to do so, you must group your date aggregation per account:
select a.*, th.max_date
from account a
join
(
select accountid, max(saledate) as max_date
from transheader
and saledate >= '2016-01-01'
and saledate < '2019-01-01'
group by accountid
) th on th.accountid = a.id
where a.isactive = 1
order by a.id;
select CompanyName,MAX(SaleDate) SaleDate from Account a
inner join Transheader b on a.id = b.accountid
group by CompanyName
order by 1

SQL: find one to many in one table

there is a table:
t1
Id | Acc Status| Acc Type|
-----------------------------------------
1 |Online | home
2 |Offine | work
3 |Declined | work
1 |Activated | home
.
Question is:
need to find all Id which has 'Acc Status' = Online AND 'Acc Status' = Activated
You must group by id and count the distinct values of accstatus:
select id
from t1
where accstatus in ('Online', 'Activated')
group by id
having count(distinct accstatus) = 2
If you need the ids that have only these accstatus values:
select id
from t1
group by id
having
count(distinct accstatus) = 2
and
sum(case when accstatus not in ('Online', 'Activated') then 1 else 0 end) = 0
You can try below query -
SELECT id
FROM T1
WHERE AccStatus IN ('Online', 'Activated')
GROUP BY id
HAVING COUNT(DISTINCT AccStatus) = 2;
Try this below query
SELECT id
FROM Table_name
WHERE AccStatus = 'Online'
and id IN (SELECT id FROM Table_name wHERE AccStatus = 'Activated')
select DiSTINCT Id
from t1
where [Acc Status] in ("Online", "Activated")

SQL update all records except the last one with a value

I need to make a query where only the last line of each user that has a car gets a license plate number.
ID UserId LicensePlate HasCar
1 1 ABC123 1
2 1 ABC123 1
3 2 NULL 0
4 3 UVW789 1
5 3 UVW789 1
Should become:
ID UserId LicensePlate HasCar
1 1 NULL 1
2 1 ABC123 1
3 2 NULL 0
4 3 NULL 1
5 3 UVW789 1
So I basically need to find all users with a licenseplate and change all but the last one and make the LicensePlate NULL
Assuming the ID column is an identity column so it can provide the ordering, something like this should do the trick:
;WITH CTE AS
(
SELECT Id,
UserId,
LicensePlate,
ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY Id DESC) rn
FROM Table
WHERE HasCar = 1
)
UPDATE CTE
SET LicensePlate = NULL
WHERE rn > 1
You can try this
UPDATE l
SET l.LicensePlate = null
FROM Car l
INNER JOIN (SELECT UserId, Max(Id) AS max_id
FROM Car
GROUP BY UserId) m ON m.UserId = l.UserId
AND m.max_id <> l.id
You can do it with a join on the table itself like that :
UPDATE car c
INNER JOIN car c2 ON c.userId = c2.userId AND c.id < c2.id AND c.HasCar = 1 AND c2.HasCar = 1
SET c.LicensePlate = NULL
The condition c.id < c2.id will avoid to select the last line
By using LAG Function also you can achieve it.
;WITH License(ID,UserId,LicensePlate,HasCar)
as
(
SELECT 1,1,'ABC123',1 UNION ALL
SELECT 2,1,'ABC123',1 UNION ALL
SELECT 3,2,NULL ,0 UNION ALL
SELECT 4,3,'UVW789',1 UNION ALL
SELECT 5,3,'UVW789',1
)
SELECT ID,UserId,LAG(LicensePlate,1,NULL) OVER(PARTITION BY UserId ORDER BY LicensePlate),HasCar FROM License

SQL count changes in column

I need to count the changes in assigned group on a ticket. The problem is my log also count changes in assignee that are in the same group.
Here is some sample data
ticket_id | assigned_group | assignee | date
----------------------------------------------------
1001 | group A | john | 1-1-15
1001 | group A | michael | 1-2-15
1001 | group A | jacob | 1-3-15
1001 | group B | eddie | 1-4-15
1002 | group A | john | 1-1-15
1002 | group B | eddie | 1-2-15
1002 | group A | john | 1-3-15
1002 | group B | eddie | 1-4-15
1002 | group A | john | 1-5-15
I need this to return
ticket_id | count
--------------------
10001 | 2
10002 | 4
My query is like this
select ticket_id, assigned_group, count(*) from mytable group by ticket_id, assigned_group
But that gives me
ticket_id | count
--------------------
10001 | 4
10002 | 5
edit:
Also if I use
select ticket_id, count(Distinct assigned_group) as [Count] from mytable group by ticket_id
I only get
ticket_id | count
--------------------
10001 | 2
10002 | 2
Any advice?
Use Distinct Count to get the result
select ticket_id, count(Distinct assigned_group) as [Count]
from mytable
group by ticket_id
try this..
with temp as
(
select ticket_id, assigned_group, count(*) as count,date from mytable group by ticket_id, assigned_group,date
)
select ticket_id, count from temp
You can use Row_number() function to look into the next record's value.
with tbl as (select *, row_number() over(partition by ticket_id order by 1) from table)
select a.ticket_id, a.assigned_group, a.assignee_name, a.date,
count(case when a.assigned_group <> b.assigned_group then 1 else 0 end) as No_of_change
from tbl as a
left join tbl as b
on a.rn = b.rn + 1
If you are using SQL Server 2012, then you can use the LAG function to determine the previous assigned group easily. Then, if the previous assigned group is different from the current assigned group, you can increment the count, as below:
WITH previous_groups AS
(
SELECT
ticket_id,
assign_date,
assigned_group,
LAG(assigned_group, 1, NULL) OVER (PARTITION BY ticket_id ORDER BY assign_date) AS prev_assign_group
FROM mytable
)
SELECT
ticket_id,
SUM(CASE
WHEN assigned_group <> prev_assign_group THEN 1
ELSE 0
END) AS count
FROM previous_groups
WHERE prev_assign_group IS NOT NULL
GROUP BY ticket_id
ORDER BY ticket_id;
If you are using SQL Server 2008 or earlier versions, then you need an extra step to determine the previous assigned group, as below:
WITH previous_assign_dates AS
(
SELECT
mt1.ticket_id,
mt1.assign_date,
MAX(mt2.assign_date) AS prev_assign_date
FROM mytable mt1
LEFT JOIN mytable mt2
ON mt1.ticket_id = mt2.ticket_id
AND mt2.assign_date < mt1.assign_date
GROUP BY
mt1.ticket_id,
mt1.assign_date
),
previous_groups AS
(
SELECT
mt1.*,
mt2.assigned_group AS prev_assign_group
FROM mytable mt1
INNER JOIN previous_assign_dates pad
ON mt1.ticket_id = pad.ticket_id
AND mt1.assign_date = pad.assign_date
LEFT JOIN mytable mt2
ON pad.ticket_id = mt2.ticket_id
AND pad.prev_assign_date = mt2.assign_date
)
SELECT
ticket_id,
SUM(CASE
WHEN assigned_group <> prev_assign_group THEN 1
ELSE 0
END) AS count
FROM previous_groups
WHERE prev_assign_group IS NOT NULL
GROUP BY ticket_id
ORDER BY ticket_id;
SQL Fiddle demo
References:
The LAG function on MSDN
Adding an ordinal number within the ticket, then a self join where the group is different and consecutive ordinals, should work:
SELECT t1.ticket_id, COUNT(*) FROM
(SELECT *, ROW_NUMBER() OVER(PARTITION BY ticket_id ORDER BY date) ordinal
FROM mytable) t1
JOIN
(SELECT *, ROW_NUMBER() OVER(PARTITION BY ticket_id ORDER BY date) ordinal FROM nytable) t2
ON t1.ticket_id=t2.ticket_id AND t1.assigned_group<>t2.assigned_group AND t1.ordinal+1=t2.ordinal
GROUP BY t1.ticket_id