Search child rows for values - sql-server

I have something like this:
Transaction Customer
1 Cust1
2 Cust2
3 Cust3
4 Cust4
TransID Code
2 A
2 B
2 D
3 A
4 B
4 C
If I want to be able to do something like "IF Customer 'Cust1' Has code 'A'", how should I best build a view? I want to end up being able to query something like "Select Customer from View where Code in [Some list of codes]" OR "Select Cust1 from View Having Codes in [Some list of codes]"
While I can do something like
Customer | Codes
Cust1 | A, B, D
Etc.
SELECT Transaction from Tbl where Codes like 'A'
This seems to me to be an impractical way to do it.

Here's how I'd do it
;with xact_cust (xact, cust) as
(
select 1, 'cust1' union all
select 2, 'cust2' union all
select 3, 'cust3' union all
select 4, 'cust4'
), xact_code (xact, code) as
(
select 2, 'A' union all
select 2, 'B' union all
select 2, 'D' union all
select 3, 'A' union all
select 4, 'B' union all
select 4, 'C'
)
select Cust, Code
from xact_cust cust
inner join xact_code code
on cust.xact = code.xact
where exists (select 1
from xact_code i
where i.xact = code.xact
and i.code = 'A')
If you NEED the codes serialized into a delimited list, take a look at this article: What this query does to create comma delimited list SQL Server?

Here's another option...
IF OBJECT_ID('tempdb..#CustomerTransaction', 'U') IS NOT NULL
DROP TABLE #CustomerTransaction;
CREATE TABLE #CustomerTransaction (
TransactionID INT NOT NULL PRIMARY KEY,
Customer CHAR(5) NOT NULL
);
INSERT #CustomerTransaction (TransactionID, Customer) VALUES
(1, 'Cust1'), (2, 'Cust2'), (3, 'Cust3'),
(4, 'Cust4'), (5, 'Cust5');
IF OBJECT_ID('tempdb..#TransactionCode', 'U') IS NOT NULL
DROP TABLE #TransactionCode;
CREATE TABLE #TransactionCode (
TransactionID INT NOT NULL,
Code CHAR(1) NOT NULL
);
INSERT #TransactionCode (TransactionID, Code) VALUES
(2, 'A'), (2, 'B'), (2, 'D'), (3, 'A'), (4, 'B'), (4, 'C');
--SELECT * FROM #CustomerTransaction ct;
--SELECT * FROM #TransactionCode tc;
--=============================================================
SELECT
ct.TransactionID,
ct.Customer,
CodeList = STUFF(tcx.CodeList, 1, 1, '')
FROM
#CustomerTransaction ct
CROSS APPLY (
SELECT
', ' + tc.Code
FROM
#TransactionCode tc
WHERE
ct.TransactionID = tc.TransactionID
ORDER BY
tc.Code ASC
FOR XML PATH('')
) tcx (CodeList);
Results...
TransactionID Customer CodeList
------------- -------- -----------
1 Cust1 NULL
2 Cust2 A, B, D
3 Cust3 A
4 Cust4 B, C
5 Cust5 NULL

Related

Selecting the smallest value in one column per group

