Left Join between 3 tables - sql

I have 3 tables as follows:
Table 1 contains the following
Id Name Section Salary
---------------------------------
1 Mark It 1000
2 Dad Hr 2000
Table 2 contains this sample data:
Id Item Salary
--------------------------
1 Holday 50
1 Food 30
1 Rent 100
2 Food 200
2 Rent 200
Table 3 contains this data:
Id Descriptions Cost
---------------------------
1 Bonce 150
1 Rate 300
2 Car 100
2 Bonce 15
2 Rate 30
I need to have the data like the attached screenshot:

That is an interesting computing problem independent of your use. It does not a simple join the tables. That is the part of SQL Server enforce, in your report tool you need to do the adjusts.
Table preparation
DECLARE #table1 table (Id int, Name varchar(10), Section varchar(10), Salary decimal(18,2))
DECLARE #table2 table (Id int, Item varchar(10), Salary decimal(18,2))
DECLARE #table3 table (Id int, Descriptions varchar(10), Cost decimal(18,2))
INSERT #table1 values
(1, 'Mark', 'It', 1000)
,(2, 'Dad ', 'Hr', 2000)
INSERT #table2 Values
(1,'Holday', 50)
,(1,'Food ', 30)
,(1,'Rent ',100)
,(2,'Food ',200)
,(2,'Rent ',200)
INSERT #table3 values
(1, 'Bonce', 150)
,(1, 'Rate ', 300)
,(2, 'Car ', 100)
,(2, 'Bonce', 15)
,(2, 'Rate ', 30)
The Select
;WITH
lv0(n) AS (SELECT 0 FROM (VALUES (0), (0))G(n)), --2
lv1(n) AS (SELECT 0 FROM lv0 a CROSS JOIN lv0 b), -- 4
lv2(n) AS (SELECT 0 FROM lv1 a CROSS JOIN lv1 b), -- 16
lv3(n) AS (SELECT 0 FROM lv2 a CROSS JOIN lv2 b), -- 256
lv4(n) AS (SELECT 0 FROM lv3 a CROSS JOIN lv3 b), -- 65,536
--lv5(N) as (select 0 from lv4 a cross join lv4 b), -- 4,294,967,296
tally(n) AS (SELECT Row_number() OVER( ORDER BY (SELECT NULL)) FROM lv4),
t1 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table1),
t2 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table2),
t3 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table3)
SELECT a.NAME
,a.section
,a.section
,b.item
,b.salary
,c.descriptions
,c.cost
,a.id
,b.id
,c.id
FROM tally t
CROSS JOIN t1 a
LEFT JOIN t2 b
ON t.n = b.n
AND a.id = b.id
LEFT JOIN t3 c
ON t.n = C.n
AND a.id = c.id
WHERE a.id IS NOT NULL
AND ( b.id IS NOT NULL
OR c.id IS NOT NULL )
ORDER BY a.n
,b.n
,c.n
The result
NAME section section item salary descriptions cost id id id
---------- ---------- ---------- ---------- --------------------------------------- ------------ --------------------------------------- ----------- ----------- -----------
Mark It It Holday 50.00 Bonce 150.00 1 1 1
Mark It It Food 30.00 Rate 300.00 1 1 1
Mark It It Rent 100.00 NULL NULL 1 1 NULL
Dad Hr Hr NULL NULL Car 100.00 2 NULL 2
Dad Hr Hr Food 200.00 Bonce 15.00 2 2 2
Dad Hr Hr Rent 200.00 Rate 30.00 2 2 2

Related

Randomly join tables and return columns

