I want to make this happen in SQL Server - sql

driverphone| drivername|guarantor1_phone|guarantor2_phone
---------------------------------------------------------
0801 |Mr A |0803 |0802
0802 |Mr B |0804 |0801
0803 |Mr C |0805 |0801
0804 |Mr D |0802 |0805
0805 |Mr E |0801 |0803
I want to get this result set in SQL Server
driverphone| drivername|Total Guaranteed
----------------------------------------
0801 |Mr A | 3
0802 |Mr B | 2
0803 |Mr C | 2
0804 |Mr D | 1
0805 |Mr E | 2
That is to select the total number guaranteed by each driver.
driver->guarantor relationship is based on phone numbers.

Do make LEFT JOIN with both guarantor columns and make a Distinct count of it.
Schema:
CREATE TABLE #TAB (
driverphone VARCHAR(10)
,drivername VARCHAR(10)
,guarantor1_phone VARCHAR(10)
,guarantor2_phone VARCHAR(10)
)
INSERT INTO #TAB
SELECT '0801',' Mr A', '0803', '0802'
UNION ALL
SELECT '0802',' Mr B', '0804', '0801'
UNION ALL
SELECT '0803',' Mr C', '0805', '0801'
UNION ALL
SELECT '0804',' Mr D', '0802', '0805'
UNION ALL
SELECT '0805',' Mr E', '0801', '0803'
Now do select like below
SELECT T.driverphone
,T.drivername
,COUNT(DISTINCT T2.driverphone) + COUNT(DISTINCT T3.driverphone)
FROM #TAB T
LEFT JOIN #TAB T2 ON T.driverphone = T2.guarantor1_phone
LEFT JOIN #TAB T3 ON T.driverphone = T3.guarantor2_phone
GROUP BY T.driverphone
,T.drivername
Result will be
+-------------+------------+------------------+
| driverphone | drivername | (No column name) |
+-------------+------------+------------------+
| 0801 | Mr A | 3 |
| 0802 | Mr B | 2 |
| 0803 | Mr C | 2 |
| 0804 | Mr D | 1 |
| 0805 | Mr E | 2 |
+-------------+------------+------------------+

I think the simplest method is outer apply:
select t.driverphone, t.drivername, g.totalguaranteed
from t outer apply
(select count(*) as totalguaranteed
from (values (guarantor1_phone), (guarantor2_phone)) v(guarantor)
where v.guarantor = t.driverphone
) g;

Related

How to get the count of policies for rejection codes