I have a table that looks like the following which was created using the following code...
SELECT Orders.ID, Orders.CHECKIN_DT_TM, Orders.CATALOG_TYPE,
Orders.ORDER_STATUS, Orders.ORDERED_DT_TM, Orders.COMPLETED_DT_TM,
Min(DateDiff("n",Orders.ORDERED_DT_TM,Orders.COMPLETED_DT_TM)) AS
Time_to_complete
FROM Orders
GROUP BY Orders.ORDER_ID, Orders.ID,
Orders.CHECKIN_DT_TM, Orders.CATALOG_TYPE, Orders.ORDERED_DT_TM,
Orders.COMPLETED_DT_TM, HAVING (((Orders.CATALOG_TYPE)="radiology");
ID Time_to_complete ... .....
1 5
1 7
1 8
2 23
2 6
3 7
4 16
4 14
I'd like to add to this code which would select the smallest Time_to_complete value per subject ID. Leaving the desired table:
ID Time_to_complete ... .....
1 5
2 6
3 7
4 14
I'm using Access and prefer to continue using Access to finish this code but I do have the option to use SQL Server if this is not possible in Access. Thanks!
I suspect you need correlated subquery :
SELECT O.*, DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) AS Time_to_complete
FROM Orders O
WHERE DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) = (SELECT Min(DateDiff("n", O1.ORDERED_DT_TM, O1.COMPLETED_DT_TM))
FROM Orders O1
WHERE O1.ORDER_ID = O.ORDER_ID AND . . .
);
EDIT : If you want unique records then you can do instead :
SELECT O.*, DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) AS Time_to_complete
FROM Orders O
WHERE o.pk = (SELECT TOP (1) o1.pk
FROM Orders O1
WHERE O1.ORDER_ID = O.ORDER_ID AND . . .
ORDER BY DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) ASC
);
pk is your identity column that specifies unique entry in Orders table, so you can change it accordingly.
Have a look at this:
DECLARE #myTable AS TABLE (ID INT, Time_to_complete INT);
INSERT INTO #myTable
VALUES (1, 5)
, (1, 7)
, (1, 8)
, (2, 23)
, (2, 6)
, (3, 7)
, (4, 16)
, (4, 14);
WITH cte AS
(SELECT *
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Time_to_complete) AS RN
FROM #myTable)
SELECT cte.ID
, cte.Time_to_complete
FROM cte
WHERE RN = 1;
Results :
ID Time_to_complete
----------- ----------------
1 5
2 6
3 7
4 14
It uses row numbers over groups, then selects the first row for each group. You should be able to adjust your code to use this technique. If in doubt wrap your entire query in a cte first then apply the technique here.
It's worth becoming familiar with this process as it gets used in a lot of places - especially around de-duping data.
Try This
DECLARE #myTable AS TABLE (ID INT, Time_to_complete INT);
INSERT INTO #myTable
VALUES (1, 5)
, (1, 7)
, (1, 8)
, (2, 23)
, (2, 6)
, (3, 7)
, (4, 16)
, (4, 14);
SELECT O.ID, O.Time_to_complete
FROM #myTable O
WHERE o.Time_to_complete = (Select min(m.Time_to_complete) FROM #myTable m
Where o.id=m.ID
);
Result :
ID Time_to_complete
1 5
2 6
3 7
4 14

Total Number of Leaves of same type in a month

I have 2 tables name EmployeeInfo and Leave and I am storing the values that which employee have taken which type of leave in month and how many times.
I am trying to calculate the number of leaves of same type but I'm stuck at one point for long time.
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('Leave'))
BEGIN;
DROP TABLE [Leave];
END;
GO
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('EmployeeInfo'))
BEGIN;
DROP TABLE [EmployeeInfo];
END;
GO
CREATE TABLE [EmployeeInfo] (
[EmpID] INT NOT NULL PRIMARY KEY,
[EmployeeName] VARCHAR(255)
);
CREATE TABLE [Leave] (
[LeaveID] INT NOT NULL PRIMARY KEY,
[LeaveType] VARCHAR(255) NULL,
[DateFrom] VARCHAR(255),
[DateTo] VARCHAR(255),
[Approved] Binary,
[EmpID] INT FOREIGN KEY REFERENCES EmployeeInfo(EmpID)
);
GO
INSERT INTO EmployeeInfo([EmpID], [EmployeeName]) VALUES
(1, 'Marcia'),
(2, 'Lacey'),
(3, 'Fay'),
(4, 'Mohammad'),
(5, 'Mike')
INSERT INTO Leave([LeaveID],[LeaveType],[DateFrom],[DateTo], [Approved], [EmpID]) VALUES
(1, 'Annual Leave','2018-01-08 04:52:03','2018-01-10 20:30:53', 1, 1),
(2, 'Sick Leave','2018-02-10 03:34:41','2018-02-14 04:52:14', 0, 2),
(3, 'Casual Leave','2018-01-04 11:06:18','2018-01-05 04:11:00', 1, 3),
(4, 'Annual Leave','2018-01-17 17:09:34','2018-01-21 14:30:44', 0, 4),
(5, 'Casual Leave','2018-01-09 23:31:16','2018-01-12 15:11:17', 1, 3),
(6, 'Annual Leave','2018-02-16 18:01:03','2018-02-19 17:16:04', 1, 2)
My query which I have tried so far look something like this.
SELECT Info.EmployeeName, Leave.LeaveType, SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves], DatePart(MONTH, Leave.DateFrom)
FROM EmployeeInfo Info, Leave
WHERE Info.EmpID = Leave.EmpID AND Approved = 1
GROUP BY Info.EmployeeName, Leave.LeaveType, [Leave].[DateFrom], [Leave].[DateTo]
And the record like given below
EmployeeName LeaveType #OfLeaves MonthNumber
-------------- ----------------- ----------- -----------
Fay Casual Leave 1 1
Fay Casual Leave 3 1
Lacey Annual Leave 3 2
Marcia Annual Leave 2 1
I want the record to look like this
EmployeeName LeaveType #OfLeaves MonthNumber
-------------- ----------------- ----------- -----------
Fay Casual Leave 4 1
Lacey Annual Leave 3 2
Marcia Annual Leave 2 1
If you don't want to modify existing query due to some constraint, this might work:
Select iq.EmployeeName, iq.LeaveType, SUM(iq.#OfLeaves) as #OfLeaves, iq.MonthNumber
From (
SELECT Info.EmployeeName, Leave.LeaveType, SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves], DatePart(MONTH, Leave.DateFrom) as MonthNumber
FROM EmployeeInfo Info, Leave
WHERE Info.EmpID = Leave.EmpID AND Approved = 1
GROUP BY Info.EmployeeName, Leave.LeaveType, [Leave].[DateFrom], [Leave].[DateTo]
)iq
group by iq.EmployeeName, iq.LeaveType, iq.MonthNumber
This just need small adjustment with your query in the GROUP BY clause. Instead of grouping them by [Leave].[DateFrom] and [Leave].[DateTo] which causes the row to be separated, you need to group it with the calculated column that uses datepart.
SELECT Info.EmployeeName,
Leave.LeaveType,
SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves],
DatePart(MONTH, Leave.DateFrom)
FROM EmployeeInfo Info
INNER JOIN Leave
ON Info.EmpID = Leave.EmpID
WHERE Approved = 1
GROUP BY Info.EmployeeName,
Leave.LeaveType,
DatePart(MONTH, Leave.DateFrom) -- <<<< change only this part
Here's a Demo.
I have also modified the syntax into ANSI format.