I have these 3 tables:
CREATE TABLE DimEmployee(EmployeeID INT)
CREATE TABLE DimDepartment(DepartmentID INT)
CREATE TABLE DimDocteur(PositionID INT)
INSERT INTO DimEmployee(EmployeeID) VALUES (1),(2),(3)
INSERT INTO DimDepartment(DepartmentID) VALUES (1),(5),(6)
INSERT INTO DimPosition(PositionID) VALUES (7),(8),(9)
I want to randomly join the 3 tables and get output like below : (example)
First execute:
EmployeeID DepartmentID PositionID RandomDate
1 4 7 2020-07-24 00:00:00.000
2 5 9 2020-11-25 00:00:00.000
Second execute:
EmployeeID DepartmentID PositionID RandomDate
1 4 7 2020-05-04 00:00:00.000
2 5 9 2020-10-30 00:00:00.000
If you want a random join :
SELECT DP.EmployeeID, Q.Department INTO #T1
FROM DimEmployee AS DP
CROSS APPLY (SELECT TOP 1 Dd.DepartmentID FROM DimDepartment AS DD
ORDER BY NEWID() ) AS Q
SELECT *
INTO #T2
FROM #T1 AS T
CROSS APPLY (SELECT TOP 1 DP.PositionID FROM DimPosition AS DP
ORDER BY NEWID() ) AS Q
Or if you want all possibilities :
SELECT
a.EmployeeID, b.DepartmentID, c.PositionID
FROM
DimEmployee AS a
CROSS JOIN
DimDepartment AS b
CROSS JOIN
DimPosition AS c
You need to row-number each table and join on row-number:
CREATE TABLE DimEmployee(EmployeeID INT)
CREATE TABLE DimDepartment(DepartmentID INT)
CREATE TABLE DimDocteur(PositionID INT)
SELECT
emp.EmployeeID,
dep.DepartmentID,
doc.PositionID,
DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0) RandomDate
FROM (
SELECT *, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) rn
FROM DimEmployee
) emp
JOIN (
SELECT *, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) rn
FROM DimDepartment
) dep ON dep.rn = emp.rn
JOIN (
SELECT *, ROW_NUMBER() OVER(ORDER BY (SELECT 1)) rn
FROM DimDocteur
) doc ON doc.rn = emp.rn
You can also change the ORDER BY to ORDER BY NEWID() to get a more random ordering.

SQL - spread previous values from one column into multiple new columns

I have a SQL table of Customer_ID, showing Payments by Year. The first (of many) customer appears like this:
ID Payment Year
112 0 2004
112 0 2005
112 0 2006
112 9592 2007
112 12332 2008
112 9234 2011
112 5400 2012
112 7392 2014
112 8321 2015
Note that some years are missing. I need to create 10 new columns, showing the Payments in the previous 10 years, for each row. The resulting table should look like this:
ID Payment Year T-1 T-2 T-3 T-4 T-5 T-6 T-7 T-8 T-9 T-10
112 0 2004 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
112 0 2005 0 NULL NULL NULL NULL NULL NULL NULL NULL NULL
112 0 2006 0 0 NULL NULL NULL NULL NULL NULL NULL NULL
112 952 2007 0 0 0 NULL NULL NULL NULL NULL NULL NULL
112 1232 2008 952 0 0 0 NULL NULL NULL NULL NULL NULL
112 924 2011 NULL NULL 1232 952 0 0 0 NULL NULL NULL
112 500 2012 924 NULL NULL 1232 952 0 0 0 NULL NULL
112 392 2014 NULL 500 924 NULL NULL 1232 952 0 0 0
112 821 2015 392 NULL 500 924 NULL NULL 1232 952 0 0
I am well aware that this is a large duplication of data, and so seems like a strange thing to do. However, I would still like to do it! (the data is being prepared for a predictive model, in which previous payments (and other info) will be used to predict the current year's payment)
I'm not really sure where to start with this. I have been looking at using pivot, but can't figure out how to get it to select values from a customer's previous year.
I would very much like to do this in SQL. If that is not possible I may be able to copy the table into R - but SQL is my preference.
Any help much appreciated.
You could use lag() if you had full data:
select t.*,
lag(payment, 1) over (partition by id order by year) as t_1,
lag(payment, 2) over (partition by id order by year) as t_2,
. . .
from t;
However, for your situation with missing intermediate years, left join may be simpler:
select t.*,
t1.payment as t_1,
t2.payment as t_2,
. . .
from t left join
t t1
on t1.id = t.id and
t1.year = t.year - 1 left join
t t2
on t1.id = t.id and
t1.year = t.year - 2 left join
. . .;
I thnk your friend will be LAG
Here's an implementation:
Declare #t table (
ID int,
Payment int,
Yr int
)
Insert Into #t Values(112,0,2004)
Insert Into #t Values(112,0,2005)
Insert Into #t Values(112,0,2006)
Insert Into #t Values(112,9592,2007)
Insert Into #t Values(112,12332,2008)
Insert Into #t Values(112,9234,2011)
Insert Into #t Values(112,5400,2012)
Insert Into #t Values(112,7392,2014)
Insert Into #t Values(112,8321,2015)
Insert Into #t Values(113,0,2009)
Insert Into #t Values(113,9234,2011)
Insert Into #t Values(113,5400,2013)
Insert Into #t Values(113,8321,2015)
;with E1(n) as (Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1 Union All Select 1)
,E2(n) as (Select 1 From E1 a, E1 b)
,E4(n) as (Select 1 From E2 a, E2 b)
,E5(n) as (Select row_number() over(order by isnull(null,1)) From E4 a, E1 b)
,IDYears as (
Select z.ID, Yr = y.n
From (
Select
Id,
MinYear = min(Yr),
MaxYear = max(Yr)
From #t a
Group By Id
) z
Inner Join E5 y On y.n between z.MinYear and z.MaxYear
)
Select
*,
[t-1] = Lag(B.Payment, 1) Over(Partition By a.ID Order By a.Yr),
[t-2] = Lag(B.Payment, 2) Over(Partition By a.ID Order By a.Yr),
[t-3] = Lag(B.Payment, 3) Over(Partition By a.ID Order By a.Yr),
[t-4] = Lag(B.Payment, 4) Over(Partition By a.ID Order By a.Yr),
[t-5] = Lag(B.Payment, 5) Over(Partition By a.ID Order By a.Yr),
[t-6] = Lag(B.Payment, 6) Over(Partition By a.ID Order By a.Yr),
[t-7] = Lag(B.Payment, 7) Over(Partition By a.ID Order By a.Yr),
[t-8] = Lag(B.Payment, 8) Over(Partition By a.ID Order By a.Yr),
[t-9] = Lag(B.Payment, 9) Over(Partition By a.ID Order By a.Yr),
[t-10] = Lag(B.Payment, 10) Over(Partition By a.ID Order By a.Yr)
From IDYears a
Left Join #t b On a.ID = b.ID and a.Yr = b.Yr
Order By A.ID

