Author |
Topic |
adalius
Starting Member
11 Posts |
Posted - 2014-11-18 : 15:37:07
|
I'm looking to find a query that will be a bit more elegant than a previous solution (using VBA to iterate row by row and populate a second table). To begin with, we have the following tables which I cannot change (they're part of an accounting package):The first, [PRUnionMN] is empty at the start of each month. Every week payroll is processed and it appends to this table. [co]/[local]/[un] identify the company, union local, and craft of the employee identified by [empl]. It is possible to have the same [empl] in this table more than once under different combinations of the above 3 fields (for instance if an ironworker worked in southern local 8 part of the month and northern local 103 the rest of the month).[ben_ty<x>] is a ID that corresponds to [PRUnionBenDedCd].[bended_cd] below. [ben_amt<x>] is the amount allocated to the benefit indicated in [ben_ty<x>] total for the month to date. CREATE TABLE [dbo].[PRUnionMN]( [co] [varchar](2) , [local] [varchar](7) , [un] [numeric](12, 0) , [empl] [int] , [ben_ty1] [smallint] , [ben_amt1] [money] , [ben_ty2] [smallint] , [ben_amt2] [money] , [ben_ty3] [smallint] , [ben_amt3] [money] , [ben_ty4] [smallint] , [ben_amt4] [money] , [ben_ty5] [smallint] , [ben_amt5] [money] , [ben_ty6] [smallint] , [ben_amt6] [money] , [ben_ty7] [smallint] , [ben_amt7] [money] , [ben_ty8] [smallint] , [ben_amt8] [money] , [ben_ty9] [smallint] , [ben_amt9] [money] , [ben_ty10] [smallint] , [ben_amt10] [money] , [ben_ty11] [smallint] , [ben_amt11] [money] , [ben_ty12] [smallint] , [ben_amt12] [money] , [ben_ty13] [smallint] , [ben_amt13] [money] , [ben_ty14] [smallint] , [ben_amt14] [money] , [ben_ty15] [smallint] , [ben_amt15] [money] , [ben_ty16] [smallint] , [ben_amt16] [money]) This table is a simple key:value pairing of an ID and a description, for instance (12:'Pension') or (3:'Apprentice Training'). CREATE TABLE [dbo].[PRUnBenDedCd]( [bended_cd] [smallint], [descr] [varchar](24)) This table correlates a company ID [co], union code, and benefit code together with a rate for how much should be deducted hourly.CREATE TABLE [dbo].[PRUnionBene]( [co] [varchar](2) , [union_cd] [numeric](12, 0) , [bended_cd] [smallint] , [rate] [float]) The question is this, given the names outlined by [PRUnionBenDedCd].[descr] can be modified/added/removed at any time, what is the best way to get a result set that looks like the following:[co], [un], [local], [empl], [Pension Amount], [Apprentice Training Amount], <so on for all known descriptions>.If [PRUnionMN].[ben_type<x>] and [ben_amt<x>] were always holding the value of what was allocated to [PRUnionBenDedCd].[descr] for [PRUnionBenDedCd].[bended_cd]=<x>, this would be an easy deal but because of the current layout I'm scratching my head. I've thought about dynamic pivots but I'm not real familiar with PIVOT to begin with, and the data isn't sorted to begin with since [ben_type1] might contain 5 different types of benefits across 5 employees.Maybe I'm just overthinking this and missing something obvious but I'm puzzled and while I'm familiar with SQL it's not what I deal with day in and day out so I'm not as 'up to snuff' on it as I should be for this project.I can get more data/info if requested but I think this might be sufficient.Hopefully someone can point me in the right direction... |
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 10:12:45
|
Anybody? |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 11:34:58
|
How would you calculate Pension Amount and Apprentice Training Amount for one description? |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 11:47:36
|
quote: Originally posted by gbritton How would you calculate Pension Amount and Apprentice Training Amount for one description?
It isn't calculated, PRUnionMN gives you the values under the ben_ty<x> and ben_amt<x> columns, but the order is non-static. One entry might have ben_ty1 = 12 and the next might have ben_ty3 = 12, so pension isn't always in column ben_ty1 from employee to employee because of how it processes. Again, that's sadly built into the accounting software so I can't change it so that column 1 is always the same thing.Imagine PRUnBenDed_cd 12 equals 'Pension', you might have a record in PRUnionMN that has (co, local, un, empl, ben_ty1, ben_amt1, ben_ty2, ben_amt2,...) such that ('73', '8', '13', 2553, 3, 40.20, 12, 20.20) which is essentially saying that employee in that union for that company had $40.20 in Apprentice training, and $20.20 in pension that month. Another entry might have ('73', '8', '15', 2202, 12, 50.50, 3, 100.00). As you can see, now pension value is $50.50 but it was stored in a different column (ben_ty2 the first time, ben_ty1 the second).Does that make sense? |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 11:53:25
|
The other idea I was toying with is making a temp table:CREATE TABLE [BenefitRuns]( [co] [varchar](2) , [local] [varchar](7) , [un] [numeric](12, 0) , [empl] [int] , [bene_type] [smallint], [bene_amt] [money] And then running 16 separate INSERT calls...INSERT INTO BenefitRuns (co, local, un, empl, bene_type, bene_amt) SELECT co, local, un, empl, ben_typ1, ben_amt1)INSERT INTO BenefitRuns (co, local, un, empl, bene_type, bene_amt) SELECT co, local, un, empl, ben_typ2, ben_amt2)INSERT INTO BenefitRuns (co, local, un, empl, bene_type, bene_amt) SELECT co, local, un, empl, ben_typ3, ben_amt3)<so on...> Then at least I'd have a much simpler table with a single benefit per entry grouped by co/local/un/empl combo... just seems like an awful lot of insert calls but it might be faster than the current VBA iteration method... |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 12:01:32
|
Can you post some sample data for the three tables and show the desired results from that data? |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 12:37:33
|
quote: Originally posted by gbritton Can you post some sample data for the three tables and show the desired results from that data?
Hoo boy. I can try. The table schema I posted above was abbreviated to omit fields that aren't pertinent, the actual is much larger. SQL Server generated a script to create schema and populate the data and that was a 388kb text file. Let me see if I can whittle it down some, unless there's a way to attach the file here or email it? It might take a bit of time to whittle it down and ensure I'm not referencing omitted data. |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 13:29:58
|
Just a few rows with expected output will do |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 14:19:14
|
Alright, in all my test data ben_ty8 thru ben_ty16 (and ben_amt8 thru ben_amt16) were all null so I excluded those.PRUnBenDedCd TableINSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (3, N'HEALTH')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (4, N'PENSION')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (6, N'APPRENTICE FUND')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (7, N'IAP')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (9, N'ANNUITY')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (13, N'IMPACT')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (15, N'CONT ADM FUND')INSERT [dbo].[PRUnBenDedCd] ([bended_cd], [descr]) VALUES (20, N'MANDATORY TRAINING') PRUnionBene TableINSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 16, 4, 0)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 6, 2, 0.04)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 11, 4, 10.02)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 6, 4, 12.35)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 2, 2, 1.63)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 2, 3, 9.10)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 2, 4, 10.02)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 4, 2, 1.63)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 4, 3, 6.80)INSERT [dbo].[PRUnionBene] ([co], [union_cd], [bended_cd], [rate]) VALUES (N'73', 4, 4, 8.91) PRUnionMN TableINSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'8',2,7530,3,336.70,4,370.74,6,12.95,9,157.25,13,8.51,7,6.29,20,9.25)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'8',2,9038,3,291.20,4,320.64,6,11.20,9,136.00,13,7.36,7,5.44,20,8.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'8',2,7191,3,1092.00,4,1202.40,6,42.00,9,510.00,13,27.60,7,20.40,20,30.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'8',2,7262,3,955.50,4,1052.10,6,36.76,9,453.70,13,24.16,7,17.86,20,26.26)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8395,3,887.40,4,1162.76,6,62.64,9,574.20,15,14.36,13,19.58,0,0.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8446,3,969.00,4,1269.68,6,68.40,9,627.00,15,15.68,13,21.38,0,0.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8448,3,941.80,4,1234.04,6,66.48,9,609.40,15,15.24,13,20.78,0,0.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8462,3,581.40,4,761.82,6,41.04,9,376.20,15,9.42,13,12.84,0,0.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8469,3,877.20,4,1149.39,6,61.92,9,567.60,15,14.19,13,19.35,0,0.00)INSERT [dbo].[PRUnionMN] ([co], [local], [un], [empl], [ben_ty1], [ben_amt1], [ben_ty2], [ben_amt2], [ben_ty3], [ben_amt3], [ben_ty4], [ben_amt4], [ben_ty5], [ben_amt5], [ben_ty6], [ben_amt6], [ben_ty7], [ben_amt7]) VALUES (N'73',N'383',4,8484,3,768.40,4,1006.84,6,54.24,9,497.20,15,12.44,13,16.96,0,0.00) Desired Output:([co], [local], [un], [empl], "HEALTH", "PENSION", "APPRENTICE FUND", "IAP", "ANNUITY", "IMPACT", "CONT ADM FUND", "MANDATORY TRAINING")I.e.:'73', '8', 2, 7530, 336.7, 370.74, 12.95, 157.25, 8.51, 6.29, 0, 9.25'73', '8', 2, 9038, 291.2, 320.64, 11.2, 136, 7.36, 5.44, 0, 8'73', '8', 2, 7191, 1092, 1202.4, 42, 510, 27.6, 20.4, 0, 30'73', '8', 2, 7262, 955.5, 1052.1, 36.76, 453.7, 24.16, 17.86, 0, 26.26'73', '383', 4, 8395, 887.4, 1162.76, 62.64, 574.2, 19.58, 0, 14.36, 0'73', '383', 4, 8446, 969, 1269.68, 68.4, 627, 21.38, 0, 15.68, 0'73', '383', 4, 8448, 941.8, 1234.04, 66.48, 609.4, 20.78, 0, 15.24, 0'73', '383', 4, 8462, 581.4, 761.82, 41.04, 376.2, 12.84, 0, 9.42, 0'73', '383', 4, 8469, 877.2, 1149.39, 61.92, 567.6, 19.35, 0, 14.19, 0'73', '383', 4, 8484, 768.4, 1006.84, 54.24, 497.2, 16.96, 0, 12.44, 0 |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 14:45:45
|
I'm thinking beginning with an unpivot operation (not necessarily the SQL keyword) to produce a derived table from the last table like:co, local, un, empl, bentype, benamt as this is probably easier to work with. However for this to work, there needs to be a guarantee that the same benefit type code is in only one of the ben_tyx columns on any given row. Is that the case here? |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 15:03:09
|
e.g.SELECT co, local, un, empl, bene.typ, bene.amtfrom dbo.prunionmn mnCROSS APPLY ( VALUES (ben_ty1, ben_amt1), (ben_ty2, ben_amt2), (ben_ty3, ben_amt3), (ben_ty4, ben_amt4), (ben_ty5, ben_amt5), (ben_ty6, ben_amt6), (ben_ty7, ben_amt7), (ben_ty8, ben_amt8), (ben_ty9, ben_amt9), (ben_ty10, ben_amt10), (ben_ty11, ben_amt11), (ben_ty12, ben_amt12), (ben_ty13, ben_amt13), (ben_ty14, ben_amt14), (ben_ty15, ben_amt15), (ben_ty16, ben_amt16)) bene(typ, amt)WHERE bene.typ IS NOT NULL AND bene.amt IS NOT null With this you have a more normalized form that should be easier to join to the other two tables. If this will work, let's start here. We'll then work up to the final form, which will be a dynamic pivot using the labels from the PRUnBenDedCd table. |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 15:23:53
|
Ok, A) CROSS APPLY is new to me. That's pretty nifty. I'll have to read up on that.B) In any row, a given benefit code will only show up once in all 16 of the columns. However, there may be multiple rows for the same employee (typically if payroll was hand adjusted). So I modified the WHERE clause to:WHERE bene.typ IS NOT NULL AND bene.amt IS NOT null AND bene.typ <> 0 AND mn.updt_fg <>'Y' This avoids any duplicates that may come about from payroll (which is fine, our current method excludes those flagged 'Y' as well).So onto the next step, this wacky dynamic pivot thingy. |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 15:27:17
|
I think I've got it (you may need some ifnull function calls):IF OBJECT_ID(N'tempdb.dbo.#temp', 'U') IS NOT NULL DROP TABLE #tempSELECT co, local, un, empl, ben.typ, sum(ben.amt) ben_total, cd.descrINTO #tempFROM dbo.prunionmn mnCROSS APPLY ( VALUES (ben_ty1, ben_amt1), (ben_ty2, ben_amt2), (ben_ty3, ben_amt3), (ben_ty4, ben_amt4), (ben_ty5, ben_amt5), (ben_ty6, ben_amt6), (ben_ty7, ben_amt7), (ben_ty8, ben_amt8), (ben_ty9, ben_amt9), (ben_ty10, ben_amt10), (ben_ty11, ben_amt11), (ben_ty12, ben_amt12), (ben_ty13, ben_amt13), (ben_ty14, ben_amt14), (ben_ty15, ben_amt15), (ben_ty16, ben_amt16)) ben(typ, amt)JOIN dbo.[PRUnBenDedCd] cd ON ben.typ = cd.bended_cdWHERE ben.typ IN ( SELECT [bended_cd] FROM dbo.[PRUnBenDedCd] )GROUP BY co, local, un, empl, ben.typ, cd.descrORDER BY co, local, un, empl, ben.typDECLARE @collist nvarchar(max) = STUFF((SELECT N','+QUOTENAME(descr)FROM (select distinct typ, descr from #temp) _ORDER BY typFOR XML path('')),1,1,'')DECLARE @sql nvarchar(max) = N'SELECT co, local, un, empl,' + @collist +' FROM ('+ 'SELECT co, local, un, empl, ben_total, descr from #temp'+ ') _' +' PIVOT (sum(ben_total) FOR descr IN ('+ @collist +')) pvt'PRINT @sqlEXEC sp_executesql @sql |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 15:36:27
|
*jaw hits floor*Absolutely brilliant!Quick examination of results seems to indicate this is exactly what I'm looking for. Just have to cross examine against the values the existing one spits out to double check but I think you've done it.You win the internet for the day! |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 15:42:22
|
Yay! |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 15:54:19
|
Ok, one more question... if I want to replace NULL benefit amounts in the output set with 0, where do I ISNULL or COALESCE to do that? I tried wrapping ben.amt and SUM(ben.amt) in the first SELECT, neither worked, I tried wrapping each ben_amt<x> in the cross apply, that didn't work, I tried wrapping ben_total in the SELECT above the pivot and SUM(ben_total) in the pivot and neither of those seem to work either... |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 16:01:36
|
You'll have 0to have two variables for the @collist (I used one). The other one for the ISNULL variables, so (untested)DECLARE @isnull_collist nvarchar(max) = STUFF((SELECT N','+ISNULL(QUOTENAME(descr),0)FROM (select distinct typ, descr from #temp) _ORDER BY typFOR XML path('')),1,1,'') then use the new variable in the first place where @collist appears in the @sql var:DECLARE @sql nvarchar(max) = N'SELECT co, local, un, empl,' + @isnull_collist + ... |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 16:08:31
|
Tweaked it to:DECLARE @isnull_collist nvarchar(max) = STUFF((SELECT N',ISNULL('+QUOTENAME(descr)+',0)'FROM (select distinct typ, descr from #temp) _ORDER BY typFOR XML path('')),1,1,'') Then it works. Thanks! |
|
|
gbritton
Master Smack Fu Yak Hacker
2780 Posts |
Posted - 2014-11-24 : 16:20:50
|
super! |
|
|
adalius
Starting Member
11 Posts |
Posted - 2014-11-24 : 16:32:29
|
Actually, it dropped column names doing it that way. If anybody ever looks back on this thread looking for an answer, it needs to be:DECLARE @isnull_collist nvarchar(max) = STUFF((SELECT N',ISNULL('+QUOTENAME(descr)+',0) AS ' + QUOTENAME(descr)FROM (select distinct typ, descr from #temp) _ORDER BY typFOR XML path('')),1,1,'') |
|
|
|
|
|