Using PIVOT with 2 related tables - sql

I am working in SQL Server (SSMS) with two tables like:
prop_ppl:
id_numb id_pers cdate val
1 4 NULL NULL
2 2 2018-12-12 250
3 1 2018-12-01 250
4 3 2018-12-11 500
5 6 2018-01-01 500
6 5 2018-12-12 480
ppl:
id_perc name
1 John
2 Derek
3 Mia
4 Chris
5 Ann
6 Dave
Then i need to get the table like this:
name
id_numb value
for these tables it should be, when its nececcary to find all values for ppl with date 2018/12/12:
Derek Ann
2 250 0
6 0 NULL
Code:
CREATE TABLE ppl(
id_perc smallint PRIMARY KEY,
name varchar(50) NOT NULL
)
CREATE TABLE prop_ppl(
id_numb int IDENTITY(1,1) PRIMARY KEY,
id_perc smallint NOT NULL,
cdate smalldatetime,
val int
)
INSERT INTO dbo.ppl (id_perc, name)
VALUES (1, 'John'), (2, 'Derek'), (3, 'Mia'), (4, 'Chris'), (5, 'Ann'),
(6, 'Dave')
INSERT INTO dbo.prop_ppl (id_perc, cdate, val)
VALUES (4, NULL,NULL), (2,'20181212', 250), (1, '20181201', 250),
(3, '20181211',500), (6,'20180101', 500), (5, '20181212', 480)
Then i try to use:
SELECT *
FROM (
SELECT name,id_numb,val
FROM prop_ppl
JOIN ppl ON prop_ppl.id_perc = ppl.id_perc
WHERE cdate='20181212'
)
PIVOT(
SUM(val)
FOR [name] in ('Derek','Ann')
)
but an error appears
"Incorrect syntax near the keyword "PIVOT"."
maybe it's possible to do without PIVOT..
Also, I would like to understand how this script could be applied to arbitrary parameters (and not just to Derek & Ann).

It's easy to handle with CTE :
With CTE as (
SELECT
name,
id_numb,
val
FROM prop_ppl
JOIN ppl ON prop_ppl.id_perc = ppl.id_perc
WHERE cdate='20181212')
Select
*
from CTE
PIVOT(SUM(val) FOR [name] in ([Derek],[Ann])) as Col

Related

Using recursive CTE to generate hierarchy results ordered by depth without the use of heiarchyid