Perform ranking depend on category

I Have a table looks like this:
RowNum category Rank4A Rank4B
-------------------------------------------
1 A
2 A
3 B
5 A
6 B
9 B
My requirement is based on the RowNum order, Make two new ranking columns depend on category. Rank4A works like the DENSERANK() by category = A, but if the row is for category B, it derives the latest appeared rank for category A order by RowNum. Rank4B have similar logic, but it orders by RowNum in DESC order. So the result would like this (W means this cell I don't care its value):
RowNum category Rank4A Rank4B
-------------------------------------------
1 A 1 W
2 A 2 W
3 B 2 3
5 A 3 2
6 B W 2
9 B W 1
One more additional requirement is that CROSS APPLY or CURSOR is not allowed due to dataset being large. Any neat solutions?
Edit: Also no CTE (due to MAX 32767 limit)
You can use the following query:
SELECT RowNum, category,
SUM(CASE
WHEN category = 'A' THEN 1
ELSE 0
END) OVER (ORDER BY RowNum) AS Rank4A,
SUM(CASE
WHEN category = 'B' THEN 1
ELSE 0
END) OVER (ORDER BY RowNum DESC) AS Rank4B
FROM mytable
ORDER BY RowNum
Giorgos Betsos' answer is better, please read it first.
Try this out. I believe each CTE is clear enough to show the steps.
IF OBJECT_ID('tempdb..#Data') IS NOT NULL
DROP TABLE #Data
CREATE TABLE #Data (
RowNum INT,
Category CHAR(1))
INSERT INTO #Data (
RowNum,
Category)
VALUES
(1, 'A'),
(2, 'A'),
(3, 'B'),
(5, 'A'),
(6, 'B'),
(9, 'B')
;WITH AscendentDenseRanking AS
(
SELECT
D.RowNum,
D.Category,
AscendentDenseRanking = DENSE_RANK() OVER (ORDER BY D.Rownum ASC)
FROM
#Data AS D
WHERE
D.Category = 'A'
),
LaggedRankingA AS
(
SELECT
D.RowNum,
AscendentDenseRankingA = MAX(A.AscendentDenseRanking)
FROM
#Data AS D
INNER JOIN AscendentDenseRanking AS A ON D.RowNum > A.RowNum
WHERE
D.Category = 'B'
GROUP BY
D.RowNum
),
DescendantDenseRanking AS
(
SELECT
D.RowNum,
D.Category,
DescendantDenseRanking = DENSE_RANK() OVER (ORDER BY D.Rownum DESC)
FROM
#Data AS D
WHERE
D.Category = 'B'
),
LaggedRankingB AS
(
SELECT
D.RowNum,
AscendentDenseRankingB = MAX(A.DescendantDenseRanking)
FROM
#Data AS D
INNER JOIN DescendantDenseRanking AS A ON D.RowNum < A.RowNum
WHERE
D.Category = 'A'
GROUP BY
D.RowNum
)
SELECT
D.RowNum,
D.Category,
Rank4A = ISNULL(RA.AscendentDenseRanking, LA.AscendentDenseRankingA),
Rank4B = ISNULL(RB.DescendantDenseRanking, LB.AscendentDenseRankingB)
FROM
#Data AS D
LEFT JOIN AscendentDenseRanking AS RA ON D.RowNum = RA.RowNum
LEFT JOIN LaggedRankingA AS LA ON D.RowNum = LA.RowNum
LEFT JOIN DescendantDenseRanking AS RB ON D.RowNum = RB.RowNum
LEFT JOIN LaggedRankingB AS LB ON D.RowNum = LB.RowNum
/*
Results:
RowNum Category Rank4A Rank4B
----------- -------- -------------------- --------------------
1 A 1 3
2 A 2 3
3 B 2 3
5 A 3 2
6 B 3 2
9 B 3 1
*/
This isn't a recursive CTE, so the limit 32k doesn't apply.