Consolidate rows in one table based on duplicates in another table

I have one table table1 that might look like this, where there could be duplicates on 2 fields Cnumber and Dob and then unique pkSID:
pkSID Cnumber Dob
1 12345 01/02/2002
2 12345 01/02/2002
3 12345 01/02/2002
4 12345 01/02/2002
5 12345 01/02/2002
There can be multiple occurrences of this in table1. I then have another table that references the pkSID, and I want to consolidate those rows so they all only reference one of the pkSID in table1, so table2 will look like this initially:
pkSTID fkSID OtherVal1 OtherVal2
1 1 s x
2 2 t f
3 3 a d
4 4 v g
5 5 b z
And then after the consolidation:
pkSTID fkSID OtherVal1 OtherVal2
1 1 s x
2 1 t f
3 1 a d
4 1 v g
5 1 b z
How can I find those rows in table1 and then consolidate in table2?
Try this:
Note: I'm considering pkSID is in continuation for relative Cnumber and Dob
CREATE TABLE #TABLE1(pkSID INT,Cnumber INT,Dob DATE)
INSERT INTO #TABLE1
SELECT 1, 12345, '01/02/2002' UNION ALL
SELECT 2, 12345, '01/02/2002' UNION ALL
SELECT 3, 12345, '01/02/2002' UNION ALL
SELECT 4, 12345, '01/02/2002' UNION ALL
SELECT 5, 12345, '01/02/2002'
CREATE TABLE #TABLE2(pkSTID INT,fkSID INT,OtherVal1 VARCHAR(10),OtherVal2 VARCHAR(10))
INSERT INTO #TABLE2
SELECT 1, 1, 's', 'x' UNION ALL
SELECT 2, 2, 't', 'f' UNION ALL
SELECT 3, 3, 'a', 'd' UNION ALL
SELECT 4, 4, 'v', 'g' UNION ALL
SELECT 5, 5, 'b', 'z'
SELECT T2.pkSTID, T3.min_pkSID fkSID, T2.OtherVal1 , T2.OtherVal2
FROM #TABLE2 T2
INNER JOIN
(
SELECT MIN(T1.pkSID) min_pkSID, MAX(T1.pkSID) max_pkSID FROM #TABLE1 T1 GROUP BY T1.Cnumber, T1.Dob --T1 ON T2.fkSID = T1.pkSID
)T3
ON T2.fkSID BETWEEN T3.min_pkSID AND T3.max_pkSID

SQL Server: Selecting using count and Case