I have a requirement where i need to map the rejection codes and give the count of the Policies of Each type for Each rejection codes. Example mentioned as below.
Policy Table:
PolicyNo | PolType |MyRejectionCode
----------|---------|--------------
Pol1 | S | M101
----------|---------|--------------
Pol2 | S | M101, M102
----------|---------|--------------
Pol3 | S | M104
----------|---------|--------------
Pol4 | F | M105, M107, M108
----------|---------|--------------
Pol5 | F | M106
----------|---------|--------------
Pol6 | F | M107
RejectionMapping Table:
MyRejCode | StandardRejCode
-----------------|----------------
M101 | S101
-----------------|----------------
M102 | S102
-----------------|----------------
M103 | S103
-----------------|----------------
M104 | S104
-----------------|----------------
M105 | S101
-----------------|----------------
M106 | S102
-----------------|----------------
M107 | S103
-----------------|----------------
M108 | S104
Now i want to create a select query to give the result in below format.
StandartRejCode | S_Pol_Type_Count | F_Pol_Type_Count
-----------------|--------------------|----------------------
S101 | 2 | 1
-----------------|--------------------|----------------------
S102 | 1 | 1
-----------------|--------------------|----------------------
S103 | 0 | 2
-----------------|--------------------|----------------------
S104 | 1 | 1
How can I create a query for mentioned output?
Sample Data
DECLARE #Policy AS TABLE (PolicyNo VARCHAR(100) , PolType VARCHAR(100) ,MyRejectionCode VARCHAR(100))
INSERT INTO #Policy
SELECT 'Pol1','S','M101' UNION ALL
SELECT 'Pol2','S','M101,M102' UNION ALL
SELECT 'Pol3','S','M104' UNION ALL
SELECT 'Pol4','F','M105,M107,M108' UNION ALL
SELECT 'Pol5','F','M106' UNION ALL
SELECT 'Pol6','F','M107'
DECLARE #RejectionMapping AS Table ( MyRejCode VARCHAR(100), StandardRejCode VARCHAR(100))
INSERT INTO #RejectionMapping
SELECT 'M101','S101' UNION ALL
SELECT 'M102','S102' UNION ALL
SELECT 'M103','S103' UNION ALL
SELECT 'M104','S104' UNION ALL
SELECT 'M105','S101' UNION ALL
SELECT 'M106','S102' UNION ALL
SELECT 'M107','S103' UNION ALL
SELECT 'M108','S104'
Now We need to split the comma separated data into single row using xml format.
Then need to join both the tables using inner join and finally find the count of occurrences of "PolType"
;WITH Cte
AS
(
SELECT PolicyNo,PolType,Split.a.value ('.','nvarchar(max)') AS MyRejectionCode
FROM
(
SELECT PolicyNo, PolType,
CAST('<S>'+REPLACE(MyRejectionCode,',','</S><S>')+'</S>' AS XML) AS MyRejectionCode
FROM #Policy
)AS A
CROSS APPLY MyRejectionCode.nodes('S') AS Split(a)
)
SELECT R.StandardRejCode,
ISNULL(SUM(CASE WHEN PolType = 'S' THEN 1 ELSE 0 END),0) AS S_Pol_Type_Count,
ISNULL(SUM(CASE WHEN PolType = 'F' THEN 1 ELSE 0 END),0) AS F_Pol_Type_Count
FROM Cte c
INNER JOIN #RejectionMapping R
ON R.MyRejCode = c.MyRejectionCode
GROUP BY R.StandardRejCode
Result
StandardRejCode S_Pol_Type_Count F_Pol_Type_Count
------------------------------------------------------
S101 2 1
S102 1 1
S103 0 2
S104 1 1

Conditionally fallback to different join condition if stricter condition not matched

I have 2 tables j and c.
Both tables have columns ports and sec, and JOIN ON j.ports = c.ports and c.sec = j.sec.
For j.port = 'ABC', if there is no c.sec = j.sec for the same ports, then JOIN ON LEFT(c.sec, 6) = LEFT(j.sec, 6)
For other j.ports, I only want to join ON j.ports = c.ports and c.sec = j.sec
How can I do that?
Example Data
Table c
+------+------------+------------+
| Port | sec | Other |
+------+------------+------------+
| ABC | abcdefghij | ONE |
| ABC | klmnop | TWO |
| LMN | qwertyuiop | THREE |
| XYZ | asdfghjkl | FOUR |
+------+------------+------------+
Table j
+------+------------+
| Port | sec |
+------+------------+
| ABC | abcdefxxxx |
| ABC | klmnop |
| LMN | qwertyuiop |
| XYZ | zxcvbnm |
+------+------------+
EDITED: Desired Results
+------+------------+------------+
| Port | sec | other |
+------+------------+------------+
| ABC | abcdefghij | ONE | --> mactching on sec's 1st 6 characters
| ABC | klmnop | TWO | --> mactching on sec
| LMN | qwertyuiop | THREE | --> mactching on sec
+------+------------+------------+
This does conditional joining:
select t1.*, t2.*
from j t1 inner join c t2
on t2.ports = t1.ports and
case
when exists (select 1 from c where sec = t1.sec) then t1.sec
else left(t1.sec, 6)
end =
case
when exists (select 1 from c where sec = t1.sec) then t2.sec
else left(t2.sec, 6)
end
I question its efficiency but I think it does what you need.
See the demo.
You can do two outer joins and then do isnull type of operation. In oracle nvl is isnull of sqlserver
with c as
(
select 'ABC' port, 'abcdefghij' sec from dual
union all select 'ABC', 'klmnop' from dual
union all select 'LMN', 'qwertyuiop' from dual
union all select 'XYZ', 'asdfghjkl' from dual
),
j as
(
select 'ABC' port, 'abcdefxxxx' sec from dual
union all select 'ABC', 'klmnop' from dual
union all select 'LMN', 'qwertyuiop' from dual
union all select 'XYZ', 'zxcvbnm' from dual
)
select c.port, c.sec, nvl(j_full.sec, j_part.sec) j_sec
from c
left outer join j j_full on j_full.port = c.port and j_full.sec = c.sec
left outer join j j_part on j_part.port = c.port and substr(j_part.sec,1,6) = substr(c.sec,1,6)
order by 1,2
One way would be to just inner join on the less strict predicate then use a ranking function to discard unwanted rows in the event that c.port = 'ABC' and the stricter condition got a match for a particular c.port, c.sec combination.
with cte as
(
select c.port as cPort,
c.sec as cSec,
c.other as other,
j.sec as jSec,
RANK() OVER (PARTITION BY c.port, c.sec ORDER BY CASE WHEN c.port = 'ABC' AND j.sec = c.sec THEN 0 ELSE 1 END) AS rnk
from c inner join j on left(j.sec,6) = left(c.sec,6)
)
SELECT cPort, cSec, other, jSec
FROM cte
WHERE rnk = 1

