Create sql query to inherits value from nearest parent row - sql-server

I have a self-referenced table called Units that has a "BossId" column refers to manager person .
There is business rule to determine the unit's Boss as described below:
1-The unit has its own BossId. (there is no more work)
2-The BossId is null. in this case we refer to the most nearest parent that has bossId value
i wanna create an efficient SQL view that all unit and their boss is specified according to the mentioned rules
below is the structure of my unit table:
CREATE TABLE [dbo].[Unit](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentId] [int] NULL,
[BossId] [int] NULL,
Here is the sample data:
INSERT INTO Units (ID, ParentID, BossId) VALUES (1, NULL, 1000)
INSERT INTO Units (ID, ParentID, BossId) VALUES (2, 1, NULL)
INSERT INTO Units (ID, ParentID, BossId) VALUES (3, 2, NULL)
INSERT INTO Units (ID, ParentID, BossId) VALUES (4, 1, 3000)
INSERT INTO Units (ID, ParentID, BossId) VALUES (5, 4, NULL)
Selecting the data as follows:
Select ID,ParentId,BossId from Units
result would be:
+----+-------+----------+
| ID | ParentId| BossId|
+----+-------+----------+
| 1 | NULL | 1000 |
| 2 | 1 | NULL |
| 3 | 2 | NULL |
| 4 | 1 | 3000 |
| 5 | 4 | NULL |
+----+-------+----------+
I need some view to produce something like this:
+----+-------+----------+
| ID | ParentId| BossId|
+----+-------+----------+
| 1 | NULL | 1000 |
| 2 | 1 | 1000 |
| 3 | 2 | 1000 |
| 4 | 1 | 3000 |
| 5 | 4 | 3000 |
+----+-------+----------+
So all unit's boss id is specified according to the rule

With _cte (ParentId, Id, BossId)
As
(
Select ParentId, Id, BossId
From Units
Union All
Select U.parentId, U.Id, c.BossId
From Units As U
Join _cte As c
On u.ParentId = c.Id
)
Select Id, ParentId, Max(BossId) As BossId
From _cte
Where BossId Is Not Null
Group
By Id, ParentId
Order
By Id, ParentId
Produces
Id ParentId BossId
----------- ----------- -----------
1 NULL 1000
2 1 1000
3 2 1000
4 1 3000
5 4 3000

Related

Separate comma values into individual values

I need to separate columns in SQL Server
Table: columnsseparates
CREATE TABLE [dbo].[columnsseparates](
[id] [varchar](50) NULL,
[name] [varchar](500) NULL
)
INSERT [dbo].[columnsseparates] ([id], [name]) VALUES (N'1,2,3,4', N'abc,xyz,mn')
GO
INSERT [dbo].[columnsseparates] ([id], [name]) VALUES (N'4,5,6', N'xy,yz')
GO
INSERT [dbo].[columnsseparates] ([id], [name]) VALUES (N'7,100', N'yy')
INSERT [dbo].[columnsseparates] ([id], [name]) VALUES (N'101', N'oo,yy')
GO
based on above data I want output like below:
id | Name
1 |abc
2 |xyz
3 |mn
4 |null
4 |xy
5 |yz
6 |null
7 |yy
100 |null
101 |oo
null |yy
How to achieve this task in SQL Server?
Storing non-atomic values in column is a sign that schema should be normalised.
Naive approach using PARSENAME(up to 4 comma separated values):
SELECT DISTINCT s.id, s.name
FROM [dbo].[columnsseparates]
CROSS APPLY(SELECT REVERSE(REPLACE(id,',','.')) id,REVERSE(REPLACE(name, ',','.')) name) sub
CROSS APPLY(VALUES (REVERSE(PARSENAME(sub.id,1)), REVERSE(PARSENAME(sub.name,1))),
(REVERSE(PARSENAME(sub.id,2)), REVERSE(PARSENAME(sub.name,2))),
(REVERSE(PARSENAME(sub.id,3)), REVERSE(PARSENAME(sub.name,3))),
(REVERSE(PARSENAME(sub.id,4)), REVERSE(PARSENAME(sub.name,4)))
) AS s(id, name)
ORDER BY s.id;
db<>fiddle demo
Output:
+------+------+
| id | name |
+------+------+
| | |
| | yy |
| 1 | abc |
| 100 | |
| 101 | oo |
| 2 | xyz |
| 3 | mn |
| 4 | |
| 4 | xy |
| 5 | yz |
| 6 | |
| 7 | yy |
+------+------+
If you have more than 4 values, then you'll to use a string splitter that can return the ordinal value. I use delimitedsplit8k_LEAD here:
WITH Ids AS(
SELECT cs.id,
cs.name,
DS.ItemNumber,
DS.Item
FROM dbo.columnsseparates cs
CROSS APPLY dbo.DelimitedSplit8K_LEAD (cs.id,',') DS),
Names AS (
SELECT cs.id,
cs.name,
DS.ItemNumber,
DS.Item
FROM dbo.columnsseparates cs
CROSS APPLY dbo.DelimitedSplit8K_LEAD (cs.[name],',') DS)
SELECT I.Item AS ID,
N.Item AS [Name]
FROM Ids I
FULL OUTER JOIN Names N ON I.id = N.id
AND I.ItemNumber = N.ItemNumber
ORDER BY CASE WHEN I.Item IS NULL THEN 1 ELSE 0 END,
TRY_CONVERT(int,I.Item);

