SQL query to get Groups and sub groups hierarchy - sql-server

Account table
ac_id ac_name st_id
----------- ------------- -----------
1 LIABILITES 1
2 ASSET 1
3 REVENUE 1
4 EXPENSES 1
5 EQUITY 1
Groups table
grp_id grp_name ac_no grp_of st_id type_ cmp_id
----------- ------------------- ---------- -------- --------- --------- --------
1 Capital Account 1 0 1 0 0
2 Current Liability 1 0 1 0 0
3 Loan Liability 1 0 1 0 0
4 Suspense A/C 1 0 1 0 0
5 Current Assets 2 0 1 0 0
6 Fixed Assests 2 0 1 0 0
7 Investment 2 0 1 0 0
8 Misc. Expenses 2 0 1 0 0
9 Direct Income 3 0 1 0 0
10 Indirect Income 3 0 1 0 0
11 Sale Account 3 0 1 0 0
12 Direct Expense 4 0 1 0 0
13 Indirect Expense 4 0 1 0 0
14 Purchase Account 4 0 1 0 0
15 Sundry Creditors 2 1 1 0 0
16 Sundry Debitors 5 1 1 0 0
17 Bank Account 5 1 1 0 0
18 Cash In Hand 5 1 1 0 0
19 Duties & Taxes 2 1 1 0 0
20 Salary 12 1 1 0 0
21 Personal 5 1 1 0 0
22 Loan 2 0 1 0 0
23 Customer 16 1 1 0 0
34 Vendor 15 1 1 0 0
38 Sale Softwares 11 1 1 1 1
46 Stock In Hand 5 1 1 1 1
47 test 1 1 1 1 1
48 test in 47 1 1 1 1
Query to get all groups hierarchy.
declare #ac_no as int =2
;With CTE(grp_id,grp_name,ac_no,Level)
AS
( SELECT
grp_id,grp_name,ac_no,CAST(1 AS int)
FROM
Groups
WHERE
grp_id in (select grp_id from Groups where (ac_no=#ac_no) and grp_of=0)
UNION ALL
SELECT
o.grp_id,o.grp_name,o.ac_no,c.Level+1
FROM
Groups o
INNER JOIN
CTE c
ON c.grp_id=o.ac_no --where o.ac_no=2 and o.grp_of=1
)
select * from CTE
Result is ok for ac_no=2/3/4
grp_id grp_name ac_no Level
----------- ------------------- ----------- ------
5 Current Assets 2 1
6 Fixed Assests 2 1
7 Investment 2 1
8 Misc. Expenses 2 1
22 Loan 2 1
16 Sundry Debitors 5 2
17 Bank Account 5 2
18 Cash In Hand 5 2
21 Personal 5 2
46 Stock In Hand 5 2
23 Customer 16 3
But when I try to get result for ac_no=1;
I get error :
Msg 530, Level 16, State 1, Line 4
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.

I think the issue is that you end up in an infinite recursion as you have a row that is it's own parent/child (eg. grp_id = ac_no).
I think it should work if you add a limiting clause to the recursive member like this:
DECLARE #ac_no AS int = 1;
WITH CTE (grp_id , grp_name , ac_no , Level ) AS (
SELECT grp_id, grp_name, ac_no, CAST( 1 AS int )
FROM Groups
WHERE grp_id IN (SELECT grp_id FROM Groups WHERE ac_no = #ac_no AND grp_of = 0)
UNION ALL
SELECT o.grp_id, o.grp_name, o.ac_no, c.Level + 1
FROM Groups o
INNER JOIN CTE c ON c.grp_id = o.ac_no --where o.ac_no=2 and o.grp_of=1
WHERE c.ac_no <> c.grp_id
)
SELECT * FROM CTE;

Related

SQL Pivot Table - Subqueries

I have a program that is logging the time a user spends on certain aspects, some are identified as specific "time". I'm struggling to get multiple lines of Grouped query results into a single line for each month as a "summary".
My current query:
SELECT
TotalMins = SUM(Minutes)
,DateMonth = MONTH(Date)
,ID1
,PC
FROM User_Time_Log
WHERE
(UserID = 1)
AND (YEAR(Date) = 2018)
GROUP BY
MONTH(Date)
,ID1
,PC1
Current results:
TotalMins DateMonth ID1 PC1
192 1 0 0
306 1 0 100
113 2 0 0
365 2 0 100
14 2 1 0
3 2 1 100
75 3 0 0
253 3 0 100
3 3 1 0
300 4 0 0
233 4 0 100
10 4 1 0
23 4 1 100
438 5 0 0
134 5 0 100
19 5 1 0
49 5 1 100
0 9 1 0
11 10 0 0
21 10 0 60
167 10 1 100
What I would like to do from this point is to create a table showing all 12 months, regardless of whether there is information within that month or not, and show the relative information within each row for that month. for example:
DateMonth NonID1 TimeID1 TimePC1 (Round((PC1/100)*TotalMins)) TimePC1ID1
1 192 0 306 0
2 113 14 365 3
3 75 3 253 0
4 300 10 233 23
5 438 19 134 49
6 0 0 0 0
7 0 0 0 0
8 0 0 0 0
9 0 0 0 0
10 11 0 13 167
11 0 0 0 0
12 0 0 0 0
What's the most efficient way to do this?
Note: I have also created a table to give me 1-12 as rows that I can use to give me the months that I need to use, where information is not within the user_time_log.
Here's a simple way to do what you're looking for:
First, create your table of month values. I made a simple temp table with a single column.
CREATE TABLE #Dates (MonthNum INT)
INSERT INTO #Dates
(
MonthNum
)
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
Next, you can put your existing query into a CTE, then LEFT JOIN to your table of months. You'll want to put your columns into a SUM'd CASE statement, like so:
;WITH Aggregation AS
(
SELECT
TotalMins = SUM(Minutes)
,DateMonth = MONTH(Date)
,ID1
,PC1
FROM #User_Time_Log
WHERE
(UserID = 1)
AND (YEAR(Date) = 2018)
GROUP BY
MONTH(Date)
,ID1
,PC1
)
SELECT
d.MonthNum
,NonID1 = SUM(CASE WHEN ID1 = 0 THEN TotalMins ELSE 0 END)
,TimeID1 = SUM(CASE WHEN ID1 = 1 THEN TotalMins ELSE 0 END)
,TimePC1 = SUM(CASE WHEN ID1 = 0 THEN ROUND((PC1/100)*TotalMins,0) ELSE 0 END)
,TimePC1ID1 = SUM(CASE WHEN ID1 = 1 THEN ROUND((PC1/100)*TotalMins,0) ELSE 0 END)
FROM #Dates d
LEFT JOIN Aggregation a ON d.MonthNum = a.DateMonth
GROUP BY d.MonthNum
Output would then look like this:
MonthNum NonID1 TimeID1 TimePC1 TimePC1ID1
1 498 0 306 0
2 478 17 365 3
3 328 3 253 0
4 533 33 233 23
5 572 68 134 49
6 0 0 0 0
7 0 0 0 0
8 0 0 0 0
9 0 0 0 0
10 32 167 0 167
11 0 0 0 0
12 0 0 0 0
EDIT:
The ROUND() function call can be changed slightly to accomodate your need for decimal results. The first parameter of ROUND() is the expression you want to round, and the second is the number of decimal places to round to. Positive numbers indicate the number of places to the right of the decimal to round to. Negative numbers indicate the number of places to the left of the decimal to round to. So if you set it to 2, you'll get an answer rounded to the nearest hundredth.
But there's one more tweak we need. PC1 and TotalMins are both assumed to be INTs in my answer. So we have to give the SQL engine a little help so that it calculates the answer as a DECIMAL. By CAST()ing the INTs to DECIMALs, SQL will perform the arithmetic op as decimal math instead of integer math. You'd just have to change TimePC1 and TimePC1ID1 like so:
,TimePC1 = SUM(CASE WHEN ID1 = 0 THEN ROUND((CAST(PC1 AS DECIMAL)/100)*CAST(TotalMins AS DECIMAL),2) ELSE 0 END)
,TimePC1ID1 = SUM(CASE WHEN ID1 = 1 THEN ROUND((CAST(PC1 AS DECIMAL)/100)*CAST(TotalMins AS DECIMAL),2) ELSE 0 END)
Then the output looks like this:
MonthNum NonID1 TimeID1 TimePC1 TimePC1ID1
1 498 0 306.000000 0.000000
2 478 17 365.000000 3.000000
3 328 3 253.000000 0.000000
4 533 33 233.000000 23.000000
5 572 68 134.000000 49.000000
6 0 0 0.000000 0.000000
7 0 0 0.000000 0.000000
8 0 0 0.000000 0.000000
9 0 0 0.000000 0.000000
10 32 167 12.600000 167.000000
11 0 0 0.000000 0.000000
12 0 0 0.000000 0.000000

Get output of multiple Counts in one query

Could you help me how to write a query for the following issue:
There are two tables:
Table persons:
P_id Name BirthDate
1 N1 2016-08-02
2 N2 2015-05-02
3 N3 2013-06-01
4 N4 2014-01-09
Table visited:(p_id is foreign key to table persons)
Id. Visitor_id. P_id. Visit_date
1 10 1 2017-03-05
2 11 2 2017-01-01
3 10 2 2017-02-03
4 12 3 2016-05-07
5 11 4 2016-04-09
6 10 1 2017-04-09
We are going to get the count of visited by each Visitor and also count of visited distinct person on filter on for those person who their age are under 1, between 1 and 2, between 2 and 3 at date of visit_date by each visitor_id.
The results should be like :
Under_one Bet_one_two Bet_two_three
Visitor_id VisitedCount/PersonCount VisitedCount/PersonCount VisitedCount/PersonCount
10 2 1 1 1 0 0
11 0 0 1 1 1 1
12 0 0 0 0 1 1
Between 1 and 2 means the result of subtracting visited_date and birthdate (for example : the result of 2013/03/05 - 2011/06/07) is between 1 and 2 years.
I don't know if I can give you the output laid out exactly as you have specified, but this
SELECT
visited.Visitor_id,
visited.P_id,
Int(([Visit_date]-[BirthDate])/365) AS Age,
Count(persons.P_id) AS NumVisits
FROM persons INNER JOIN visited ON persons.P_id = visited.P_id
GROUP BY
visited.Visitor_id,
visited.P_id,
Int((-[BirthDate]+[Visit_date])/365);
returns
Visitor_id P_id Age NumVisits
10 1 0 2
10 2 1 1
11 2 1 1
11 4 2 1
12 3 2 1

How to count number of months in T-SQL

I've got a problem in SQL Server.
"Whate'er is well conceived is clearly said, And the words to say it flow with ease", Nicolas Boileau-Despreaux
Well, I don't think I'll be able to make it clear but I'll try ! And I'd like to apologize for my bad english !
I've got this table :
id ind lvl result date
1 1 a 3 2017-01-31
2 1 a 3 2017-02-28
3 1 a 1 2017-03-31
4 1 a 1 2017-04-30
5 1 a 1 2017-05-31
6 1 b 1 2017-01-31
7 1 b 3 2017-02-28
8 1 b 3 2017-03-31
9 1 b 1 2017-04-30
10 1 b 1 2017-05-31
11 2 a 3 2017-01-31
12 2 a 1 2017-02-28
13 2 a 3 2017-03-31
14 2 a 1 2017-04-30
15 2 a 3 2017-05-31
I'd like to count the number of month the combo {ind, lvl} remain in the result 1 before re-initializing the number of month to 0 if the result is not 1.
Clearly, I need to get something like that :
id ind lvl result date BadResultRemainsFor%Months
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
So that if I was looking for the number of months the result was 1 for the date 2017-05-31 with the id 1 and the lvl a, I know it's been 3 months.
Assume all the date the the end day of month:
;WITH tb(id,ind,lvl,result,date) AS(
select 1,1,'a',3,'2017-01-31' UNION
select 2,1,'a',3,'2017-02-28' UNION
select 3,1,'a',1,'2017-03-31' UNION
select 4,1,'a',1,'2017-04-30' UNION
select 5,1,'a',1,'2017-05-31' UNION
select 6,1,'b',1,'2017-01-31' UNION
select 7,1,'b',3,'2017-02-28' UNION
select 8,1,'b',3,'2017-03-31' UNION
select 9,1,'b',1,'2017-04-30' UNION
select 10,1,'b',1,'2017-05-31' UNION
select 11,2,'a',3,'2017-01-31' UNION
select 12,2,'a',1,'2017-02-28' UNION
select 13,2,'a',3,'2017-03-31' UNION
select 14,2,'a',1,'2017-04-30' UNION
select 15,2,'a',3,'2017-05-31'
)
SELECT t.id,t.ind,t.lvl,t.result,t.date
,CASE WHEN t.isMatched=1 THEN ROW_NUMBER()OVER(PARTITION BY t.ind,t.lvl,t.id-t.rn ORDER BY t.id) ELSE 0 END
FROM (
SELECT t1.*,c.MonthDiff,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END AS isMatched
,CASE WHEN c.MonthDiff=t1.result THEN ROW_NUMBER()OVER(PARTITION BY t1.ind,t1.lvl,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END ORDER BY t1.id) ELSE null END AS rn
FROM tb AS t1
LEFT JOIN tb AS t2 ON t1.ind=t2.ind AND t1.lvl=t2.lvl AND t2.id=t1.id-1
CROSS APPLY(VALUES(ISNULL(DATEDIFF(MONTH,t2.date,t1.date),1))) c(MonthDiff)
) AS t
ORDER BY t.id
id ind lvl result date
----------- ----------- ---- ----------- ---------- --------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
By slightly tweaking your input data and slightly tweaking how we define the requirement, it becomes quite simple to produce the expected results.
First, we tweak your date values so that the only thing that varies is the month and year - the days are all the same. I've chosen to do that my adding 1 day to each value1. The fact that this produces results which are one month advanced doesn't matter here, since all values are similarly transformed, and so the monthly relationships stay the same.
Then, we introduce a numbers table - here, I've assumed a small fixed table is adequate. If it doesn't fit your needs, you can easily locate examples online for creating a large fixed numbers table that you can use for this query.
And, finally, we recast the problem statement. Instead of trying to count months, we instead ask "what's the smallest number of months, greater of equal to zero, that I need to go back from the current row, to locate a row with a non-1 result?". And so, we produce this query:
declare #t table (id int not null,ind int not null,lvl varchar(13) not null,
result int not null,date date not null)
insert into #t(id,ind,lvl,result,date) values
(1 ,1,'a',3,'20170131'), (2 ,1,'a',3,'20170228'), (3 ,1,'a',1,'20170331'),
(4 ,1,'a',1,'20170430'), (5 ,1,'a',1,'20170531'), (6 ,1,'b',1,'20170131'),
(7 ,1,'b',3,'20170228'), (8 ,1,'b',3,'20170331'), (9 ,1,'b',1,'20170430'),
(10,1,'b',1,'20170531'), (11,2,'a',3,'20170131'), (12,2,'a',1,'20170228'),
(13,2,'a',3,'20170331'), (14,2,'a',1,'20170430'), (15,2,'a',3,'20170531')
;With Tweaked as (
select
*,
DATEADD(day,1,date) as dp1d
from
#t
), Numbers(n) as (
select 0 union all select 1 union all select 2 union all select 3 union all select 4
union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
)
select
id, ind, lvl, result, date,
COALESCE(
(select MIN(n) from Numbers n1
inner join Tweaked t2
on
t2.ind = t1.ind and
t2.lvl = t1.lvl and
t2.dp1d = DATEADD(month,-n,t1.dp1d)
where
t2.result != 1
),
1) as [BadResultRemainsFor%Months]
from
Tweaked t1
The COALESCE is just there to deal with the edge case, such as for your 1,b data, where there is no previous row with a non-1 result.
Results:
id ind lvl result date BadResultRemainsFor%Months
----------- ----------- ------------- ----------- ---------- --------------------------
1 1 a 3 2017-01-31 0
2 1 a 3 2017-02-28 0
3 1 a 1 2017-03-31 1
4 1 a 1 2017-04-30 2
5 1 a 1 2017-05-31 3
6 1 b 1 2017-01-31 1
7 1 b 3 2017-02-28 0
8 1 b 3 2017-03-31 0
9 1 b 1 2017-04-30 1
10 1 b 1 2017-05-31 2
11 2 a 3 2017-01-31 0
12 2 a 1 2017-02-28 1
13 2 a 3 2017-03-31 0
14 2 a 1 2017-04-30 1
15 2 a 3 2017-05-31 0
1An alternative way to perform the adjustment is to use a DATEADD/DATEDIFF pair to perform a "floor" operation against the dates:
DATEADD(month,DATEDIFF(month,0,date),0) as dp1d
Which resets all of the date values to be the first of their own month rather than the following month. This may fell more "natural" to you, or you may already have such values available in your original data.
Assuming the dates are continously increasing in month, you can use window function like so:
select
t.id, ind, lvl, result, dat,
case when result = 1 then row_number() over (partition by grp order by id) else 0 end x
from (
select t.*,
dense_rank() over (order by e, result) grp
from (
select
t.*,
row_number() over (order by id) - row_number() over (partition by ind, lvl, result order by id) e
from your_table t
order by id) t ) t;

SQL Query multiple values of a column as multiple columns (SQL 2005)

When I run a SQL query on a single table and here is the data (this is just a sample, error column might be more than 10)
time total Error
00:16 6 10000(E)
00:20 4 10000(E)
00:46 2 10000(E)
01:01 2 10000(E)
01:40 2 10000(E)
02:07 2 10000(E)
02:52 1 10000(E)
04:27 2 10000(E)
04:29 6 10000(E)
04:32 4 10000(E)
04:49 2 10000(E)
04:50 2 10000(E)
06:18 2 10000(E)
09:04 1 10000(E)
10:57 4 10000(E)
10:58 4 10000(E)
00:36 1 9401(E)
00:37 1 9401(E)
00:57 1 9401(E)
00:58 1 9401(E)
01:32 1 9401(E)
01:33 1 9401(E)
02:36 2 9401(E)
03:05 1 9401(E)
03:06 1 9401(E)
09:53 2 9401(E)
12:11 2 9401(E)
12:12 4 9401(E)
12:41 1 9401(E)
I want to write a SQL query so that I want to get the above data like this
time 10000(E) 9401(E)
---------------------------
00:16 6 0
00:20 4 0
00:36 0 1
00:37 0 1
00:46 2 0
00:57 0 1
00:58 0 1
01:01 2 0
01:32 0 1
01:33 0 1
01:40 2 0
02:07 2 0
02:36 0 2
02:52 1 0
03:05 0 1
03:06 0 1
04:27 2 0
04:29 6 0
04:32 4 0
04:49 2 0
04:50 2 0
06:18 2 0
09:04 1 0
09:53 0 1
10:57 4 0
10:58 4 0
12:11 0 2
12:12 0 4
12:41 0 1
is this possible??
Does this meet your requirement?
select e.time
, e.[10000(E)]
, e.[9401(E)]
from (
select time
, SUM(case when Error LIKE N'10000(E)' then Total else NULL end) as [10000(E)]
, null as [9401(E)]
from MyTable
where Error LIKE N'10000(E)'
group by time
union
select time
, null as [10000(E)]
, SUM(case when Error LIKE N'9401' then Total else NULL end) as [9401(E)]
from MyTable
where Error LIKE N'9401(E)'
group by time
) e
order by e.time
If no, please tell me about the result so that I can bring the righteous corrections.
The SUM function only comes to group the number of occurences of a same error into one given time, which seems to be what you have in your table, actually. So, it shouldn't modify any data. On the other hand, if you had two different records of the same error by the same time, then they should be grouped by this time and the total of occurences of this error will be additioned.
For your given in- and output it could be as simple as this.
SELECT *
FROM (
SELECT time
, [10000(E)] = Total
, [9401(E)] = 0
FROM YourTable
WHERE Error = '10000(E)'
UNION ALL
SELECT time
, [10000(E)] = 0
, [9401(E)] = Total
FROM YourTable
WHERE Error = '9401(E)'
) q
ORDER BY
time

SQL Pivot MIN( COUNT (

I have a complicated MS SQL 2005 query with a PIVOT in it...
I have data like this :
Data ( clients left join visits ):
ClientID VisitID ServiceID
1 34 5
1 36 2
1 36 5
1 45 2
1 48 2
1 60 2
2 44 1
3 48 2
3 78 3
3 79 2
And what I need is this:
ID [1] [2] [3] [4] [5]
1 0 1 0 0 1
2 1 0 0 0 0
3 0 1 1 0 0
The SQL Query I am using is this:
select * from
(select clients.ClientID, clients.serviceID FROM clients left join visits on visit.cliendid=clients.clientid ) e
pivot ( COUNT(serviceID) for serviceID in ([1],[2],...,[54]) ) p
But this SQL Query doesn't do what I want, instead it does this:
ID [1] [2] [3] [4] [5]
1 0 4 0 0 2
2 1 0 0 0 0
3 0 2 1 0 0
I need all of the data in the columns to be either 0 or 1, as in, 0 has never used this service, 1 has used this service...
How do I do this?
It would be awesome if I could do pivot ( Math.MIN ( COUNT(serviceID), 0 ) for ... or if I could do ( CASE (COUNT(serviceID) > 0 ) THEN 1 ELSE 0 for ...
but it won't let me.
SELECT *
FROM (
SELECT DISTINCT clients.ClientID, clients.serviceID
FROM clients
) e
PIVOT (
COUNT(serviceID)
FOR serviceID in ([1],[2])
) p

Resources