Multiple select from CTE with different number of rows in a StoredProcedure

How to do two select with joins from the cte's which returns total number of columns in the two selects?
I tried doing union but that appends to the same list and there is no way to differentiate for further use.
WITH campus AS
(SELECT DISTINCT CampusName, DistrictName
FROM dbo.file
),creditAcceptance AS
(SELECT CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal, COUNT(id) AS N
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%') AND (CollegeCreditEarnedFinal = 'Yes') AND (CollegeCreditAcceptedFinal = 'Yes')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
),eligibility AS
(SELECT CampusName, EligibilityStatusFinal, COUNT(id) AS N, CollegeCreditAcceptedFinal
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
)
SELECT a.CampusName, c.[EligibilityStatusFinal], SUM(c.N) AS creditacceptCount
FROM campus as a FULL OUTER JOIN creditAcceptance as c ON a.CampusName=c.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName ,c.EligibilityStatusFinal
Union ALL
SELECT a.CampusName , b.[EligibilityStatusFinal], SUM(b.N) AS eligible
From Campus as a FULL OUTER JOIN eligibility as b ON a.CampusName = b.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName,b.EligibilityStatusFinal
Expected output:
+------------+------------------------+--------------------+
| CampusName | EligibilityStatusFinal | creditacceptCount |
+------------+------------------------+--------------------+
| M | G | 1 |
| E | NULL | NULL |
| A | G | 4 |
| B | G | 8 |
+------------+------------------------+--------------------+
+------------+------------------------+----------+
| CampusName | EligibilityStatusFinal | eligible |
+------------+------------------------+----------+
| A | G | 8 |
| C | G | 9 |
| A | T | 9 |
+------------+------------------------+----------+
As you can see here CTEs can be used in a single statement only, so you can't get the expected output with CTEs.
Here is an excerpt from Microsoft docs:
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns. A CTE can also
be specified in a CREATE VIEW statement as part of the defining SELECT
statement of the view.
You can use table variables (declare #campus table(...)) or temp tables (create table #campus (...)) instead.

Join returns too much info

I have 2 tables
Table1
+---------+--------+-------+----+
| CALDATE | GROOMS | ROOMS | fn |
+---------+--------+-------+----+
| 1/5/18 | 15 | 17 | A12|
| 1/5/18 | 0 | 0 | A12|
| 1/6/18 | 0 | 0 | B34|
| 1/6/18 | 75 | 77 | B34|
| 1/7/18 | 123 | 125 | C56|
| 1/7/18 | 0 | 0 | C56|
+---------+--------+-------+----+
-
Table2
+----------+--------+----+
| ROOMDATE | pickup | FN |
+----------+--------+----+
| 1/5/18 | 0 | A12|
| 1/5/18 | 2 | A12|
| 1/5/18 | 1 | A12|
| 1/5/18 | 7 | A12|
| 1/6/18 | 2 | B34|
| 1/6/18 | 1 | B34|
| 1/6/18 | 13 | B34|
| 1/7/18 | 3 | C56|
| 1/7/18 | 0 | C56|
| 1/7/18 | 12 | C56|
+----------+--------+----+
Querying each I use
Select caldate as date, sum(grooms) as g, sum (rooms) as r
from Table1
and
Select roomdate as date, sum(pickup) as p
from Table2
These each give me the info I'm expecting, however when I try and join them things get wonky. I was hoping for something like
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
But that returns way too high of each.
How do I join these queries so that I get my expected output of
+--------+-----+-----+----+----+
| Date | g | r | p | fn |
+--------+-----+-----+----+----+
| 1/5/18 | 15 | 17 | 10 | A12|
| 1/6/18 | 75 | 77 | 16 | B34|
| 1/7/18 | 123 | 125 | 15 | C56|
+--------+-----+-----+----+----+
Each row in your first table will match with each available row in the other table based on your join predicate. Take fn = A12 for example: since you have 2 rows in table1 and 4 rows in table2, you will end up with (4x2) 8 rows in your result set. That will cause your sums to be higher than they should be.
One way to fix this is to use derived tables to get your sums, then join them together:
SELECT t1.date, g, r, p, t1.fn
FROM (SELECT fn, caldate as date, sum(grooms) as g, sum (rooms) as r
FROM Table1
GROUP BY fn, caldate) t1
JOIN (SELECT fn, roomdate as date, sum(pickup) as p
FROM Table2
GROUP BY fn, roomdate) t2 on t1.fn = t2.fn
This makes sure there is one row returned from each table before the join.
You should be Grouping by 'caldate'. This way you will only be getting the sums per date.
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
group by caldate
The reason that you get more rows than expected is because of join condition. if you want to find grooms and rooms for each fn on each day you have add date to join condition as well:
Select table1.caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from table1
inner join table2 on table1.fn = table2.fn
and table1.caldate = table2.roomdate
Here's an answer that allows you to group by FN and date:
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from Table1 t1
inner join (
select fn, roomdate, sum(pickup) as p from Table2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
I created some sample data with DML statements so you can test grouping by different combinations of FN and date, and the output:
declare #t1 table (caldate datetime, grooms int, rooms int, fn varchar(3))
declare #t2 table (roomdate datetime, pickup int, fn varchar(3))
insert into #t1 select '1/5/18', 15, 17,'A12'
insert into #t1 select '1/5/18', 0, 0,'A12'
insert into #t1 select '1/6/18', 0, 0,'B34'
insert into #t1 select '1/6/18', 75, 77,'B34'
insert into #t1 select '1/7/18',123,125,'C56'
insert into #t1 select '1/8/18',100,200,'C56' -- changed to 1/8/18, changed vals
insert into #t2 select '1/5/18', 0 ,'A12'
insert into #t2 select '1/5/18', 2 ,'A12'
insert into #t2 select '1/5/18', 1 ,'A12'
insert into #t2 select '1/5/18', 7 ,'A12'
insert into #t2 select '1/6/18', 2 ,'B34'
insert into #t2 select '1/6/18', 1 ,'B34'
insert into #t2 select '1/6/18',13 ,'B34'
insert into #t2 select '1/7/18', 3 ,'C56'
insert into #t2 select '1/7/18', 0 ,'C56'
insert into #t2 select '1/8/18',12 ,'C56' -- changed to 1/8/18
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from #t1 t1
inner join (
select fn, roomdate, sum(pickup) as p from #t2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
Output:
date g r p fn
1/5/2018 15 17 10 A12
1/6/2018 75 77 16 B34
1/7/2018 123 125 3 C56
1/8/2018 100 200 12 C56
If you don't also join by date, then adding different combinations of caldate/fn give you duplicates. You must mean to join by the date as well, right?

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