TSQL Manager Hierarchy

I need to be able to show all my managers in a hierarchy in different columns. I don't know how many levels there will be.
Example: Employee – ManagerOfEmployee - TheBigBoss
I have tried the below but cant get it to work the way I want it.
I need the results to look like this:
Level1Column Level2Column Level3Column
------------------------------------------
1 2 3
Code:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid)
VALUES (1, 2), (2, 3)
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
1 AS level
FROM
#tblHRData
WHERE
Emplid = 1
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
level + 1
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT *
FROM CTE;
With an unknown depth you will have to go the dynamic SQL route. But such cases tend to have a maximal depth. As your columns will have computable names, you can try this:
I enhanced your table a bit:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT,
Descr VARCHAR(100)
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid, Descr)
VALUES (1, 2, 'lvl 3.2.1') --boss is 2
,(2, 3, 'lvl 3.2') --boss is 3
,(3,null, 'big boss')--big boss reports to no one
,(4, 3, 'lvl 3.4') --one more 2nd lvl
,(5, 4, 'lvl 3.4.5') --below 4
,(6, 4, 'lvl 3.4.6') --another one below 4
-- And I changed the recursive CTE to start off with the big boss and to build a sort string on the fly. In this case this is limited to 3 digits. You'll have to widen this with Emplids exceeding 999:
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
Descr,
0 AS EmpLvl,
CAST(REPLACE(STR(Emplid,3),' ','0') AS VARCHAR(MAX)) AS SortOrder
FROM
#tblHRData
WHERE
ReportsToEmplid IS NULL --start with the big boss
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
child.Descr,
parent.EmpLvl + 1,
parent.SortOrder + REPLACE(STR(child.Emplid,3),' ','0')
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT Emplid,
SortOrder,
MAX(CASE WHEN EmpLvl=0 THEN Descr END) AS BossDescr,
MAX(CASE WHEN EmpLvl=1 THEN Descr END) AS Lvl1Descr,
MAX(CASE WHEN EmpLvl=2 THEN Descr END) AS Lvl2Descr,
MAX(CASE WHEN EmpLvl=3 THEN Descr END) AS Lvl3Descr,
MAX(CASE WHEN EmpLvl=4 THEN Descr END) AS Lvl4Descr,
MAX(CASE WHEN EmpLvl=5 THEN Descr END) AS Lvl5Descr
--add as many as you need and add some more to be future safe
FROM CTE
GROUP BY EmpLvl,Emplid,SortOrder
ORDER BY SortOrder;
GO
DROP TABLE #tblHRData
The result
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Emplid | SortOrder | BossDescr | Lvl1Descr | Lvl2Descr | Lvl3Descr | Lvl4Descr | Lvl5Descr |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 3 | 003 | big boss | NULL | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 2 | 003002 | NULL | lvl 3.2 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 1 | 003002001 | NULL | NULL | lvl 3.2.1 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 4 | 003004 | NULL | lvl 3.4 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 5 | 003004005 | NULL | NULL | lvl 3.4.5 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 6 | 003004006 | NULL | NULL | lvl 3.4.6 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
Some remarks:
- I use the conditional aggregation as PIVOT approach. With just one column this can be done with PIVOT() too.
- The SortOrder is important to be created in the recursion. It is kind of the path to the entry and will allow you to order your result-set.
- This path must allow alphanumerical sorting. Therefore I concatenate padded strings.

Sum column for stock calculation

