SQL - Join two tables and count different things - sql

I have two tables TEILNEHMERKURS and KURS.
Kurs:
| Bezeichnung |
| ---------------- |
| Java Programming |
| Java Programming |
| Database |
and the second Table TEILNEHMERKURS
| Bezeichnung |
| ---------------- |
| Database |
| Java Programming |
| Database |
And I need a Statment to generate following output:
| Bezeichnung | Count in Table Kurs |Count in Table Teilnehmerkurs
| ---------------- |-------------------- |-----------------------------
| Database | 1 |2
| Java Programming | 2 |1
I tried following statement:
select k.bezeichnung, count(k.bezeichnung), count(tk.bezeichnung)
from kurs k
left join teilnehmerkurs tk on tk.kursnr = k.kursnr
group by k.bezeichnung;
and my actual output is:
| Bezeichnung | Count in Table Kurs |Count in Table Teilnehmerkurs
| ---------------- |-------------------- |-----------------------------
| Database | 2 |2
| Java Programming | 2 |1

You can use FULL JOIN after group byseparately.
DECLARE #Kurs TABLE ( Bezeichnung VARCHAR(50))
INSERT INTO #Kurs VALUES
('Java Programming'),
('Java Programming'),
('Database')
DECLARE #TEILNEHMERKURS TABLE( Bezeichnung VARCHAR(50))
INSERT INTO #TEILNEHMERKURS VALUES
('Database'),
('Java Programming'),
('Database')
SELECT COALESCE(K.Bezeichnung, T.Bezeichnung) Bezeichnung
, K.[Count in Table Kurs]
, T.[Count in Table Teilnehmerkurs]
FROM
(SELECT Bezeichnung, COUNT(*) [Count in Table Kurs] FROM #Kurs GROUP BY Bezeichnung ) K
FULL JOIN
(SELECT Bezeichnung, COUNT(*) [Count in Table Teilnehmerkurs] FROM #TEILNEHMERKURS GROUP BY Bezeichnung) T
ON K.Bezeichnung = T.Bezeichnung
Result:
Bezeichnung Count in Table Kurs Count in Table Teilnehmerkurs
-------------------- ------------------- -----------------------------
Database 1 2
Java Programming 2 1

This can be tricky. A pretty fool-proof method is union all and group by:
select Bezeichnung, sum(inkurs), sum(inTeilnehmerkurs)
from ((select Bezeichnung, count(*) as inkurs, 0 as inTeilnehmerkurs
from kurs
group by Bezeichnung
) union all
(select Bezeichnung, 0 as inkurs, count(*) as inTeilnehmerkurs
from Teilnehmerkurs
group by Bezeichnung
)
) kt
group by Bezeichnung;
If you use a join to bring the results together, then you have to be careful about missing Bezeichnung in either table. If you use a full outer join, then you need to use coalesce().

You could use select union for obtain all the Bezeichnung from both the table and then left join the count
select t.Bezeichnung, tk.count_in_kurs, tt.count_in_teilnehmerkurs
from (
select Bezeichnung
from Kurs
union
select Bezeichnung
from TEILNEHMERKURS)
left join (
select Bezeichnung, count(*) as count_in_kurs
from kurs
group by Bezeichnung
) tk on t.Bezeichnung = tk.Bezeichnung
left join (
select Bezeichnung, count(*) as count_in_TEILNEHMERKURS
from kurs
group by Bezeichnung
) tt on t.Bezeichnung = tt.Bezeichnung

Related

Get related tables not contains result

I have a DesignGroup table as:
+--------------------------------------+----------+
| DesignGroupId | Name |
+--------------------------------------+----------+
| 3A81C1FF-442F-4291-B8E2-7079D80920CF | Design 1 |
| 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | Design 2 |
+--------------------------------------+----------+
Each DesignGroup can have multiple customers, so I have a table DesignGroupCustomers as:
+--------------------------------------+--------------------------------------+-------------+
| DesignGroupCustomerId | DesignGroupId (FK) | CustomerKey |
+--------------------------------------+--------------------------------------+-------------+
| D0828677-F295-46F7-BB85-65888D5A48B7 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 10 |
| 10C01BB9-1DDB-4DB4-BEC4-9539E030BF68 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 20 |
| F88C9F66-C0D9-EB11-8481-5CF9DDF6DC87 | 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | 10 |
+--------------------------------------+--------------------------------------+-------------+
Each customer have a CustomerType as, customerTable:
+-------------+-------------+
| CustomerKey | CustTypeKey |
+-------------+-------------+
| 10 | 2 |
| 20 | 1 |
+-------------+-------------+
That I want to achieve is to get only this statement:
return only the DesignGroup who not have a customer with custTypeKey = 1
In this case it should return Design 2 because it does not have customer with custTypeKey = 1
I was thinking about CTE usage but I just have not idea how to get the desire result:
;WITH CTE
AS (SELECT
[DG].[DesignGroupId]
, ROW_NUMBER() OVER(PARTITION BY [DesignGroupCustomer]) AS [RN]
FROM [DesignGroup] AS [DG]
INNER JOIN [DesignGroupCustomer] AS [DGC] ON [DG].[DesignGroupId] = [DGC].[DesignGroupId]
INNER JOIN [Customer] AS [C] ON [DGC].[CustomerKey] = [C].[CustomerKey]
INNER JOIN [CustomerType] AS [CT] ON [C].[CustTypeKey] = [CT].[CustTypeKey])
SELECT
[DesignGroupId]
FROM [CTE] -- WHERE CustomerType NOT CONTAINS (1)
WITH temp AS (
SELECT DISTINCT
dgc.DesignGroupId AS DesignGroupId
FROM DesignGroupCustomers dgc
INNER JOIN customerTable ct
ON dgc.CustomerKey = ct.CustomerKey
WHERE ct.CustTypeKey = 1
)
SELECT
DesignGroupId
FROM DesignGroup
WHERE DesignGroupId NOT IN (
SELECT
DesignGroupId
FROM temp
)
Firstly, you can get all designgroups having CustTypeKey =1 and then get all other designgroups using NOT IN. Please let me know if you face any issues
You can use a subquery to return the design groups which have this customer type key of 1 and then LEFT JOIN the subquery on the design table and filter down to results that have a DesignGroupId of null (any design group that isn't included in the dataset of the subquery)
SELECT d.[DesignGroupId]
FROM [DesignGroup] AS d
LEFT JOIN
(
SELECT dgc.[DesignGroupId]
FROM [DesignGroupCustomer] AS dgc
ON dgc.[DesignGroupId] = d.[DesignGroupId]
INNER JOIN [Customer] AS c
ON c.[CustomerKey] = dgc.[CustomerKey]
WHERE c.[CustTypeKey] = 1
GROUP BY dgc.[DesignGroupId]
) x
ON x.[DesignGroupId] = d.[DesignGroupId]
WHERE x.[DesignGroupId] IS NULL

How to Inner Join Three Tables and Return Maximum of Value in Third

I have three tables as follows:
First table:
ordenes
id_orden | date | total | id_usuario
1 |15-may|50 | 1
2 |20-may|60 | 2
Second table:
usuario
id_usuario | name | phone
1 | abc | 999
2 | def | 888
Third table:
estado
id_orden | edo
1 | c
1 | b
1 | a
2 | b
2 | a
And this is the desired result:
Results:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
maxedo needs to be the maximum record from the edo in the third table after aggregating based on order.
How do I do this?
The below code sample gives you the result.
CREATE TABLE #ordenes(id_orden int, datevalue date, total int, id_usuario int)
INSERT INTO #ordenes
VALUES
(1,'20160515',50,1),
(2,'20160520',60,2)
CREATE TABLE #usuario(id_usuario int, name varchar(10), phone int)
INSERT INTO #usuario
VALUES
(1,'abc',999),
(2,'def',888)
CREATE TABLE #estado(id_orden int, edo char(1))
INSERT INTO #estado
VALUES
(1,'c'),
(1,'b'),
(1,'a'),
(2,'b'),
(2,'a')
SELECT id_orden,datevalue,total,id_usuario,name,phone,edo as maxedo
FROM
(SELECT o.id_orden,o.datevalue,o.total,o.id_usuario,u.name,u.phone,e.edo,ROW_NUMBER() OVER(PARTITION BY o.id_orden ORDER BY e.edo DESC) as rnk
FROM #ordenes o
JOIN #usuario u
on o.id_usuario = u.id_usuario
join #estado e
on o.id_orden = e.id_orden) as t
where rnk = 1
The following should do the job (assuming edo is actually a numeric amount). I've included aliases using the AS command so you even get the column titles you want.
SELECT
oe.id_orden AS id_orden,
oe.date AS date,
oe.total AS total,
u.id_usario AS id_usuario,
u.name AS name,
u.phone AS phone,
oe.maxedo AS maxedo
FROM usuario u
INNER JOIN
(SELECT
o.id_orden AS id_orden,
o.date AS date,
o.total AS total,
o.id_usuario AS id_usuario,
e.maxestedo AS maxestedo
FROM ordenes o
INNER JOIN
(SELECT
id_orden AS id_orden,
MAX(edo) AS maxedo
FROM estado
GROUP BY id_orden) e
ON e.id_orden=o.id_orden) oe
ON u.id_usuario=oe.id_usuario
In order of processing (which is not how SQL works but is useful way of breaking it down into steps) it goes:
Create table of Maximum edos (NB: MAX also works on alphabetical order);
Links this to ordenes using the id_ordene;
Joins this to usuario data using the id_usuario; and
Publishes this as a table in the required format.
The problem can be split into the following three steps:
Step 1: Calculate maximum edo for each id_orden in the table estado:
Select id_orden, max(edo) maxedo
From estado
Group By id_orden;
Result:
| id_orden | edo |
| 1 | c |
| 2 | b |
Step 2: Join the two tables ordenes and usuario on the key "id_usuario":
Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Join usuario u
On o.id_usuario = u.id_usuario;
Result:
id_orden | date | total | id_usuario | name | phone
1 |15-may|50 | 1 | abc | 999
2 |20-may|60 | 2 | def | 888
Step 3: Join the table form the step1 and step2 on the key id_orden:
Select a.id_orden, a.date, a.total, a.id_usuario, a.name, a.phone, b.maxestado
From (Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Inner Join usuario u
On o.id_usuario = u.id_usuario ) a
Join (Select id_orden, max(edo) maxestado
From estado
Group By id_orden) b
On a.id_orden = b.id_orden;
Result:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
SQLFiddle example: http://sqlfiddle.com/#!5/a79a1/2
:)
I think you need to get the max(edo) from the third table and group by id_orden, yes? try this.
select temp.*, max(edo) as maxedo
from estado
inner join(
select ordenes.*,usuario.name,usuario.phone
from ordenes,usuario
where ordenes.id_usuario = usuario.id_usuario
) as temp
on temp.id_orden = estado.id_orden
group by estado.id_orden

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id

Select record if exist in one table, else select from another

I'm writing a report for quotes. The quote table have a copy to track the change history, so every time someone change the quote table the old values are salved in this history table.
I want to include a field in my report named Original Due Date, which will show what is the original due date for the quote. If there is a record in the history table, it should get the first due date from there. Otherwise it should get the due date from the original table.
Here is some sample of the records I have in my tables.
Table 1:
|ID | Order | Due Date |
|1 | C1234 | 15/01/2000 |
|2 | C1235 | 15/02/2000 |
|3 | C1236 | 15/03/2000 |
|4 | C1237 | 15/04/2000 |
History Table:
|ID | Order | Due Date |
|1 | C1234 | 02/01/2000 |
|2 | C1234 | 05/01/2000 |
|3 | C1236 | 05/03/2000 |
|4 | C1236 | 07/03/2000 |
Expected results:
|ID | Order | Original Due Date |
|1 | C1234 | 02/01/2000 |
|2 | C1235 | 15/02/2000 |
|3 | C1236 | 05/03/2000 |
|4 | C1237 | 15/04/2000 |
This is the code I've tried, which does not work because my subquery return more than 1 value.
select case when exists (select 1 from quoteheader qh inner join quoteheaderhistory qhh on QH.QH_RecordID = QHH.QH_RecordID)
then (select QHH.QH_RFQ_Date from quoteheader qh inner join quoteheaderhistory qhh on QH.QH_RecordID = QHH.QH_RecordID)
else QH.QH_RFQ_Date
end,
* from quoteheader qh inner join quoteheaderhistory qhh
on QH.QH_RecordID = QHH.QH_RecordID
This should work:
DECLARE #tblO TABLE(ID INT,[Order] VARCHAR(10),DueDate DATE);
INSERT INTO #tblO VALUES(1,'C1234','20000115'),(2,'C1235','20000215'),(3,'C1236','20000315'),(4,'C1237','20000415');
DECLARE #tblH TABLE(ID INT,[Order] VARCHAR(10),DueDate DATE);
INSERT INTO #tblH VALUES(1,'C1234','20000102'),(2,'C1234','20000105'),(3,'C1236','20000305'),(4,'C1236','20000307');
WITH MinHistoricalDates AS
(
SELECT MIN(DueDate) AS MinHistDat,[order]
FROM #tblH
GROUP BY [order]
)
SELECT orig.ID
,orig.[order]
,ISNULL(mhd.MinHistDat,orig.DueDate) AS DueDateResolved
FROM #tblO AS orig
LEFT JOIN MinHistoricalDates AS mhd ON mhd.[order]=orig.[order]
try:
select [Order],[DueDate] from quoteheader
where [Order] not in
(select [Order] from
(select [Order],MIN(DueDate) as DueDate
from quoteheaderhistory
group by [Order]) a
)
union all
select [Order],MIN(DueDate) as DueDate
from quoteheaderhistory
group by [Order]
although the join answer might work better for you, depending on your data
Here's what I was talking about in my comment:
DECLARE #A TABLE ([ID] INT, [Order] CHAR(5), [DueDate] DATE)
DECLARE #H TABLE ([ID] INT, [Order] CHAR(5), [DueDate] DATE)
INSERT INTO #A VALUES
(1, 'C1234', '01/15/2000'),
(2, 'C1235', '02/15/2000'),
(3, 'C1236', '03/15/2000'),
(4, 'C1237', '04/15/2000')
INSERT INTO #H VALUES
(1, 'C1234', '01/02/2000'),
(2, 'C1234', '01/05/2000'),
(3, 'C1236', '03/05/2000'),
(4, 'C1236', '03/07/2000')
SELECT
[header].[ID],
[header].[Order],
[DueDate] = ISNULL(MIN([history].[DueDate]), MIN([header].[DueDate]))
FROM
#A [header]
LEFT JOIN #H [history] ON [header].[Order] = [history].[Order]
GROUP BY
[header].ID, [header].[Order];
EDIT: Shnugo's answer has a better execution plan, though; go with that one.
Alternatively, you could also do a left join and use windowed function version of MIN to achieve your result:
select distinct
t1.id,
t1.[order],
COALESCE(min(ht.Due_Date) OVER (partition by t1.[order] order by t1.id)
,min(t1.due_date) OVER (partition by t1.[order] order by t1.id)) as Original Due Date
from table1 t1
left join history_table ht on ht.[Order] = t1.[Order];
SQL Fiddle Demo
Try this:
SELECT _order, min(due_date) as due_date
FROM history JOIN table1 USING (_order)
GROUP BY _order
This solution ignores the id, but you can easily generate after getting appropiate values.

Distinct on two columns (separately) and MIN on another column

I've tried to strip this problem down the the bare bones; I hope I've still captured the essence of what I'm trying to achieve in the original query!
Code to generate the tables and data can be found here.
SQL flavour is Microsoft SQL Server 2000 (although I've been running this stripped down test case on MySQL)
The original table
+-----------+----------+----------+
| master_id | slave_id | distance |
+-----------+----------+----------+
| 1 | 1 | 0.1 |
| 1 | 3 | 10 |
| 2 | 2 | 3 |
| 3 | 2 | 2 |
+-----------+----------+----------+
Description of what is required
I would like to select slave_id master_id pairs with MIN(distance) with no duplicates of either master_id or slave_id.
The desired results table
+-----------+----------+----------+
| master_id | slave_id | distance |
+-----------+----------+----------+
| 1 | 1 | 0.1 |
| 3 | 2 | 2 |
+-----------+----------+----------+
My Attempt
SELECT
join_table.master_id,
join_table.slave_id,
join_table.distance
FROM join_table
INNER JOIN
(
SELECT
slave_id,
MIN(distance) AS distance
FROM join_table
GROUP BY slave_id
) AS self_join
ON self_join.slave_id = join_table.slave_id
AND self_join.distance = join_table.distance
What's wrong with my attempt
This query produces duplicates of master_id
Any help will be very much appreciated.
This should give the correct result:
select distinct t.master_id,
t.slave_id,
t.distance
from join_table t
inner join
(
SELECT ID, min(Distance) dist
FROM
(
SELECT master_ID ID, MIN(distance) AS Distance
FROM join_table
GROUP BY master_ID
UNION
SELECT slave_ID ID, MIN(distance) AS Distance
FROM join_table
GROUP BY slave_ID
) src
GROUP BY ID
) md
on t.distance = md.dist
and (t.master_id = md.id or t.slave_id = md.id)
See SQL Fiddle with Demo
If I got you right, here is what I would do:
SELECT DISTINCT t.master_id
,t.slave_id
,t.distance
FROM your_table t
INNER JOIN
(
SELECT master_id id, min(distance) distance
FROM your_table
GROUP BY master_id
UNION
SELECT slave_id id, min(distance) distance
FROM your_table
GROUP BY slave_id
) sub
ON (sub.id = t.master_id AND sub.distance = t.distance)
OR (sub.id = t.slave_id AND sub.distance = t.distance)