Combine two tables without repeat second table value - sql-server

I have two tables that need to be joined.
Example:
Table 1: tbl_Item
Id ---- ItemName
1 ----- A<br/>
1 ----- B<br/>
1 ----- c<br/>
2 ----- A<br/>
2 ----- B<br/>
Table 2: tbl_Detail
Id ---- Total
1 ---- 100 <br/>
2 ---- 300<br/>
I need to join the tables and get the following result:
Id -- ItemName -- Total
1 -- A --- Null<br/>
1 -- B --- Null<br/>
1 -- C --- 100<br/>
2 -- A --- Null<br/>
2 -- B --- 300<br/>
Thanks in advance.

You can assign the total value to an indeterminate single row by using row_number():
select t.id, t.ItemName,
(case when row_number() over (partition by t.id order by (select NULL)) = 1
then d.total
end) as total
from tbl_item t join
tbl_detail d
on t.id = d.id;
If you have an ordering (probably specified by another column), then replace (select null) with the appropriate logic. For the example data, for instance, you might use i.name desc, but I doubt that is the actual ordering you are looking for.

You could use ROW_NUMBER for this:
;WITH CTE AS (
SELECT Id, ItemName,
ROW_NUMBER() OVER (PARTITION BY Id ORDER BY ItemName DESC) AS rn
FROM tbl_Item
)
SELECT t1.Id, t1.ItemName,
CASE WHEN t1.rn = 1 THEN t2.Total END AS Total
FROM CTE AS t1
LEFT JOIN tbl_Detail AS t2 ON t1.Id = t2.Id

Related

In SQL, best way to loop children to get sales at each level?

I wish to have all sales for an items, at each level of the production... and I'm not sure how to do this efficiency.
I have a table containing sales per SKU (pre-calulated each night).
It's like this :
SKU -- SALES
--------------
SKU1 -- 123.34
SKU2 -- 452.23
SKU3 -- 183.12
...
And in the system, I have all the "PARENT" needed to make the SKU (if any)
Like this :
SKU -- PARENT
---------------
SKU1 -- NULL
SKU2 -- SKU3
SKU3 -- SKU1
SKU4 -- NULL
SKU5 -- SKU1
...
As you can see, SKU1 is "root" and it's needed to make SKU3 and SKU5. To make SKU3, you will need SKU2 (So to make SKU2, you will need SKU1 => SKU3 => SKU2).
I first made a view to load all the SKU and then all the SKU -- PARENT.
SELECT LTRIM(RTRIM(ITEMNO)) AS ITEMNO, '' AS PARENT1
FROM dbo.ICITEM T1
WHERE NOT EXISTS (SELECT *
FROM dbo.ICITEMO T2
WHERE T2.OPTFIELD = 'PARENT1' AND T1.ITEMNO = T2.ITEMNO)
UNION ALL
SELECT LTRIM(RTRIM(ITEMNO)) AS ITEMNO, LTRIM(RTRIM(REPLACE(VALUE, '-', ''))) AS PARENT1
FROM dbo.ICITEMO T2
WHERE T2.OPTFIELD = 'PARENT1'
Then, made another view to get the children of each items (if any).
SELECT LTRIM(RTRIM(ITEMNO)) AS ITEMNO, '' AS ENFANT1
FROM dbo.ICITEM T1
WHERE NOT EXISTS (SELECT *
FROM dbo.VIEW_ICITEM_PARENT1 T2
INNER JOIN dbo.VIEW_ICITEM_PARENT1 AS VIEW_ICITEM_PARENT1_1 ON T2.ITEMNO = VIEW_ICITEM_PARENT1_1.PARENT1
WHERE T1.ITEMNO = T2.ITEMNO)
UNION ALL
SELECT dbo.VIEW_ICITEM_PARENT1.ITEMNO, VIEW_ICITEM_PARENT1_1.ITEMNO AS ENFANT1
FROM dbo.VIEW_ICITEM_PARENT1
INNER JOIN dbo.VIEW_ICITEM_PARENT1 AS VIEW_ICITEM_PARENT1_1 ON dbo.VIEW_ICITEM_PARENT1.ITEMNO = VIEW_ICITEM_PARENT1_1.PARENT1
Then, I tried to run a code (took somewhere here) to run all the loop and get each level... if it was okay, I would add the sales calculations... but it took 2 hours to run and had some weird stuff in it.
with descendants as
( select ITEMNO, ENFANT1 as descendant, 1 as level
from VIEW_ICITEM_ENFANT1
union all
select d.ITEMNO, s.ENFANT1, d.level + 1
from descendants as d
join VIEW_ICITEM_ENFANT1 s
on d.descendant = s.ITEMNO
)
select *
from descendants
--order by ITEMNO, level, descendant
option (maxrecursion 5)
To explain a little more :
If I have this :
ROOT -- CHILD
A -- B
A -- C
B -- D
B -- E
C -- NULL
I'm expecting this :
SKU -- SALES_DIRECT -- SALES_TOTAL
A -- A -- A+B+C+D+E
B -- B -- B+D+E
C -- C -- C
Note: Could also be a stored procedure if it's easier.
Here's my final request, running in 2 seconds with 33482 lines.
;with C as
(
select T.ID,
T.SKU,
(SELECT QTYSHIPPED FROM dbo.PRDATA_OEINV_TOTALQTYSHIPPED WHERE T.SKU = ITEMNO) as QtyShipped,
T.SKU as RootID
from ICITEM_ENFANT1 T
union all
select T.ID,
T.SKU,
(SELECT QTYSHIPPED FROM dbo.PRDATA_OEINV_TOTALQTYSHIPPED WHERE T.SKU = ITEMNO) as QtyShipped,
C.RootID
from ICITEM_ENFANT1 T
inner join C
on T.SKU_ENFANT1 = C.SKU
)
select T.ID,
T.SKU,
T.SKU_ENFANT1,
(SELECT QTYSHIPPED FROM dbo.PRDATA_OEINV_TOTALQTYSHIPPED WHERE T.SKU = ITEMNO) as QtyShipped,
S.AmountIncludingChildren
from ICITEM_ENFANT1 T
inner join (
select RootID,
sum(QtyShipped) as AmountIncludingChildren
from C
group by RootID
) as S
on T.SKU = S.RootID
order by T.SKU,T.SKU_ENFANT1, QtyShipped
option (maxrecursion 0)
Thanks for the help!