How to join tables, concatenate some data

I have two tables:
1. indexes and quantity of indexes
2. indexes and quantity of indexes with specified boxcodes. Boxcode is a number of box, which box contains indexes.
1. input table 1
item_id quantity
1 10
2 15
3 5
1 5
1 5
2 5
3 5
sum:
1 - 20
2 - 20
3 - 10
2. input table 2
item_id quantity boxcode
1 3 abc
2 2 abc
1 8 def
3 10 ghi
1 9 ghi
2 9 def
2 8 ghi !!!!!!!
1 item_id once on 1 boxcode
I want to get result:
3. result
item_id quantity boxcodes
1 10 abc/3, def/7
2 15 abc/2, def/9, ghi/4
3 5 ghi/5
1 5 def/1, ghi/4
1 5 ghi/5
2 5 ghi/4 !!!!!!!!
3 5 ghi/5
Records from table 1 must be in the same order.
I have no idea how it can be done.
Any suggestion?
CREATE TABLE #input1
(
rownum int,
item_id int,
quantity int
)
CREATE TABLE #input2
(
item_id int,
quantity int,
boxcode varchar(10)
)
INSERT INTO #input1 VALUES (1,1,10)
INSERT INTO #input1 VALUES (2,2,15)
INSERT INTO #input1 VALUES (3,3,5)
INSERT INTO #input1 VALUES (4,1,5)
INSERT INTO #input1 VALUES (5,1,5)
INSERT INTO #input1 VALUES (6,2,5)
INSERT INTO #input1 VALUES (7,3,5)
INSERT INTO #input2 VALUES (1,3, 'abc')
INSERT INTO #input2 VALUES (2,2, 'abc')
INSERT INTO #input2 VALUES (1,8, 'def')
INSERT INTO #input2 VALUES (3,10, 'ghi')
INSERT INTO #input2 VALUES (1,9, 'ghi')
INSERT INTO #input2 VALUES (2,9, 'def')
INSERT INTO #input2 VALUES (2,8, 'ghi')
select * from #input1
select * from #input2
drop table #input1
drop table #input2
result
Thanks,
Weird, but it works:
;WITH rec1 AS (
SELECT rownum,
item_id,
1 as q,
1 as [Level],
quantity
from #input1
UNION ALL
SELECT r.rownum,
r.item_id,
1,
[Level] + 1,
i.quantity
FROM rec1 r
INNER JOIN #input1 i
ON r.rownum = i.rownum AND r.item_id = i.item_id
WHERE [Level] < i.quantity
), rec2 AS (
SELECT boxcode,
item_id,
1 as q,
1 as [Level],
quantity
from #input2
UNION ALL
SELECT r.boxcode,
r.item_id,
1,
[Level] + 1,
i.quantity
FROM rec2 r
INNER JOIN #input2 i
ON r.boxcode = i.boxcode AND r.item_id = i.item_id
WHERE [Level] < i.quantity
), cte1 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, rownum) as rn
FROM rec1
), cte2 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, boxcode) as rn
FROM rec2
), final AS (
SELECT c1.rownum,
c1.item_id,
c1.quantity,
c2.boxcode+'/'+CAST(SUM(c2.q) as nvarchar(10)) as boxcodes
FROM cte1 c1
INNER JOIN cte2 c2
ON c1.item_id = c2.item_id and c1.rn = c2.rn
GROUP BY c1.rownum, c1.item_id, c1.quantity, c2.boxcode
)
SELECT DISTINCT
f.rownum,
f.item_id,
f.quantity,
STUFF((
SELECT ', '+f1.boxcodes
FROM final f1
WHERE f1.rownum = f.rownum
AND f1.item_id = f.item_id
AND f1.quantity = f.quantity
FOR XML PATH('')
),1,2,'') boxcodes
FROM final f
Output for dataset you have provided:
rownum item_id quantity boxcodes
1 1 10 abc/3, def/7
2 2 15 abc/2, def/9, ghi/4
3 3 5 ghi/5
4 1 5 def/1, ghi/4
5 1 5 ghi/5
6 2 5 ghi/4
7 3 5 ghi/5
The main idea is to spread quantity in both tables for a small parts 1. Than add row number, then join and get result.
A solution (but it's totally based on gofr1's answer, to be honest !), to simplify a bit, would be to create a Numbers table, with as many numbers as you want.
CREATE TABLE Numbers(Number INT PRIMARY KEY);
INSERT Numbers
SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_columns;
That would just avoid the 2 recursive CTEs.
You could then use the same logic as gofr1 :
with rec1 AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, rownum) as rn,
rownum,
item_id,
case when quantity = 0 then 0 else 1 end as q,
quantity
from #input1
join Numbers n on n.Number <= quantity
)
, rec2 AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, boxcode) as rn,
boxcode,
item_id,
case when quantity = 0 then 0 else 1 end as q,
quantity
from #input2
join Numbers n on n.Number <= quantity
),
final AS (
SELECT c1.rownum,
c1.item_id,
c1.quantity,
c2.boxcode+'/'+CAST(SUM(c2.q) as nvarchar(10)) as boxcodes
FROM rec1 c1
INNER JOIN rec2 c2
ON c1.item_id = c2.item_id and c1.rn = c2.rn
GROUP BY c1.rownum, c1.item_id, c1.quantity, c2.boxcode
),
stuffed as (
SELECT
distinct rownum,
f.item_id,
f.quantity,
STUFF((
SELECT ', '+f1.boxcodes
FROM final f1
WHERE f1.rownum = f.rownum
AND f1.item_id = f.item_id
AND f1.quantity = f.quantity
FOR XML PATH('')
),1,2,'') boxcodes
FROM final f
group by item_id, quantity, boxcodes, rownum)
select *
from stuffed
order by rownum