I'm trying to calculate stock in sql. I've 3 table product, purchase and sales.
product table is
+----+------------------+
| id | product_name |
+----+------------------+
| 1 | apple |
|----|------------------|
| 2 |banana |
|----|------------------|
| 3 |mango |
+----+------------------+
Now color table
+----+------------------+
| id | color_name |
+----+------------------+
| 1 | dark |
|----|------------------|
| 2 | light |
+----|------------------+
purchase table is
+-------+-------------+
| id | quantity |color
+-------+-------------+
| 1 | 15 |dark
+-------+-------------+
| 1 | 10 |light
+-------+-------------+
| 2 | 5 |dark
+-------+-------------+
| 3 | 25 |light
+-------+-------------+
and sales table is
+-------+-------------+
| id | quantity |color
+-------+-------------+
| 1 | 5 |dark
+-------+-------------+
| 1 | 5 |light
+-------+-------------+
| 2 | 5 |dark
+-------+-------------+
| 3 | 5 |light
+-------+-------------+
Purchase and sales table have foreign key id references id of product table. Now I'm trying to calculate stock available i.e. difference of purchase and sales in below format on the basis of color too
+----+------------------+-------------+
| id | product_name | quantity |color
+----+------------------+-------------+
| 1 | apple | 10 |dark
|----|------------------|-------------|
| 1 | apple | 5 |light
|----|------------------|-------------|
| 2 |banana | 0 |dark
|----|------------------|-------------|
| 3 |mango | 20 |light
+----+------------------+-------------+
You may looking for this
SELECT p.id,p.Name,Purchase.purchaseQty-sales.salseQty as totalQty
FROM Product p
OUTER APPLY(
SELECT purchase.id, SUM(purchase.quantity) purchaseQty
FROM purchase
where purchase.id= p.id
GROUP BY purchase.id
)Purchase
OUTER APPLY(
SELECT sales.id, SUM(sales.quantity) salseQty
FROM sales
where sales.id= p.id
GROUP BY sales.id
)sales
SELECT a.id, a.product_name,
SUM(b.quantity) - SUM(c.quantity) as 'quantity'
FROM product a
LEFT JOIN purchase b
ON a.id=b.id
LEFT JOIN sales c
ON b.id = c.id group by a.id, a.product_name
(I didn't test anything)
Try this solution with outer apply.
drop table if exists dbo.tProduct;
drop table if exists dbo.tPurchase;
drop table if exists dbo.tSale;
create table dbo.tProduct (
id int
, product_name varchar(100)
);
create table dbo.tPurchase (
id int
, quantity int
);
create table dbo.tSale (
id int
, quantity int
);
insert into dbo.tProduct (id, product_name)
values (1, 'apple'), (2, 'banana'), (3, 'mango');
insert into dbo.tPurchase (id, quantity)
values (1, 15), (2, 10), (1, 5), (3, 25);
insert into dbo.tSale (id, quantity)
values (1, 5), (3, 10), (1, 5), (3, 5), (2, 5);
select
p.id
, p.product_name
, pur.Quantity - sal.Quantity as Quantity
from dbo.tProduct p
outer apply (
select
sum(tp.quantity) as Quantity
from dbo.tPurchase tp
where p.id = tp.id
) pur
outer apply (
select
sum(tp.quantity) as Quantity
from dbo.tSale tp
where p.id = tp.id
) sal

Update postgres table to squash duplicate values in second table

I have a postgresql schema with two tables:
tableA: tableB:
| id | username | | fk_id | resource |
| 1 | user1 | | 2 | item1 |
| 2 | user1 | | 1 | item3 |
| 3 | user1 | | 1 | item2 |
| 4 | user2 | | 4 | item5 |
| 5 | user2 | | 5 | item8 |
| 6 | user3 | | 3 | item9 |
The foreign key fk_id in tableB references id in tableA.
How can I update all of the foreign key id's of tableB to point to the lowest entry for a unique username in tableA?
update table_b b
set fk_id = d.id
from table_a a
join (
select distinct on (username) username, id
from table_a
order by 1, 2
) d using(username)
where a.id = b.fk_id;
Test it here.
The query used inside the update gives actual_id, username, desired_id:
select a.id actual_id, username, d.id desired_id
from table_a a
join (
select distinct on (username) username, id
from table_a
order by 1, 2
) d using(username)
actual_id | username | desired_id
-----------+----------+------------
1 | user1 | 1
2 | user1 | 1
3 | user1 | 1
4 | user2 | 4
5 | user2 | 4
6 | user3 | 6
(6 rows)
We define your tables:
CREATE TABLE tableA (id, username) AS
SELECT * FROM
(
VALUES
(1, 'user1'),
(2, 'user1'),
(3, 'user1'),
(4, 'user2'),
(5, 'user2'),
(6, 'user2')
) AS x ;
CREATE TABLE tableB (fk_id, resource) AS
SELECT * FROM
(
VALUES
(2, 'item1'),
(1, 'item3'),
(1, 'item2'),
(4, 'item5'),
(5, 'item8'),
(3, 'item9')
) AS x ;
With that info, you can create a (virtual) conversion table, and use it to update your data:
-- Using tableA, make a new table with the
-- minimum id for every username
WITH username_to_min_id AS
(
SELECT
min(id) AS min_id, username
FROM
tableA
GROUP BY
username
)
-- Convert the previous table to a id -> min_id
-- conversion table
, id_to_min_id AS
(
SELECT
id, min_id
FROM
tableA
JOIN username_to_min_id USING(username)
)
-- Use this conversion table to update tableB
UPDATE
tableB
SET
fk_id = min_id
FROM
id_to_min_id
WHERE
-- JOIN condition with table to update
id_to_min_id.id = tableB.fk_id
-- Take out the ones that won't change
AND (fk_id <> min_id)
RETURNING
* ;
The result you would get is:
+-------+----------+----+--------+
| fk_id | resource | id | min_id |
+-------+----------+----+--------+
| 1 | item1 | 2 | 1 |
| 1 | item9 | 3 | 1 |
| 4 | item8 | 5 | 4 |
+-------+----------+----+--------+
Shows you that three rows have been updated, that had fk_id = (2, 3, 5), and have now (1, 1, 4). (The id is the "old" fk_id value).
You can check it at http://rextester.com/EQPH47434
You can "squeeze everything" [change every virtual table name by its definition, and do a couple of SELECT optimizations] and get this equivalent query (probably less clear, yet totally equivalent):
UPDATE
tableB
SET
fk_id = min_id
FROM
tableA
JOIN
(
SELECT
min(id) AS min_id, username
FROM
tableA
GROUP BY
username
) AS username_to_min_id
USING (username)
WHERE
tableA.id = tableB.fk_id
AND (fk_id <> min_id)
RETURNING
* ;

skip records based on columns condition

I have a question in sql server
table name : Emp
Id |Pid |Firstname| LastName | Level
1 |101 | Ram |Kumar | 3
1 |100 | Ravi |Kumar | 2
2 |101 | Jaid |Balu | 10
1 |100 | Hari | Babu | 5
1 |103 | nani | Jai |44
1 |103 | Nani | Balu |10
3 |103 |bani |lalu |20
Here need to retrieve unique records based on id and Pid columns and records which have duplicate records need to skip.
Finally I want output like below
Id |Pid |Firstname| LastName | Level
1 |101 | Ram |Kumar | 3
2 |101 | Jaid |Balu | 10
3 |103 |bani |lalu |20
I found duplicate records based on below query
select id,pid,count(*) from emp group by id,pid having count(*) >=2
this query get duplicated records 2 that records need to skip to retrieve output
please tell me how to write query to achieve this task in sql server.
Since your output is based on unique ID and PID which do not have any duplicate value, You can use COUNT with partition to achieve your desired result.
SQL Fiddle
Sample Data
CREATE TABLE Emp
([Id] int, [Pid] int, [Firstname] varchar(4), [LastName] varchar(5), [Level] int);
INSERT INTO Emp
([Id], [Pid], [Firstname], [LastName], [Level])
VALUES
(1, 101, 'Ram', 'Kumar', 3),
(1, 100, 'Ravi', 'Kumar', 2),
(2, 101, 'Jaid', 'Balu', 10),
(1, 100, 'Hari', 'Babu', 5),
(1, 103, 'nani', 'Jai', 44),
(1, 103, 'Nani', 'Balu', 10),
(3, 103, 'bani', 'lalu', 20);
Query
SELECT *
FROM
(
SELECT *,rn = COUNT(*) OVER(PARTITION BY ID,PID)
FROM Emp
) Emp
WHERE rn = 1
Output
| Id | Pid | Firstname | LastName | Level |
|----|-----|-----------|----------|-------|
| 1 | 101 | Ram | Kumar | 3 |
| 2 | 101 | Jaid | Balu | 10 |
| 3 | 103 | bani | lalu | 20 |

Resources