How to find the cumulative sum in SubQuery? [duplicate]

declare #t table
(
id int,
SomeNumt int
)
insert into #t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23
select * from #t
the above select returns me the following.
id SomeNumt
1 10
2 12
3 3
4 15
5 23
How do I get the following:
id srome CumSrome
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from #t t1
inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id
SQL Fiddle example
Output
| ID | SOMENUMT | SUM |
-----------------------
| 1 | 10 | 10 |
| 2 | 12 | 22 |
| 3 | 3 | 25 |
| 4 | 15 | 40 |
| 5 | 23 | 63 |
Edit: this is a generalized solution that will work across most db platforms. When there is a better solution available for your specific platform (e.g., gareth's), use it!
The latest version of SQL Server (2012) permits the following.
SELECT
RowID,
Col1,
SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
or
SELECT
GroupID,
RowID,
Col1,
SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
This is even faster. Partitioned version completes in 34 seconds over 5 million rows for me.
Thanks to Peso, who commented on the SQL Team thread referred to in another answer.
For SQL Server 2012 onwards it could be easy:
SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM #t
because ORDER BY clause for SUM by default means RANGE UNBOUNDED PRECEDING AND CURRENT ROW for window frame ("General Remarks" at https://msdn.microsoft.com/en-us/library/ms189461.aspx)
Let's first create a table with dummy data:
Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)
Now let's insert some data into the table;
Insert Into CUMULATIVESUM
Select 1, 10 union
Select 2, 2 union
Select 3, 6 union
Select 4, 10
Here I am joining same table (self joining)
Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc
Result:
ID SomeValue SomeValue
-------------------------
1 10 10
2 2 10
2 2 2
3 6 10
3 6 2
3 6 6
4 10 10
4 10 2
4 10 6
4 10 10
Here we go now just sum the Somevalue of t2 and we`ll get the answer:
Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc
For SQL Server 2012 and above (much better performance):
Select
c1.ID, c1.SomeValue,
Sum (SomeValue) Over (Order By c1.ID )
From CumulativeSum c1
Order By c1.id Asc
Desired result:
ID SomeValue CumlativeSumValue
---------------------------------
1 10 10
2 2 12
3 6 18
4 10 28
Drop Table CumulativeSum
A CTE version, just for fun:
;
WITH abcd
AS ( SELECT id
,SomeNumt
,SomeNumt AS MySum
FROM #t
WHERE id = 1
UNION ALL
SELECT t.id
,t.SomeNumt
,t.SomeNumt + a.MySum AS MySum
FROM #t AS t
JOIN abcd AS a ON a.id = t.id - 1
)
SELECT * FROM abcd
OPTION ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.
Returns:
id SomeNumt MySum
----------- ----------- -----------
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
Late answer but showing one more possibility...
Cumulative Sum generation can be more optimized with the CROSS APPLY logic.
Works better than the INNER JOIN & OVER Clause when analyzed the actual query plan ...
/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP
SELECT * INTO #TMP
FROM (
SELECT 1 AS id
UNION
SELECT 2 AS id
UNION
SELECT 3 AS id
UNION
SELECT 4 AS id
UNION
SELECT 5 AS id
) Tab
/* Using CROSS APPLY
Query cost relative to the batch 17%
*/
SELECT T1.id,
T2.CumSum
FROM #TMP T1
CROSS APPLY (
SELECT SUM(T2.id) AS CumSum
FROM #TMP T2
WHERE T1.id >= T2.id
) T2
/* Using INNER JOIN
Query cost relative to the batch 46%
*/
SELECT T1.id,
SUM(T2.id) CumSum
FROM #TMP T1
INNER JOIN #TMP T2
ON T1.id > = T2.id
GROUP BY T1.id
/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT T1.id,
SUM(T1.id) OVER( PARTITION BY id)
FROM #TMP T1
Output:-
id CumSum
------- -------
1 1
2 3
3 6
4 10
5 15
Select
*,
(Select Sum(SOMENUMT)
From #t S
Where S.id <= M.id)
From #t M
You can use this simple query for progressive calculation :
select
id
,SomeNumt
,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from #t
There is a much faster CTE implementation available in this excellent post:
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx
The problem in this thread can be expressed like this:
DECLARE #RT INT
SELECT #RT = 0
;
WITH abcd
AS ( SELECT TOP 100 percent
id
,SomeNumt
,MySum
order by id
)
update abcd
set #RT = MySum = #RT + SomeNumt
output inserted.*
For Ex: IF you have a table with two columns one is ID and second is number and wants to find out the cumulative sum.
SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Once the table is created -
select
A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
from #t A, #t B where A.id >= B.id
group by A.id, A.SomeNumt
order by A.id
The SQL solution wich combines "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" and "SUM" did exactly what i wanted to achieve.
Thank you so much!
If it can help anyone, here was my case. I wanted to cumulate +1 in a column whenever a maker is found as "Some Maker" (example). If not, no increment but show previous increment result.
So this piece of SQL:
SUM( CASE [rmaker] WHEN 'Some Maker' THEN 1 ELSE 0 END)
OVER
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT
Allowed me to get something like this:
User 1 Rank1 MakerA 0
User 1 Rank2 MakerB 0
User 1 Rank3 Some Maker 1
User 1 Rank4 Some Maker 2
User 1 Rank5 MakerC 2
User 1 Rank6 Some Maker 3
User 2 Rank1 MakerA 0
User 2 Rank2 SomeMaker 1
Explanation of above: It starts the count of "some maker" with 0, Some Maker is found and we do +1. For User 1, MakerC is found so we dont do +1 but instead vertical count of Some Maker is stuck to 2 until next row.
Partitioning is by User so when we change user, cumulative count is back to zero.
I am at work, I dont want any merit on this answer, just say thank you and show my example in case someone is in the same situation. I was trying to combine SUM and PARTITION but the amazing syntax "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" completed the task.
Thanks!
Groaker
Above (Pre-SQL12) we see examples like this:-
SELECT
T1.id, SUM(T2.id) AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
T1.id
More efficient...
SELECT
T1.id, SUM(T2.id) + T1.id AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
T1.id
Try this
select
t.id,
t.SomeNumt,
sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from
#t t
group by
t.id,
t.SomeNumt
order by
t.id asc;
Try this:
CREATE TABLE #t(
[name] varchar NULL,
[val] [int] NULL,
[ID] [int] NULL
) ON [PRIMARY]
insert into #t (id,name,val) values
(1,'A',10), (2,'B',20), (3,'C',30)
select t1.id, t1.val, SUM(t2.val) as cumSum
from #t t1 inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.val order by t1.id
Without using any type of JOIN cumulative salary for a person fetch by using follow query:
SELECT * , (
SELECT SUM( salary )
FROM `abc` AS table1
WHERE table1.ID <= `abc`.ID
AND table1.name = `abc`.Name
) AS cum
FROM `abc`
ORDER BY Name

How to replace some rows in a SELECT query from another SELECT

I have two tables:
T1:
ID Department ATTRIBUTES TEAM
--- ---------- ---------- ------
1 R&D Dress_Code NULL
2 R&D Dress_Code Web
3 R&D Food System
4 R&D Food NULL
5 R&D Color NULL
6 Marketing Food System
T2:
ID VAL
--- ----------
1 Smart
2 Casual
3 Beef
4 Chicken
5 Green
6 Fish
The purpose of T1 is to show all the department attributes.
If the TEAM is null, it is for everyone in that department. Sometimes a team has special settings which override the generic settings.
For example, I want to get the settings as a 'Web' team in R&D.
I can write:
SELECT T1.DEPARTMENT, T1.ATTRIBUTES, T1.TEAM, T2.VAL
FROM T1
LEFT JOIN T2 ON T1.ID = T2.ID
WHERE T1.DEPARTMENT = 'R&D' AND T1.TEAM = 'Web'
This will show one record which says dress code is casual.
But I want the result to be:
ATTRIBUTES VAL
---------- ------
Dress_Code Casual
Food Chicken
Color Green
Similarly for the 'System' team in R&D, the result would be smart dress code, beef, and green color.
I'm thinking first select all R&D results and then replace the rows with the above select results.
I need to write this as a stored procedure.
Any help is much appreciated!
Using CTE and row_number() :
with CTE as(
select
ATTRIBUTES,
VAL,
T1.TEAM,
row_number() over (partition by ATTRIBUTES order by team desc) rn
from t1 t1
inner join t2 t2 on t1.ID =t2.ID
AND ( T1.TEAM = 'Web' or T1.TEAM is null )
)
select
ATTRIBUTES ,
VAL
from cte where rn=1
order by val
OutPut :
SELECT
T1.DEPARTMENT,
T1.ATTRIBUTES,
T1.TEAM,
T2.VAL
INTO #temp
FROM T1
INNER JOIN T2 ON T1.ID = T2.ID
WHERE T1.TEAM is NULL
SELECT
T1.ATTRIBUTES,
T2.VAL
INTO #t2
FROM T1
INNER JOIN T2 ON T1.ID = T2.ID
WHERE T1.TEAM = 'Web'
UPDATE t
SET t.VAL=b.VAL
FROM #temp t
join #t2 b on b.ATTRIBUTES=t.ATTRIBUTES
SELECT
DEPARTMENT,
ATTRIBUTES,
TEAM,
VAL
FROM #temp
Even this too will help.

SQL Server - Select most recent records with condition

I have a table like this.
Table :
ID EnrollDate ExitDate
1 4/1/16 8/30/16
2 1/1/16 null
2 1/1/16 7/3/16
3 2/1/16 8/1/16
3 2/1/16 9/1/16
4 1/1/16 12/12/16
4 1/1/16 12/12/16
4 1/1/16 12/12/16
4 1/1/16 null
5 5/1/16 11/12/16
5 5/1/16 11/12/16
5 5/1/16 11/12/16
Need to select the most recent records with these conditions.
One and only one record has the most recent enroll date - select that
Two or more share same most recent enroll date and one and only one record has either a NULL Exit Date or the most recent Exit Date - Select the record with null. If no null record pick the record with recent exit date
Two or more with same enroll and Exit Date - If this case exists, don't select those record
So the expected result for the above table should be :
ID EnrollDate ExitDate
1 4/1/16 8/30/16
2 1/1/16 null
3 2/1/16 9/1/16
4 1/1/16 null
I wrote the query with group by. I am not sure how to select with the conditions 2 and 3.
select t1.* from table t1
INNER JOIN(SELECT Id,MAX(EnrollDate) maxentrydate
FROM table
GROUP BY Id)t2 ON EnrollDate = t2.maxentrydate and t1.Id=t2.Id
Please let me know what is the best way to do this.
Using the rank() window function, I think it's possible.
This is untested, but it should work:
select t.ID, t.EnrollDate, t.ExitDate
from (select t.*,
rank() over(
partition by ID
order by EnrollDate desc,
case when ExitDate is null then 1 else 2 end,
ExitDate desc) as rnk
from tbl t) t
where t.rnk = 1
group by t.ID, t.EnrollDate, t.ExitDate
having count(*) = 1
The basic idea is that the rank() window function will rank the most "recent" rows with a value of 1, which we filter on in the outer query's where clause.
If more than one row have the same "most recent" data, they will all share the same rank of 1, but will get filtered out by the having count(*) = 1 clause.
Use ROW_NUMBER coupled with CASE expression to achieve the desired result:
WITH Cte AS(
SELECT t.*,
ROW_NUMBER() OVER(
PARTITION BY t.ID
ORDER BY
t.EnrollDate DESC,
CASE WHEN t.ExitDate IS NULL THEN 0 ELSE 1 END,
t.ExitDate DESC
) AS rn
FROM Tbl t
INNER JOIN (
SELECT
ID,
COUNT(DISTINCT CHECKSUM(EnrollDate, ExitDate)) AS DistinctCnt, -- Count distinct combination of EnrollDate and ExitDate per ID
COUNT(*) AS RowCnt -- Count number of rows per ID
FROM Tbl
GROUP BY ID
) a
ON t.ID = a.ID
WHERE
(a.DistinctCnt = 1 AND a.RowCnt = 1)
OR a.DistinctCnt > 1
)
SELECT
ID, EnrollDate, ExitDate
FROM Cte c
WHERE Rn = 1
The ORDER BY clause in the ROW_NUMBER takes care of conditions 2 and 3.
The INNER JOIN and the WHERE clause take care of 1 and 4.
ONLINE DEMO
with B as (
select id, enrolldate ,
exitdate,
row_number() over (partition by id order by enrolldate desc, case when exitdate is null then 0 else 1 end, exitdate desc) rn
from ab )
select b1.id, b1.enrolldate, b1.exitdate from b b1
left join b b2
on b1.rn = b2.rn -1 and
b1.id = b2.id and
b1.exitdate = b2.exitdate and
b1.enrolldate = b2.enrolldate
where b1.rn = 1 and
b2.id is nULL
The left join is used to fullfill the 3) requirement. When record is returned then we don't want it.

Limited T-SQL Join

This should be simple enough, but somehow my brain stopped working.
I have two related tables:
Table 1:
ID (PK), Value1
Table 2:
BatchID, Table1ID (FK to Table 1 ID), Value2
Example data:
Table 1:
ID Value1
1 A
2 B
Table 2:
BatchID Table1ID Value2
1 1 100
2 1 101
3 1 102
1 2 200
2 2 201
Now, for each record in Table 1, I'd like to do a matching record on Table 2, but only the most recent one (batch ID is sequential). Result for the above example would be:
Table1.ID Table1.Value1 Table2.Value2
1 A 102
2 B 201
The problem is simple, how to limit join result with Table2. There were similar questions on SO, but can't find anything like mine. Here's one on MySQL that looks similar:
LIMITing an SQL JOIN
I'm open to any approach, although speed is still the main priority since it will be a big dataset.
WITH Latest AS (
SELECT Table1ID
,MAX(BatchID) AS BatchID
FROM Table2
GROUP BY Table1ID
)
SELECT *
FROM Table1
INNER JOIN Latest
ON Latest.Table1ID = Table1.ID
INNER JOIN Table2
ON Table2.BatchID = Latest.BatchID
SELECT id, value1, value2
FROM (
SELECT t1.id, t2.value1, t2.value2, ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY t2.BatchID DESC) AS rn
FROM table1 t1
JOIN table2 t2
ON t2.table1id = t1.id
) q
WHERE rn = 1
Try
select t1.*,t2.Value2
from(
select Table1ID,max(Value2) as Value2
from [Table 2]
group by Table1ID) t2
join [Table 1] t1 on t2.Table1ID = t1.id
Either GROUP BY or WHERE clause that filters on the most recent:
SELECT * FROM Table1 a
INNER JOIN Table2 b ON (a.id = b.Table1ID)
WHERE NOT EXISTS(
SELECT 1 FROM Table2 c WHERE c.Table1ID = a.id AND c.BatchID > b. BatchID
)

Resources