I have a table where I have all the customers and a table where I have all their restrictions.
CUSTOMER
customer_id customer_name
1 name 1
2 name 2
CUSTOMER_RESTRICTIONS
rest_type day_of_week hour_start hour_stop customer_id
TYPE1 0 08:00 12:00 1
TYPE1 0 13:00 17:00 1
TYPE2 0 17:00 23:59 1
Problem: I only have a record for a restriction type and a customer when the customer has a restriction and this is a problem in the visualization I want to build.
I need every customer, every day and every restriction type, even when there is no restriction. In that case hour_start and hour_stop would be NULL.
For the tables shown, the output would be
rest_type day_of_week hour_start hour_stop customer_id
TYPE1 0 08:00 12:00 1
TYPE1 0 08:00 12:00 1
TYPE1 1 NULL NULL 1
TYPE1 2 NULL NULL 1
TYPE1 3 NULL NULL 1
TYPE1 4 NULL NULL 1
TYPE1 5 NULL NULL 1
TYPE1 6 NULL NULL 1
TYPE1 1 NULL NULL 1
TYPE1 2 NULL NULL 1
TYPE1 3 NULL NULL 1
TYPE1 4 NULL NULL 1
TYPE1 5 NULL NULL 1
TYPE2 0 NULL NULL 1
TYPE2 1 NULL NULL 1
TYPE2 2 NULL NULL 1
TYPE2 3 NULL NULL 1
TYPE2 4 NULL NULL 1
TYPE2 5 NULL NULL 1
TYPE2 6 NULL NULL 1
TYPE1 0 NULL NULL 2
TYPE1 1 NULL NULL 2
TYPE1 2 NULL NULL 2
TYPE1 3 NULL NULL 2
TYPE1 4 NULL NULL 2
TYPE1 5 NULL NULL 2
TYPE1 6 NULL NULL 2
TYPE2 0 NULL NULL 2
TYPE2 1 NULL NULL 2
TYPE2 2 NULL NULL 2
TYPE2 3 NULL NULL 2
TYPE2 4 NULL NULL 2
TYPE2 5 NULL NULL 2
TYPE2 6 NULL NULL 2
How can I achieve that? I couldn't even start to build this query.
Essentially you need to start with the data you must have and left join the optional data. E.g., something like this:
select c.customer_id
,r.[rest_type]
,d.[day_of_week]
,r.[hour_start]
,r.[hour_stop]
from CUSTOMER c
cross apply (
select 0 as day_of_week
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
) d
left join CUSTOMER_RESTRICTIONS r on c.customer_id = r.customer_id and d.day_of_week = r.day_of_week
Output:
customer_id rest_type day_of_week hour_start hour_stop
----------- --------- ----------- ---------- ---------
1 TYPE1 0 08:00 12:00
1 TYPE1 0 13:00 17:00
1 TYPE2 0 17:00 23:59
1 NULL 1 NULL NULL
1 NULL 2 NULL NULL
1 NULL 3 NULL NULL
1 NULL 4 NULL NULL
1 NULL 5 NULL NULL
1 NULL 6 NULL NULL
If there are only type rest_types, you don't have a lookup table for them, and you want to show a row for each, you would do:
select c.customer_id
,t.[rest_type]
,d.[day_of_week]
,r.[hour_start]
,r.[hour_stop]
from CUSTOMER c
cross apply (
select 0 as day_of_week
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
) d
cross apply (
select 'TYPE1' as rest_type
union all select 'TYPE2'
) t
left join CUSTOMER_RESTRICTIONS r on c.customer_id = r.customer_id
and d.day_of_week = r.day_of_week
and t.rest_type = r.rest_type
(select rest_type, day_of_week,
hour_start ,
hour_stop
from table A
where rest_type IS NOT NULL)
Union
(select rest_type,
day_of_week,
NULL ,NULL
from table A
where rest_type IS NULL)
Is this what you want ?
First off, I wouldn't store rest type as you are, that is a bad habit, it should be a reference table!
You need to cross apply to get all your possible combinations, and then add in the values you DO have...
DECLARE #Customer TABLE (Id INT IDENTITY(1,1), Name NVARCHAR(100))
DECLARE #Rest TABLE (Id INT IDENTITY(1,1), Name NVARCHAR(100))
DECLARE #Restrictions TABLE (Id INT IDENTITY(1,1), RestID INT, CustomerID INT, Day_of_Week TINYINT, hour_start TIME, hour_end TIME)
INSERT INTO #Customer (NAME)
VALUES('JOHN'),('SUSAN')
INSERT INTO #Rest (NAME)
VALUES ('TYPE A'),('TYPE B')
INSERT INTO #Restrictions (RestID,CustomerID,Day_of_Week,hour_start,hour_end)
VALUES (1,1,0,'08:00','12:00'),
(1,1,0,'13:00','17:00'),
(1,2,0,'17:00','23:59')
;WITH DaysofWeek AS
(
SELECT 0 AS dow
UNION ALL
SELECT dow+1
FROM DaysofWeek
WHERE dow<5
)
SELECT *
FROM #Customer C
CROSS APPLY #Rest R
CROSS APPLY DaysofWeek D
LEFT JOIN #Restrictions X
ON X.Day_of_Week=D.dow
AND X.CustomerID=C.Id
AND X.RestID=R.Id
ORDER BY C.Id, D.dow, R.Id
Related
I have table audit_log with these records:
log_id | request_id | status_id
1 | 2 | 5
2 | 2 | 10
3 | 2 | 20
4 | 3 | 10
5 | 3 | 20
I would like to know if there exists request_ids having status_id 5 and 10 at the same time. So this query should return request_id = 2 as its column status_id has values 5 and 10 (request_id 3 is omitted because status_id column has only value of 10 without 5).
How could I do this with SQL?
I think I should use group by request_id, but I don't know how to check if group has status_id with values 5 and 10?
Thanks,
mismas
This could be a way:
/* input data */
with yourTable(log_id , request_id , status_id) as (
select 1 , 2 , 5 from dual union all
select 2 , 2 , 10 from dual union all
select 3 , 2 , 20 from dual union all
select 4 , 3 , 10 from dual union all
select 5 , 3 , 20 from dual
)
/* query */
select request_id
from yourTable
group by request_id
having count( distinct case when status_id in (5,10) then status_id end) = 2
How it works:
select request_id,
case when status_id in (5,10) then status_id end as checkColumn
from yourTable
gives
REQUEST_ID CHECKCOLUMN
---------- -----------
2 5
2 10
2
3 10
3
So the condition count (distinct ...) = 2 does the work
SELECT request_id
FROM table_name
GROUP BY request_id
HAVING COUNT( CASE status_id WHEN 5 THEN 1 END ) > 0
AND COUNT( CASE status_id WHEN 10 THEN 1 END ) > 0
To check if both values exists (without regard to additional values) you can filter before aggregation:
select request_id
from yourTable
where status_id in (5,10)
group by request_id
having count(*) = 2 -- status_id is unique
-- or
having count(distinct status_id) = 2 -- status_id exists multiple times
This should do it:
select
log5.*, log10.status_id
from
audit_log log5
join audit_log log10 on log10.request_id = log5.request_id
where
log5.status_id = 5
and log10.status_id = 10
order by
log5.request_id
;
Here's the output:
+ ----------- + --------------- + -------------- + -------------- +
| log_id | request_id | status_id | status_id |
+ ----------- + --------------- + -------------- + -------------- +
| 1 | 2 | 5 | 10 |
+ ----------- + --------------- + -------------- + -------------- +
1 rows
And here's the sql to set up the example:
create table audit_log (
log_id int,
request_id int,
status_id int
);
insert into audit_log values (1,2,5);
insert into audit_log values (2,2,10);
insert into audit_log values (3,2,20);
insert into audit_log values (4,3,10);
insert into audit_log values (5,3,20);
I have a table with the recorded date, time and quantity of each item a child was given. My end goal is to pivot on that data, but preserve each individual quantity being given out according to date/time and child.
This is easy to achieve without a pivot, but it still takes up an entire row for each instance. What I want, is to flatten out the results to take up fewer rows. There isn't a huge functional difference, I'm just doing this to take up less real estate in the report that will end up using this data.
Updated to include a query for sample data:
DECLARE #Items TABLE (Name VARCHAR(10), Date DATETIME, ItemID INT, Quantity INT)
INSERT INTO #Items VALUES ('Jimmy', '01/23/2017 10:00:00', 1, 2),
('Jimmy', '01/23/2017 12:00:00', 1, 1),
('Jimmy', '01/23/2017 15:00:00', 2, 2),
('Billy', '01/23/2017 09:00:00', 1, 1),
('Billy', '01/23/2017 10:00:00', 2, 3)
This is what my starting table looks like:
Name Date ItemID Quantity
Jimmy 2017-01-23 10:00:00.000 1 2
Jimmy 2017-01-23 12:00:00.000 1 1
Jimmy 2017-01-23 15:00:00.000 2 2
Billy 2017-01-23 09:00:00.000 1 1
Billy 2017-01-23 10:00:00.000 2 3
I use a join to sum up the quantities for each day, sort the quantities into their own respective columns, and then drop the time:
SELECT d.Name, CAST(d.Date AS DATE) AS Date,
SUM(CASE WHEN s.ItemID = 1 THEN s.Quantity ELSE NULL END) AS SumBooks,
SUM(CASE WHEN s.ItemID = 2 THEN s.Quantity ELSE NULL END) AS SumPencils,
MAX(CASE WHEN d.ItemID = 1 THEN d.Quantity ELSE NULL END) AS Books,
MAX(CASE WHEN d.ItemID = 2 THEN d.Quantity ELSE NULL END) AS Pencils
FROM #Items d
INNER JOIN #Items s ON s.Name = d.Name AND CAST(s.Date AS DATE) = CAST(d.Date AS DATE)
GROUP BY d.Name, d.Date
This is the resulting data:
Name Date SumBooks SumPencils Books Pencils
Billy 2017-01-23 1 3 1 NULL
Billy 2017-01-23 1 3 NULL 3
Jimmy 2017-01-23 3 2 2 NULL
Jimmy 2017-01-23 3 2 1 NULL
Jimmy 2017-01-23 3 2 NULL 2
This is the structure I am trying to achieve:
Name Date SumBooks SumPencils Books Pencils
Billy 2017-01-23 1 3 1 3
Jimmy 2017-01-23 3 2 2 2
Jimmy 2017-01-23 3 2 1 NULL
I was able to do this using a cursor to iterate over each row and check a new table for any matches of Date, Name, and Books = NULL. If a match was found, I update that row with the quantity. Else, I insert a new row with the Books quantity and a NULL value in the Pencils field, later to be updated with a Pencils quantity, and so on.
So, I am able to get the results I need, but this check has to be done for every item column. For just a couple items, it isn't a big deal. When there's a dozen or more items and the result has 30+ columns, it ends up being a lot of declared variables and large, repeating IF/ELSE statements.
I'm not sure if this is commonly done, but if it is, I'm lacking the proper verbiage to find out on my own. Thanks in advance for any Suggestions.
If we trade the inner join for an outer apply() or a left join
and include those values to the group by we can get the results you are looking for based on the test data provided.
;with cte as (
select
i.Name
, [Date] = convert(date,i.[Date])
, SumBooks = sum(case when ItemId = 1 then Quantity else null end)
, SumPencils = sum(case when ItemId = 2 then Quantity else null end)
, Books = b.Books
, Pencils = max(case when ItemId = 2 then Quantity else null end)
, rn = row_number() over (
partition by i.Name, convert(varchar(10),i.[Date],120)
order by b.booksdate
)
from #Items i
outer apply (
select Books = Quantity, BooksDate = b.[Date]
from #Items b
where b.ItemId = 1
and b.Name = i.Name
and convert(date,b.[Date])=convert(date,i.[Date])
) as b
group by
i.Name
, convert(date,i.[Date])
, b.Books
, b.BooksDate
)
select
Name
, Date
, SumBooks
, SumPencils
, Books
, Pencils = Pencils + case when rn > 1 then null else 0 end
from cte
alternate left join for b:
left join (
select Books = Quantity, BooksDate = b.[Date], Name, Date
from Items b
where b.ItemId = 1
) as b on b.Name = i.Name and convert(date,b.[Date])=convert(date,i.[Date])
test setup: http://rextester.com/IXHU81911
create table Items (
Name varchar(64)
, Date datetime
, ItemID int
, Quantity int
);
insert into Items values
('Jimmy','2017-01-23 10:00:00.000',1,2)
, ('Jimmy','2017-01-23 12:00:00.000',1,1)
, ('Jimmy','2017-01-23 13:00:00.000',1,1) /* Another 1 Book */
, ('Jimmy','2017-01-23 15:00:00.000',2,2)
, ('Billy','2017-01-23 09:00:00.000',1,1)
, ('Billy','2017-01-23 10:00:00.000',2,3)
, ('Zim' ,'2017-01-23 10:00:00.000',2,1) /* No books */
query:
;with cte as (
select
i.Name
, [Date] = convert(varchar(10),i.[Date],120)
, SumBooks = sum(case when ItemId = 1 then Quantity else null end)
, SumPencils = sum(case when ItemId = 2 then Quantity else null end)
, Books = b.Books
, Pencils = max(case when ItemId = 2 then Quantity else null end)
, rn = row_number() over (
partition by i.Name, convert(varchar(10),i.[Date],120)
order by b.booksdate
)
from Items i
outer apply (
select Books = Quantity, BooksDate = b.[Date]
from Items b
where b.ItemId = 1
and b.Name = i.Name
and convert(date,b.[Date])=convert(date,i.[Date])
) as b
group by
i.Name
, convert(varchar(10),i.[Date],120)
, b.Books
, b.BooksDate
)
select
Name
, Date
, SumBooks
, SumPencils
, Books
, Pencils = Pencils + case when rn > 1 then null else 0 end
from cte
note: convert(varchar(10),i.[Date],120) is used on rextester to override default formatting of date. Use convert(date,i.[Date]) or cast(i.[Date] as date) outside of rextester.
results:
+-------+------------+----------+------------+-------+---------+
| Name | Date | SumBooks | SumPencils | Books | Pencils |
+-------+------------+----------+------------+-------+---------+
| Billy | 2017-01-23 | 1 | 3 | 1 | 3 |
| Jimmy | 2017-01-23 | 4 | 2 | 1 | 2 |
| Jimmy | 2017-01-23 | 4 | 2 | 1 | NULL |
| Jimmy | 2017-01-23 | 4 | 2 | 2 | NULL |
| Zim | 2017-01-23 | NULL | 1 | NULL | 1 |
+-------+------------+----------+------------+-------+---------+
I have a table which stores information as follows and has ID as the primary key:
Link for the table view: https://drive.google.com/file/d/0B4UzXmbWLTJZaU84WnVZMUJDT3M/view?usp=sharing
ID ScheduleDate WorkArea Employee1 Hours1 Employee2 Hours2 Employee3 Hours3 Employee4 Hours4 Employee5 Hours5 Employee6 Hours6 Employee7 Hours7 Employee8 Hours8
1 7/1/2014 W1 A 8 B 7 C 4 D 3 NULL 0 NULL 0 NULL 0 NULL 0
2 7/1/2014 W2 B 8 C 8 0 0 0 0 NULL
3 7/1/2014 W3 C 8 A 8 E 8 F 8 NULL NULL NULL NULL NULL NULL NULL NULL
4 7/1/2014 W4 D 8 B 8 F 8 NULL NULL NULL NULL NULL NULL
5 7/1/2014 W5 E 8 C 8 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
6 7/1/2014 W6 F 8 D 8 8 NULL NULL NULL NULL NULL NULL NULL NULL NULL
7 8/1/2014 W1 G 4 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
8 8/1/2014 W2 A 4 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
9 8/1/2014 W3 B 8 F 8 8 8 NULL NULL NULL NULL NULL NULL NULL NULL
For any particular ScheduleDate, there can be only one WorkArea (combination of ScheduleDate and WorkArea is unique). Now, I want to check if the sum of hours of the particular employee is greater than 8 hours or not in any single date. For example, the sum of hours of employee A for the particular date (can be hour1, hour2 or anything based on employee's placement in the table) should not be more than 8 hours. How do I check that?
Please help.
Thanks
You don't really strictly need the aliases on each of the union-ed queries:
with t as (
select ScheduleDate, Workarea, Employee1 as Employee, Hours1 as Hrs union all
select ScheduleDate, Workarea, Employee2 as Employee, Hours2 as Hrs union all
...
select ScheduleDate, Employee8 as Employee, Hours8 as Hrs
)
select ScheduleDate, Employee, sum(Hrs) as [Hours]
from t
group by ScheduleDate, Employee
having sum(Hrs) > 8
Given the following data table
Id Code Date PIVOT VALUE1 VALUE2
1 WMAZ 2014-01-31 12:23:06.000 1 103 1
2 EEEE 2014-01-31 11:59:15.000 2 74 2
3 WMAZ 2014-01-31 11:59:10.000 1 3 3
4 WMAZ 2014-01-31 11:56:55.000 2 10 4
5 WMAZ 2014-01-31 11:56:14.000 2 96 5
6 EEEE 2014-01-31 11:55:26.000 2 159 6
I need to pivot the data to get this:
Code Date SUMVALUE1FORPIVOT1 SUMVALUE1FORPIVOT2 SUMVALUE2FORPIVOT1 SUMVALUE2PIVOT2
WMAZ 2014-01-31 (103+3) (10+96) (1+3) (4+5)
EEEE 2014-01-31 NULL (74+159) NULL (6+2)
How do I get a sum for value1 and value2 for each pivot grouped by Code and Date without writing a sub-query for each field?
You could do something like this:
Test data
DECLARE #tbl TABLE(Id INT,Code VARCHAR(100),
Date DATETIME,
[PIVOT] INT,
VALUE1 INT,
VALUE2 INT)
INSERT INTO #tbl
VALUES
(1,'WMAZ','2014-01-31 12:23:06.000',1,103,1),
(2,'EEEE','2014-01-31 11:59:15.000',2,74,2),
(3,'WMAZ','2014-01-31 11:59:10.000',1,3,3),
(4,'WMAZ','2014-01-31 11:56:55.000',2,10,4),
(5,'WMAZ','2014-01-31 11:56:14.000',2,96,5),
(6,'EEEE','2014-01-31 11:55:26.000',2,159,6)
Query
SELECT
tbl.Code,
CAST(tbl.Date AS DATE) AS Date,
SUM(CASE WHEN [PIVOT]=1 THEN VALUE1 ELSE NULL END) AS SUMVALUE1FORPIVOT1,
SUM(CASE WHEN [PIVOT]=2 THEN VALUE1 ELSE NULL END) AS SUMVALUE1FORPIVOT2,
SUM(CASE WHEN [PIVOT]=1 THEN VALUE2 ELSE NULL END) AS SUMVALUE2FORPIVOT1,
SUM(CASE WHEN [PIVOT]=2 THEN VALUE2 ELSE NULL END) AS SUMVALUE2FORPIVOT2
FROM
#tbl AS tbl
GROUP BY
tbl.Code,
CAST(tbl.Date AS DATE)
ORDER BY
tbl.Code DESC
Result:
Code Date SUMVALUE1FORPIVOT1 SUMVALUE1FORPIVOT2 SUMVALUE2FORPIVOT1 SUMVALUE2PIVOT2
---------------------------------------------------------------------------------------------------
WMAZ 2014-01-31 106 106 4 9
EEEE 2014-01-31 NULL 233 NULL 8
I know I already asked a similar question, but here I start from zero... Without giving any query I tried so I don't influance you.
If this is my table:
status PGID nvarchar5 nvarchar10 CatId tp_ID isActive
IT NULL Information technology NULL 1 1 1
HR NULL Human Recource NULL 1 2 1
FIN NULL Finance NULL 1 3 1
New 1 NULL 1354 2 10001 1
New 1 NULL 464 2 10002 1
New 1 NULL 13465 2 10003 1
Active 1 NULL 79846 2 10004 1
Deleted 1 NULL 132465 2 10005 1
New 2 NULL 79847 2 10006 1
New 2 NULL 341 2 10007 1
Deleted 2 NULL 465 2 10008 1
Deleted 2 NULL 132 2 10009 1
Deleted 2 NULL 465 2 10010 1
Deleted 2 NULL 1 2 10011 1
New 3 NULL 465 2 10012 1
New 3 NULL 1465 2 10013 1
New 3 NULL 132 2 10014 1
NULL NULL NULL NULL 3 20136 1
NULL NULL NULL NULL 4 22165 1
NULL NULL NULL NULL 3 24566 1
NULL NULL NULL NULL 10 24566 1
What should be the query if I want a result like this:
status PGID nvarchar5 total
new 1 Information technology 3
active 1 Information technology 1
deleted 1 Information technology 1
new 2 Human Recource 2
active 2 Human Recource 0
deleted 2 Human Recource 4
new 3 Finance 3
active 3 Finance 0
deleted 3 Finance 0
Or is this not possible?
Edit:
If you want to see what I've tried: wrong number in count()
Update:
How I calculate my total:
I won't bother going into how bad the data looks, column names etc and will presume it's a case of 'this is what I have to work with'. So given the data try
SELECT t.Status, t.Department_ID, t.Department, COALESCE(s.Total, 0) AS Total
FROM (
SELECT nvarchar5 AS Department, tp_ID AS Department_ID, Status
FROM My_Table,
(SELECT 'new' AS Status
UNION ALL
SELECT 'active' AS Status
UNION ALL
SELECT 'deleted' AS Status ) AS m
WHERE tp_ID IN (1,2,3)
) AS t
LEFT JOIN (
SELECT status AS Status, PGID AS Department_ID, COUNT(1) AS Total
FROM My_Table
WHERE PGID IS NOT NULL
GROUP BY Status, PGID
) AS s
ON t.Status = s.Status
AND t.Department_ID = s.Department_ID
That is not the answer, but I think this can help you too:
declare #t as table (
num varchar(50) null,
t varchar(50) null,
ord int not null,
pgid int null
)
insert into #t values ('IT', 'Info', 1 , null)
insert into #t values ('HR' , 'Human', 2 , null)
insert into #t values ('FIN', 'Finance', 3 , null )
insert into #t values ('New' , NULL, 10001, 1 )
insert into #t values ('New' , NULL, 10002, 1 )
insert into #t values ('New' , NULL, 10003, 1 )
insert into #t values ('Active' , NULL, 10004, 1 )
insert into #t values ('Deleted', NULL, 10005, 1 )
insert into #t values ('New' , NULL, 10006, 2 )
insert into #t values ('New' , NULL, 10007, 2 )
insert into #t values ('Deleted', NULL, 10008, 2 )
insert into #t values ('Deleted', NULL, 10009, 2 )
insert into #t values ('Deleted', NULL, 10010, 2 )
insert into #t values ('Deleted', NULL, 10011, 2 )
insert into #t values ('New' , NULL, 10012, 3 )
insert into #t values ('New' , NULL, 10013, 3 )
insert into #t values ('New' , NULL, 10014, 3 )
select num, pgid, case when pgid = 1 then 'Information technology'
when pgid = 2 then 'Human Recource'
else 'Finance' end,count(num)
from #t
where num in (
'New' ,
'Active' ,
'Deleted')
group by num, pgid
SELECT case when pgid = 1 then 'Information technology'
when pgid = 2 then 'Human Recource'
else 'Finance' end,
[New] ,
[Active] ,
[Deleted]
FROM
(SELECT num, pgid
FROM #t
where num in (
'New' ,
'Active' ,
'Deleted')) AS SourceTable
PIVOT
(
count(num)
FOR num IN ([New],
[Active] ,
[Deleted])
) AS PivotTable;
//try this query it will help you to solve this problem
select t.satus,t.PGID,u.nvarchar5,t.total from
(select status,PGID,count(*) as total from USRData group by status,PGID where PGID is not null) t
inner join USRData u on(t.PGID=u.tp_ID) where u.PGID is null and status is not null;