Repeat Rows N Times According to Column Value

I have following table.
Table A:
ID ProductFK Quantity Price
------------------------------------------------
10 1 2 100
11 2 3 150
12 1 1 120
----------------------------------------------
I need select that repeat Rows N Time According to Quantity Column Value.
So I need following select result:
ID ProductFK Quantity Price
------------------------------------------------
10 1 1 100
10 1 1 100
11 2 1 150
11 2 1 150
11 2 1 150
12 1 1 120
You can use a simple JOIN to get the desired result as below:
SELECT t1.*, t2.number + 1 RepeatNumber
FROM TableA t1
JOIN master.dbo.spt_values t2 ON t2.type = 'P' AND t2.number < t1.Quantity
The above query repeats each record by the specified number in Quantity column.
Note for master.dbo.spt_values on type = 'P':
This table is used for getting a series of number which is hard-coded in it by condition of type = 'P'.
You could do that with a recursive CTE using UNION ALL:
;WITH cte AS
(
SELECT * FROM Table1
UNION ALL
SELECT cte.[ID], cte.ProductFK, (cte.[Order] - 1) [Order], cte.Price
FROM cte INNER JOIN Table1 t
ON cte.[ID] = t.[ID]
WHERE cte.[Order] > 1
)
SELECT [ID], ProductFK, 1 [Order], Price
FROM cte
ORDER BY 1
Here's a working SQLFiddle.
Here's a longer explanation of this technique.
Since your input is too large for this recursion, you could use an auxillary table to have "many" dummy rows and then use SELECT TOP([Order]) for each input row (CROSS APPLY):
;WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b)
SELECT t.[ID], t.ProductFK, 1 [Order], t.Price
FROM Table1 t CROSS APPLY (
SELECT TOP(t.[Order]) N
FROM E16) ca
ORDER BY 1
(The auxillary table is borrowed from here, it allows up to 65536 rows per input row and can be extended if required)
Here's a working SQLFiddle.
CREATE TAblE #temp
(
T_Name VARCHAR(50),
T_Times BIGINT
)
INSERT INTO #temp(T_Name,T_Times) VALUES ('ASHISH',4)
INSERT INTO #temp(T_Name,T_Times) VALUES ('PANKAJ',3)
INSERT INTO #temp(T_Name,T_Times) VALUES ('RUPESH',2)
INSERT INTO #temp(T_Name,T_Times) VALUES ('MANISH',5)
SELECT t.T_Name ,t.T_Times FROM
(SELECT T_Name,T_Times,CAST(('<val>'+REPLICATE(T_Name+'</val><val>',T_Times-1)
+'</val>') AS XML )AS X FROM #temp)t CROSS APPLY t.X.nodes('/val')y(z)
drop table #temp