I would like to query hierarchy results ordered by depth first without the use of SQL's heiarchyid built in function. Essentially, I am hoping to accomplish the depth ordering without any fancy functions.
I have provided a temp table below that contains these records:
Id
p_Id
order1
name1
1
null
1
josh
2
null
2
mary
3
null
3
george
4
1
1
joe
5
1
2
jeff
6
2
1
marg
7
2
2
moore
8
2
3
max
9
3
1
gal
10
3
2
guy
11
4
1
tod
12
4
2
ava
13
9
1
ron
14
9
2
bill
15
9
100
pat
where p_Id is the id of the parent record, and order1 is essentially just the ordering of which the depth first output should be displayed. To show why my query does not fully work, I made the order1 of the last record 100 instead of say, 3. However this should not ultimately matter since 100 and 3 both come after the previous order1 value, 2.
An example of a correct result table is shown below:
Id
p_Id
order1
name1
Descendants
1
null
1
josh
josh
4
1
1
joe
josh/joe
11
4
1
tod
josh/joe/tod
12
4
2
ava
josh/joe/ava
5
1
2
jeff
josh/jeff
2
null
2
mary
mary
6
2
1
marg
mary/marg
7
2
2
moore
mary/moore
8
2
3
max
mary/max
3
null
3
george
george
9
3
1
gal
george/gal
13
9
1
ron
george/gal/ron
15
9
2
bill
george/gal/bill
14
9
100
pat
george/gal/pat
10
3
2
guy
george/guy
Where an example of my results are shown below:
Id
p_Id
order1
name1
Descendants
levels
1
null
1
josh
josh
.1
4
1
1
joe
josh/joe
.1.1
11
4
1
tod
josh/joe/tod
.1.1.1
12
4
2
ava
josh/joe/ava
.1.1.2
5
1
2
jeff
josh/jeff
.1.2
2
null
2
mary
mary
.2
6
2
1
marg
mary/marg
.2.1
7
2
2
moore
mary/moore
.2.2
8
2
3
max
mary/max
.2.3
3
null
3
george
george
.3
9
3
1
gal
george/gal
.3.1
13
9
1
ron
george/gal/ron
.3.1.1
15
9
100
pat
george/gal/pat
.3.1.100
14
9
2
bill
george/gal/bill
.3.1.2
10
3
2
guy
george/guy
.3.2
where I have created a levels column that essentially concatenates the order1 values and separates them with a period. This almost returns the correct results, but due to the fact that I am ordering by this string (of numbers and periods), the levels value of .3.1.100 will come before .3.1.2 , which is not what the desired output should look like. I am sure there is a different method to return the correct depth order. See below for the code that generates a temp table, and the code that I used to generate the incorrect output that I have so far.
if object_id('tempdb..#t1') is not null drop table #t1
CREATE TABLE #t1 (Id int, p_Id int, order1 int, name1 varchar(150))
INSERT into #t1 VALUES
(1, null, 1, 'josh'),
(2, null, 2, 'mary'),
(3, null, 3, 'george'),
(4, 1, 1, 'joe'),
(5, 1, 2, 'jeff'),
(6, 2, 1, 'marg'),
(7, 2, 2, 'moore'),
(8, 2, 3, 'max'),
(9, 3, 1, 'gal'),
(10, 3, 2, 'guy'),
(11, 4, 1, 'tod'),
(12, 4, 2, 'ava'),
(13, 9, 1, 'ron'),
(14, 9, 2, 'bill'),
(100, 9, 100, 'pat');
select * from #t1
-- Looking to generate heiarchy results ordered by depth --
; with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc
Take II. As pointed out by OP my original answer fails for more than 10 children. So what we can do (OP's suggestion) is pad the values out with zeros to a constant length. But what length? We need to take the largest number of children under a node and add this to the largest value or order, so for the example provided this is 100 + 3, and then take the length of that (3) and pad every order with zeros to 3 digits long. This means we will always be ordering as desired.
declare #PadLength int = 0;
select #PadLength = max(children)
from (
select len(convert(varchar(12),max(order1)+count(*))) children
from #t1
group by p_Id
) x;
-- Looking to generate heiarchy results ordered by depth --
with structure as (
-- Non-recursive term
-- Select the records where p_Id is null
select
p.Id [Id]
, p.p_Id [ParentId]
, p.order1 [OrderBy]
, p.name1 [Name]
, cast(p.name1 as varchar(64)) Descendants
, concat('.', right(replicate('0',#Padlength) + convert(varchar(12),p.order1), #PadLength)) Levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select
c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)),
concat(p.levels, '.', right(replicate('0',#Padlength) + convert(varchar(12),c.order1), #PadLength))
from #t1 c -- c being the 'child' records
inner join structure p on c.p_Id = p.Id -- p being the 'parent' records
)
select *
from structure
order by replace(levels, '.', '') asc;
Note: This answer fails in the case when there are more than 10 children under a particular node. Leaving for interest.
So this issue you have run into is that you are ordering by a string not a number. So the string 100 comes before the string 2. But you need to order by a string to take care of the hierarchy, so one solution is to replace order1 with row_number() based on the order1 column while its still a number and use the row_number() to build your ordering string.
So you replace:
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
with
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
giving a full query of
with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc;
Which returns the desired results.
Note: good question, well written.

Duplicating Results when using Subquery, need to filter out Nulls and only show value if value exists

(Edited) I am having a problem with duplicating results when using a Count() Function and then Grouping by a column that changes from Null to having a value. See Below:
Here is a sample table of data:
t_order_table
Line# Order User_Assigned
1 12345 Null
2 12345 Null
3 12345 Null
4 12345 Null
5 12345 Null
6 12345 Null
7 12345 Null
8 12345 Null
9 12345 Null
1 11223 Null
2 11223 Null
3 11223 Null
Here is the script to report on the table(below):
Select Order,
(Select Count(Line#) from t_order_table ord
Where User_Assigned is null
and o.Order = ord.Order) as 'Open Lines',
(Select Count(Line#) from t_order_table ord
Where User_Assigned is not null
and o.Order = ord.Order) as 'Picked Lines',
(Select User_Assigned from t_order_table ord
Where o.Order = ord.Order) as 'User Assigned'
from t_order_table o
Group By Order, User_Assigned
Which Returns:
Order Open Lines Picked Lines User_Assigned
12345 9 0 Null
11223 3 0 Null
As a worker logs into the system, he gets assigned to the lines, one by one as he picks the order. After he picks 3 lines, the table would look like this:
t_order_table
Line# Order User_Assigned
1 12345 Chris
2 12345 Chris
3 12345 Chris
4 12345 Null
5 12345 Null
6 12345 Null
7 12345 Null
8 12345 Null
9 12345 Null
1 11223 Null
2 11223 Null
3 11223 Null
and the report would look like this:
Order Open Lines Picked Lines User_Assigned
12345 9 3 Null
12345 9 3 Chris
11223 3 0 Null
The Result has a duplicated record because the SubSelect Query for 'User_Assigned' found a null and a user, so there is two records. I would like there to be some logic that checks if a user is assigned, it returns the user, else returns null. I can't figure out how to do that. Thanks for the help!!!!
This seems to give you what you need.
set transaction isolation level read uncommitted;
declare #OrderTable as table
(
[Line] tinyint,
[Order] int,
[User_Assigned] nvarchar(50)
);
insert into #OrderTable
(
[Line],
[Order],
[User_Assigned]
)
values
(1, 12345, 'Chris'),
(2, 12345, 'Chris'),
(3, 12345, 'Chris'),
(4, 12345, null),
(5, 12345, null),
(6, 12345, null),
(7, 12345, null),
(8, 12345, null),
(9, 12345, null),
(1, 11223, null),
(2, 11223, null),
(3, 11223, null);
select
o.[Order],
sum(iif(o.User_Assigned is null, 1, 0)) as [OpenLines],
sum(iif(o.User_Assigned is not null, 1, 0)) as [PickedLines],
max(o.User_Assigned) as [User_Assigned]
from #OrderTable as [o]
group by
o.[Order]
Returns:
Order OpenLines PickedLines User_Assigned
11223 3 0 NULL
12345 6 3 Chris

Grouping while adding previous row value into next

I have a table where I am storing leave information of employees and I want to calculate total leaves employee have taken so far after taking every leave.
I have tried query but it is not grouping for each employee instead it keeps on adding all the previous rows values.
CREATE TABLE SampleLeave
(
ID Numeric,
EmpCode Numeric,
NoOfLeaves Numeric
);
INSERT INTO SampleLeave VALUES (1, 12, 2);
INSERT INTO SampleLeave VALUES (2, 12, 1);
INSERT INTO SampleLeave VALUES (3, 12, 3);
INSERT INTO SampleLeave VALUES (4, 1, 1);
INSERT INTO SampleLeave VALUES (5, 1, 5);
SELECT *, SUM([NoOfLeaves]) OVER (ORDER BY [ID]) Leaves FROM SampleLeave
Here is the output I am getting instead I want the Sum function to calculate the number of leaves when employee ID differs.
ID EmpCode NoOfLeaves Leaves
--------------------------------------- --------------------------------------- --------------------------------------- ---------------------------------------
1 12 2 2
2 12 1 3
3 12 3 6
4 1 1 7
5 1 5 12
Output I am expecting:
ID EmpCode NoOfLeaves Leaves
--------------------------------------- --------------------------------------- --------------------------------------- ---------------------------------------
1 12 2 2
2 12 1 3
3 12 3 6
4 1 1 1
5 1 5 6
You would required to add partition clause in order to get departmental leaves based on EmpCode
select *,
sum([NoOfLeaves]) over (partition by EmpCode order by id) Leaves
from SampleLeave
order by 1

SQL - Efficient way to make sure both entities represented in a set of key Keypairs exist in final dataset

Question: How do I efficiently get a list of people (#People table below, one person per row) that are matched as a set (#Keys table below), but also make sure that the sets are unique.
Background: I'm working with sets of matches in a database (in the form of KeyId, PersonId1, PersonId2). We have an automatic method that flags people as duplicates and writes them to a Match table. There aren't too many, but we usually sit with about 100K match records and 200K people. The match process has also added duplicate records in the form of matching Person 1 with 2, and also matching 2 with 1. Also, we logically delete people records (IsDeleted = 1) so do not want to return matches where one person has already been deleted.
We have an administration screen where people can look at the duplicates and flag whether they aren't dupes, or delete one of the pair. There has been a problem where even if one person in the pair was deleted, the other one was still showing in the list. The SQL below is an attempt to make sure that only people that exist as a set are returned.
Test Data Setup:
CREATE TABLE #Keys
(
KeyId int PRIMARY KEY,
PersonId1 int,
PersonId2 int
)
CREATE TABLE #People
(
PersonId int PRIMARY KEY,
Name varchar(150),
IsDeleted bit
)
INSERT INTO #People
VALUES (1, 'John',0),
(2, 'Madeline',0),
(3, 'Ralph',1),
(4, 'Sarah',0),
(5, 'Jack',0),
(6, 'Olivia',0),
(7, 'Ethan',0),
(8, 'Sophia',0)
INSERT INTO #Keys
VALUES (1,1,2),
(2,2,3),
(3,1,3),
(4,2,1),
(5,4,8),
(6,3,7),
(7,6,1)
SELECT *
FROM #Keys k
JOIN #People p1
ON k.PersonId1 = p1.PersonId
AND p1.IsDeleted = 0
JOIN #People p2
ON k.PersonId2 = p2.PersonId
AND p2.IsDeleted = 0
Returns:
KeyId PersonId1 PersonId2 PersonId Name IsDeleted PersonId Name IsDeleted
1 1 2 1 John 0 2 Madeline 0
4 2 1 2 Madeline 0 1 John 0
5 4 8 4 Sarah 0 8 Sophia 0
7 6 1 6 Olivia 0 1 John 0
SELECT KeyId, p1.PersonId, p1.Name
INTO #Results
FROM #Keys k
JOIN #People p1
ON k.PersonId1 = p1.PersonId
AND p1.IsDeleted = 0
JOIN #People p2
ON k.PersonId2 = p2.PersonId
AND p2.IsDeleted = 0
INSERT INTO #Results
SELECT KeyId, p2.PersonId, p2.Name
FROM #Keys k
JOIN #People p1
ON k.PersonId1 = p1.PersonId
AND p1.IsDeleted = 0
JOIN #People p2
ON k.PersonId2 = p2.PersonId
AND p2.IsDeleted = 0
SELECT * from #Results
order by KeyId
DROP TABLE #People
DROP TABLE #Keys
DROP TABLE #Results
The final query returns this set:
KeyId PersonId Name
1 2 Madeline
1 1 John
4 2 Madeline
4 1 John
5 8 Sophia
5 4 Sarah
7 6 Olivia
7 1 John
But it has the problem that Keys 1 and 4 have the same people, just reversed in order. The set I'd like returned is:
KeyId PersonId Name
1 2 Madeline
1 1 John
5 4 Sarah
5 8 Sophia
7 1 John
7 6 Olivia
First I would make
PersonId1 int,
PersonId2 int
The PK on #Keys and drop KeyId
A quick way to get the unique
select PersonId1, PersonId2
from keys
where PersonId1 < PersonId2
union
select PersonId2, PersonId1
from keys
where PersonId2 < PersonId1
Clearly you would need to add in the join on deleted
You could also put a constraint on #Keys that PersonId1 < PersonId2
I think this will work also
select PersonId1, PersonId2 from keys
except
select PersonId2, PersonId1 from keys

Finding ID having all values (mySQL, SQL)

Think about a table like this
ID Value
100 1
100 3
101 1
101 4
102 2
102 5
103 2
103 4
104 1
104 3
105 2
105 5
The problem is, if I give values 2 and 5 I should get 102 and 105 which both 102 and 105 are having values 2 and 5 at the same time or if I give values 1 and 3 I should get 100 and 104.
How can I do this with only one sql command? I need a query as light as possible.
and using UNION, INTERSECTION or NESTED SQLs, which one is faster, slower, heavier e.t.c?
Thanks in advance
Ergec
Try something like this:
SELECT id
FROM test
WHERE
value in (2,5)
GROUP by id
HAVING count(*) = 2
If you want to test it, simple table for it (with no indexes!):
CREATE TABLE IF NOT EXISTS test (
id int(4) NOT NULL,
`value` int(4) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO test (id, value) VALUES
(100, 1),
(100, 3),
(101, 1),
(101, 4),
(102, 2),
(102, 5),
(103, 2),
(103, 4),
(104, 1),
(104, 3),
(105, 2),
(105, 5);
I had a very similar question a few days ago. see MySQL - Find rows matching all rows from joined table
There's lots of solutions. Addition to the Jhonny's solution, you can use a join
SELECT DISTINCT t1.id
FROM table t1, table t2
WHERE t1.id = t2.id
AND t1.value = 2
AND t2.value = 5
OR use Intersect
SELECT id FROM table WHERE value = 2
INTERSECT
SELECT id FROM table WHERE value = 5
This would be my way to achieve it:
SELECT DISTINCT id
FROM table
WHERE
id IN (SELECT id FROM table WHERE value = 2)
AND
id IN (SELECT id FROM table WHERE value = 5)