Hello a client of ours has requested a "count(values) as NAME"
now on it's own that would be nice and simple but what they are asking for is
3 seperate columns where the defining factor is an "ID"
with a count for values for instance:
Count(CC where CC in ('A','B','C')
adding up to something like this
select case((select count(CC where CC in ('A','B','C') AB,
count(CC where CC in ('D','E') DE and id = 1234),
case((select count(CC where CC in ('Z','X','Y') XY,
count(CC where CC in ('W','G') WG and id = 1235)
Can anyone think of anyway to make this possible?
Sample Data
ID, CC
1234 A
1234 B
1234 C
1234 A
1235 B
1235 B
1234 A
1235 C
1234 A
1234 B
1235 C
1234 A
1234 B
1235 B
1234 A
1235 C
Expected Output
CC ID:1234 ID:1235
A 6 0
B 3 3
C 1 1
( sorry to say Dynamic SQL is out the window on this one, the software the client uses to pull the information does not allow for temporary tables, updates, inserts or deletes)
Use dynamic SQL:
CREATE TABLE TestTbl (ID INT, CC VARCHAR(10));
INSERT INTO TestTbl VALUES
(1234,'A')
,(1234,'B')
,(1234,'C')
,(1234,'A')
,(1235,'B')
,(1235,'B')
,(1234,'A')
,(1235,'C')
,(1234,'A')
,(1234,'B')
,(1235,'C')
,(1234,'A')
,(1234,'B')
,(1235,'B')
,(1234,'A')
,(1235,'C');
DECLARE #PivotColumns VARCHAR(MAX)=STUFF(
(
SELECT DISTINCT ',[ID:' + CAST(ID AS VARCHAR(MAX)) + ']'
FROM TestTbl
FOR XML PATH('')
),1,1,'');
DECLARE #SqlCmd VARCHAR(MAX)=
'
SELECT p.*
FROM
(
SELECT DISTINCT CC
,COUNT(CC) OVER(PARTITION BY ID,CC) AS CountCC
,''ID:'' + CAST(ID AS VARCHAR(10)) AS ColumnName
FROM TestTbl AS tbl
) AS x
PIVOT
(
MIN(CountCC) FOR ColumnName IN(' + #PivotColumns + ')
) AS p
';
EXEC(#SqlCmd);
DROP TABLE TestTbl;
The Result:
CC ID:1234 ID:1235
A 6 NULL
B 3 3
C 1 3
I suspect I may have misunderstood your requirement, so please accept my apologies if this isn't the answer you are looking for for.
Is this just a case of conditionally counting rows, based on a combination of CC and ID?
Example
DECLARE #Sample TABLE
(
ID INT,
CC VARCHAR(1)
)
;
INSERT INTO #Sample
(
ID,
CC
)
VALUES
(1234, 'A'),
(1234, 'B'),
(1234, 'C'),
(1234, 'A'),
(1235, 'B'),
(1235, 'B'),
(1234, 'A'),
(1235, 'C'),
(1234, 'A'),
(1234, 'B'),
(1235, 'C'),
(1234, 'A'),
(1234, 'B'),
(1235, 'B'),
(1234, 'A'),
(1235, 'C')
;
SELECT
CC,
SUM(CASE WHEN ID = 1234 THEN 1 ELSE 0 END) AS [ID:1234],
SUM(CASE WHEN ID = 1235 THEN 1 ELSE 0 END) AS [ID:1235]
FROM
#Sample
GROUP BY
CC
;
Returned
CC ID:1234 ID:1235
A 6 0
B 3 3
C 1 3
The last record contains a different value in ID:1235 to your example. If you can explain why this should 1 I'll take another look at my query.

Compare the most recent row with the immediate previous in the same table

I am facing this problem where I need to compare the most recent row with the immediate previous one based on the same criteria (it will be trader in this case).
Here is my table:
ID Trader Price
-----------------
1 abc 5
2 xyz 5.2
3 abc 5.7
4 xyz 5
5 abc 5.2
6 abc 6
Here is the script
CREATE TABLE Sale
(
ID int not null PRIMARY KEY ,
trader varchar(10) NOT NULL,
price decimal(2,1),
)
INSERT INTO Sale (ID,trader, price)
VALUES (1, 'abc', 5), (2, 'xyz', 5.2),
(3, 'abc', 5.7), (4, 'xyz', 5),
(5, 'abc', 5.2), (6, 'abc', 6);
So far I am working with this solution that is not perfect yet
select
a.trader,
(a.price - b.price ) New_price
from
sale a
join
sale b on a.trader = b.trader and a.id > b.ID
left outer join
sale c on a.trader = c.trader and a.id > c.ID and b.id < c.ID
where
c.ID is null
Above is not perfect because I want to compare only the most recent with the immediate previous on... In this sample for example
Trader abc : I will compare only id = 6 and id = 5
Trader xyz : id = 4 and id = 2
Thanks for any help!
If you are using SQL Server 2012 or later, you can use functions LEAD and LAG to join previous and next data. Unfortunately these function can only be used in SELECT or ORDER BY clause, so you will need to use subquery to get the data you need:
SELECT t.trader, t.current_price - t.previous_price as difference
FROM (
SELECT
a.trader,
a.price as current_price,
LAG(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as previous_price,
LEAD(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as next_price
FROM sale a
) t
WHERE t.next_price IS NULL
Here in your subquery you create additional columns for previous and next value. Then in your main query you filter only these rows where next price is NULL - that indicates this is the last row for the specific trader.

Resources