Related
I have the following data:
CREATE TABLE SampleData
(
orderid int,
[name] nvarchar(1),
[date] date
);
INSERT INTO SampleData
VALUES
(1, 'a', '2017-01-01'),
(2, 'a', '2017-01-05'),
(3, 'a', '2017-02-01'),
(4, 'a', '2017-04-01'),
(5, 'a', '2017-10-01'),
(6, 'b', '2017-04-01');
I need to retrieve each new order according to the following rules:
The first date for a name is the 'current order' for that name
Orders with the same name, but less than 3 months difference with the 'current order' is considered the same order and needs to be ignored
3 months or more difference with the 'current' order is considered a new order and is now the 'current order' (in the SampleData orderid 1 and 4 need to be compared instead of 3 and 4, because 3 is not the current order)
If the name and date are the same, then the row with the lowest orderid is the superior order
So with the sample data I need the following result:
id name, date
1 a 2017-01-01
4 a 2017-04-01
5 a 2017-10-01
6 b 2017-04-01
I tried several approaches, but without success. Any idea's on how I can achieve this?
Below is a quick fix solution that can be built upon if your code scales beyond the sample data provided. I will state beforehand that this isn't the prettiest solution but it does return the result set you indicated you were after.
If anything, you may want to consider looking into T-SQL Window Functions as well as Analytic Functions. I will advice that that they don't play well with all datatypes.
My goal with the solution below was to rank the rows while partitioning by name and order by the date field. Thus you have something similar to your order id but the rank is specific to the customer who placed the order.
I'll do my best to answer any questions:
if object_id('tempdb..#tmp_SampleData','u') is not null
drop table #tmp_SampleData
CREATE TABLE #tmp_SampleData
(
orderid int,
[name] nvarchar(1),
[date] date
);
INSERT INTO #tmp_SampleData
VALUES
(1, 'a', '2017-01-01'),
(2, 'a', '2017-01-05'),
(3, 'a', '2017-02-01'),
(4, 'a', '2017-04-01'),
(5, 'a', '2017-10-01'),
(6, 'b', '2017-04-01');
if object_id('tempdb..#tmp_iter','u') is not null
drop table #tmp_iter
select
orderid
,name
,date
,rank() over (partition by name order by date) [Rank]
,lag(orderid,1,0) over (partition by name order by date) [LagRank]
--,rank() over (partition by name order by date desc) [ReverseRank]
into #tmp_Iter
from #tmp_SampleData
if object_id('tempdb..#tmp_final','u') is not null
drop table #tmp_final
select
i.orderid
,i.name
,i.date
,datediff(month,i.date,i2.date) [MonthsPassed]
into #tmp_final
from #tmp_Iter i
left join #tmp_Iter i2
on i.Rank = i2.LagRank
select *
from #tmp_final
where 1=1
and MonthsPassed > 3
or MonthsPassed = 0
or MonthsPassed < 0
or MonthsPassed is null
#SQLUser44, thanks for your input. Unfortunately your code is not working. The result for the table below should be orderid's 1,6,7,8 and 9. Yours results in 1,2,3,5,6,7,8 and 9.
INSERT INTO #tmp_SampleData
VALUES
(1,'a','2017-01-01'),
(2,'a','2017-01-08'),
(3,'a','2017-05-01'),
(4,'a','2017-01-05'),
(5,'a','2017-02-01'),
(6,'b','2017-01-01'),
(7,'b','2017-09-01'),
(8,'c','2017-10-01'),
(9,'a','2017-04-01');
I came up with the following that works, but I think it will lack performance...
if object_id('tempdb..#tmp_SampleData','u') is not null
drop table #tmp_SampleData
CREATE TABLE #tmp_SampleData
(
orderid int,
[name] nvarchar(1),
[date] date
);
INSERT INTO #tmp_SampleData
VALUES
(1,'a','2017-01-01'),
(2,'a','2017-01-08'),
(3,'a','2017-05-01'),
(4,'a','2017-01-05'),
(5,'a','2017-02-01'),
(6,'b','2017-01-01'),
(7,'b','2017-09-01'),
(8,'c','2017-10-01'),
(9,'a','2017-04-01');
DECLARE Test_Cursor CURSOR FOR
SELECT * FROM #tmp_SampleData ORDER BY [name], [date];
OPEN Test_Cursor;
DECLARE #orderid int;
DECLARE #name nvarchar(255);
DECLARE #date date;
FETCH NEXT FROM Test_Cursor INTO #orderid, #name, #date;
DECLARE #current_date date = #date;
DECLARE #current_name nvarchar(255) = #name;
DECLARE #listOfIDs TABLE (orderid int);
INSERT #listOfIDs values(#orderid);
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#name = #current_name AND DATEDIFF(MONTH, #current_date, #date) >= 3)
BEGIN
SET #current_date = #date
INSERT #listOfIDs values(#orderid)
END
IF(#name != #current_name)
BEGIN
SET #current_name = #name
SET #current_date = #date
INSERT #listOfIDs values(#orderid)
END
FETCH NEXT FROM Test_Cursor INTO #orderid, #name, #date;
END;
CLOSE Test_Cursor;
DEALLOCATE Test_Cursor;
SELECT * FROM #tmp_SampleData WHERE orderid IN (SELECT orderid FROM #listOfIDs);
Better performing alternatives are very welcome!
SELECT
dbo.PaymentMaster.Code,
dbo.PaymentMaster.OrderNo,
dbo.PaymentMaster.CustIssueNo,
dbo.PaymentMaster.Amount,
dbo.CustomerMaster.CustName,
dbo.OrderRequestMaster.ORequestQty,
InvoiceMaster.SGST,
InvoiceMaster.CGST,
InvoiceMaster.SGST,
InvoiceMaster.SubTotal,
InvoiceMaster.TotalAmount
FROM
dbo.CustomerIssueMaster,
dbo.PaymentMaster,
dbo.CustomerMaster,
dbo.OrderMaster,
dbo.OrderRequestMaster,
InvoiceMaster
WHERE (dbo.PaymentMaster.CustIssueNo = dbo.CustomerIssueMaster.CustIssueNo
OR OrderMaster.OrderNo = PaymentMaster.OrderNo)
AND OrderRequestMaster.ORequestNo = OrderMaster.ORequestNo
I want to create invoice where all conditions should be match but I didn't get any row when there is one row which should be given as result.
I got this result from payment master. and I want customer info, order request info , order info and payment info in invoice master with invoice master field i.e Code, InvoiceNo, CustNo, InvoiceDate, SubTotal, CGST, SGST, TotalAmount.
Code PaymentNo OrderNo CustIssueNo PaymentMode PaymentStatus Amount PaymentDate InvoiceNo InvoiceDate
4 KCS[P][00004] NULL KCS[CI][00001] Cash Paid 500 14:28.3 NULL NULL
5 KCS[P][00005] KCS[O][00001] NULL Cash Paid 2000 22:33.9 NULL NULL
6 KCS[P][00006] KCS[O][00002] NULL Cash Paid 2000 40:38.1 NULL NULL
I got this results
Without any data we are going to be very hard pressed to help. You say one row should meet criteria, but it is very hard to see how only one record would be returned unless your tables have only one record!
I note from your query that you want CustName, without including CustomerMaster in your WHERE clause - this will lead to trouble!
Please use the following code in a query window and add any missing fields that are important. Then create some INSERT lines to populate. Re-post this so that we can help you with a query that does what you want:
declare #CustomerIssueMaster table
(
CustIssueNo int
)
declare #PaymentMaster table
(
Code int,
PaymentNo varchar(50),
OrderNo varchar(50),
CustIssueNo varchar(50),
PaymentMode varchar(50),
PaymentStatus varchar(50),
Amount decimal (18,2),
PaymentDate datetime,
InvoiceNo int,
InvoiceDate datetime
)
declare #CustomerMaster table
(
CustName varchar(50)
)
declare #OrderRequestMaster table
(
ORequestQty int,
ORequestNo int
)
declare #InvoiceMaster table
(
SGST decimal(18,2),
CGST decimal(18,2),
SubTotal decimal(18,2),
TotalAmount decimal(18,2)
)
declare #OrderMaster table
(
OrderNo int
)
INSERT INTO #PaymentMaster VALUES(4, 'KCS[P][00004]', NULL, 'KCS[CI][00001]','Cash','Paid',500,'2014-03-28', NULL, NULL)
INSERT INTO #PaymentMaster VALUES(5, 'KCS[P][00005]', 'KCS[O][00001]', NULL, 'Cash','Paid',2000,'2014-03-28', NULL, NULL)
INSERT INTO #PaymentMaster VALUES(6, 'KCS[P][00006]', 'KCS[O][00002]', NULL, 'Cash','Paid',2000,'2014-03-28', NULL, NULL)
SELECT * FROM #PaymentMaster
EDIT
Please note I have amended the structure of #PaymentMaster and given three INSERTs to mimic the data you show from SELECT * FROM PaymentMaster, with one exception: I do not understand what format your PaymentDate is supposed to be. The first two looked like times, but the third cannot be. I just stuck in a dummy date instead.
Now will you please do the same for the other tables? I have shown you the format you need to use.
I have been trying to Write a Stored Procedure where i can perform UpSert using Merge with the Following Condition
If Record is Present then change EndDate of Target to Yesterday's day i.e., Present Day - 1
If Record is not Present then Insert New Record
Here is the Table tblEmployee i used in SP
CREATE TABLE tblEmployee
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
Here is my SP which Takes UDTT as Input parameter
CREATE PROCEDURE [dbo].[usp_UpsertEmployees]
#typeEmployee typeEmployee READONLY -- It has same column like tblEmployye except EmployeeID
AS
BEGIN
SET NOCOUNT ON;
MERGE INTO tblEmployee AS TARGET
USING #typeEmployee AS SOURCE
ON TARGET.Name = SOURCE.Name
WHEN MATCHED and TARGET.StartDate < SOURCE.StartDate
THEN
--First Update Existing Record EndDate to Previous Date as shown below
UPDATE
set TARGET.EndDate = DATEADD(day, -1, convert(date, SOURCE.StartDate))
-- Now Insert New Record
--INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
WHEN NOT MATCHED by TARGET
THEN
INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
SET NOCOUNT OFF;
END
How can i perform both Updating Existing Record and Adding New Record When Column is matched
Can Please someone Explain me the Execution Flow of Merge in TSQL i.e.,
WHEN MATCHED --Will this Execute Everytime
WHEN NOT MATCHED by TARGET -- Will this Execute Everytime
WHEN NOT MATCHED by SOURCE -- Will this Execute Everytime
Will all above 3 condition get executed everytime in Merge or only Matching condition is executed Everytime
Thanks in Advance
This isn't what MERGE is meant to do (update and insert in same clause). To accomplish this, you can use the OUTPUT clause to get all the updated records only. The MERGE/OUTPUT combo is very picky. Your OUTPUT updates are really the TARGET records that got updated, so you have to start the TARGET records in a temp/table variable. Then you match those back against the SOURCE to do the INSERT. You won't be allowed to join the output results directly back to source or even use as a correlated subquery within the WHERE.
Setup some sample data
The code below just sets up some sample data.
-- Setup sample data
DECLARE #typeEmployee TABLE (
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
DECLARE #tblEmployee TABLE (
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
INSERT #tblEmployee VALUES ('Emp A', '1/1/2016', '2/1/2016')
INSERT #typeEmployee VALUES ('Emp A', '1/5/2016', '2/2/2016'), ('Emp B', '3/1/2016', '4/1/2016')
Updates to Stored Procedure
You can use OUTPUT at the end of a MERGE to have it return the modified records of the target records, and by including $action, you will also get whether it was an insert, update, or delete.
However, the result set from MERGE / OUTPUT cannot be directly joined against the SOURCE table so you can do your INSERT since you only get the TARGET records back. You can't use the results of the OUTPUT within correlated sub-query from the SOURCE table either. Easiest thing is to use a temp table or table variable to capture the output.
-- Logic to do upsert
DECLARE #Updates TABLE (
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
INSERT #Updates
SELECT
Name,
StartDate,
EndDate
FROM (
MERGE INTO #tblEmployee AS TARGET
USING #typeEmployee AS SOURCE
ON TARGET.Name = SOURCE.Name
WHEN MATCHED AND TARGET.StartDate < SOURCE.StartDate
THEN
--First Update Existing Record EndDate to Previous Date as shown below
UPDATE SET
EndDate = DATEADD(DAY, -1, CONVERT(DATE, SOURCE.StartDate))
WHEN NOT MATCHED BY TARGET -- OR MATCHED AND TARGET.StartDate >= SOURCE.StartDate -- Handle this case?
THEN
INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate)
OUTPUT $action, INSERTED.Name, INSERTED.StartDate, INSERTED.EndDate
-- Use the MERGE to return all changed records of target table
) AllChanges (ActionType, Name, StartDate, EndDate)
WHERE AllChanges.ActionType = 'UPDATE' -- Only get records that were updated
Now that you've captured the output of the MERGE and filtered to only get updated TARGET records, you can then do your outstanding INSERT by filtering only the SOURCE records that were part of the MERGE update.
INSERT #tblEmployee
SELECT
SOURCE.Name,
SOURCE.StartDate,
SOURCE.EndDate
FROM #typeEmployee SOURCE
WHERE EXISTS (
SELECT *
FROM #Updates Updates
WHERE Updates.Name = SOURCE.Name
-- Other join conditions to ensure 1:1 match against SOURCE (start date?)
)
Ouput
This is the output of the sample records after the change. Your intended TARGET changes were made.
-- Show output
SELECT * FROM #tblEmployee
Following the idea from the accepted answer, this works as well in Sql server 2008 r2:
create table Test1 (
Id int, FromDate date, ThruDate date, Value int
)
insert into dbo.Test1
(Id, FromDate, ThruDate, [Value])
select
t.Id, t.FromDate, T.ThruDate, t.Value * 100
from (
MERGE dbo.Test1 AS Target
USING (
select 1 as Id, '2000-01-01' as FromDate, '2000-12-31' as ThruDate, 2 as Value
) AS Source
ON ( target.id = source.Id
)
WHEN MATCHED
THEN
UPDATE SET Target.[Id] = Source.[Id]
, Target.[FromDate] = Source.[FromDate]
, Target.[ThruDate] = Source.[ThruDate]
, Target.[Value] = Source.[Value]
WHEN NOT MATCHED BY TARGET
THEN
INSERT ([Id]
, [FromDate]
, [ThruDate]
, [Value])
VALUES (Source.[Id]
, Source.[FromDate]
, Source.[ThruDate]
, Source.[Value])
OUTPUT $ACTION as Act, Inserted.*
) t
where t.Act = 'Update'
You can play with different values.
I have an order table that has both past membership data and current data. I want to view this data in single row. I have a temp table for past data, but not exactly sure how to write this query to get current data in the same row. I know it has something to do with the MAX(order no). Here is the query to get the past membership data in a temp table
set transaction isolation level read uncommitted
declare
#ship_master_customer_id varchar (10), #cycle_begin_date datetime, #cycle_end_date datetime, #OrderNo varchar(10), #Description Char(100)
create table #t1(ShipMasterCustomerID varchar(10), OrderNo varchar (10), cycle_begin_date datetime, cycle_end_date datetime, Description Char(100))
Insert into #t1
Select SHIP_MASTER_CUSTOMER_ID, ORDER_NO, CYCLE_BEGIN_DATE,CYCLE_END_DATE, DESCRIPTION FROM [ORDER_DETAIL]
where SHIP_MASTER_CUSTOMER_ID = '11115555' and
CYCLE_END_DATE = '2/29/2016'
Select * from #t1
Drop table #t1
Here is my script.
declare
#ship_master_customer_id varchar (10), #cycle_begin_date datetime, #cycle_end_date datetime, #OrderNo varchar(10), #Description Char(100)
create table #t2(ShipMasterCustomerID varchar(10), OrderNo varchar (10), cycle_begin_date datetime, cycle_end_date datetime, Description Char(100))
Insert into #t2 (shipmastercustomerid, orderno, cycle_begin_date, cycle_end_date, DESCRIPTION)
VALUES (1111555,9004731815, 2015/01/01, 2015/31/12,'Annual Mem'),
(1111555, 9005148308, 2016/01/01, 2016/31/12,'Annual Mem'),
(1111222, 9005027152, 2015/01/03, 2016/29/02,'Annual Mem'),
(1111222, 9005440369, 2016/01/03, 2017/31/03,'Annual Mem'),
(2223333, 9005027152, 2014/01/01, 2016/31/12,'Annual Mem'),
(2223333, 9005442116, 2016/01/01, 2017/31/12,'Annual Mem')
Select * from #t2
Drop table #t2
Sample Data
You don't need a temp table. You can query the same table twice, giving it an alias and then use the alias to prefix your column names. Since you didn't give us a complete schema or a fiddle I'm simulating your database with a temp table but the essence is here. There are considerations that you didn't mention, though. Are you guaranteed that every customer will have both a historical AND a current record? If not, they will not appear in the query below because of the INNER JOIN. You could change it to an OUTER join but when customers don't have a new record you will see NULL values in those columns. My point is that here be dragons... this is by no means a complete or bulletproof solution, only a nudge in the right direction.
DECLARE #ORDER_DETAIL AS TABLE(
ShipMasterCustomerId varchar(20),
OrderNo varchar(20),
cycle_begin_date date,
cycle_end_date date,
Description varchar(100)
)
INSERT #ORDER_DETAIL SELECT '11115555', '9005337015', '02/26/15', '2/29/16', 'Membership 26-Feb-2015 to 29-Feb-2016'
INSERT #ORDER_DETAIL SELECT '11115555', '9005743023', '02/28/17', '2/28/17', 'Membership 01-Mar-2016 to 28-Feb-2017'
SELECT
hist.ShipMasterCustomerId,
hist.OrderNo,
hist.cycle_begin_date,
hist.CYCLE_END_DATE,
hist.[Description],
curr.ShipMasterCustomerId,
curr.OrderNo,
curr.cycle_begin_date,
curr.CYCLE_END_DATE,
curr.[Description]
FROM
#ORDER_DETAIL AS hist
INNER JOIN #ORDER_DETAIL AS curr ON (
(curr.ShipMasterCustomerId = hist.ShipMasterCustomerId) AND (curr.cycle_end_date =
(SELECT MAX(cycle_end_date) FROM #ORDER_DETAIL WHERE ShipMasterCustomerId = hist.ShipMasterCustomerId))
)
WHERE
(hist.ShipMasterCustomerId = '11115555')
AND
(hist.cycle_end_date = '2/29/2016')
I have been searching but still not get the best way to do this
I have a table with sex column (bit) so the value will be 1/0/null
My stored procedure with parameter #sex
SELECT *
FROM [dbo].[data]
WHERE sex = ISNULL(#sex, sex)
When I'm send #sex null, the query just give me all data with sex field true and false but not null
Can someone give me a way to get all value ? true,false,null ?
I'm trying using case but not work to, help me
My stored procedure from the last comment
select
[idUser],
[sex]
FROM
[dbo].[data]
WHERE
(#sex IS NULL AND sex is null)
OR (#sex IS NOT NULL AND sex = #sex)
Try this.. assign #sex with 0,1 or null
CREATE TABLE #TEMP(ID INT, SEX BIT)
INSERT INTO #TEMP VALUES(1,1)
INSERT INTO #TEMP VALUES(2,1)
INSERT INTO #TEMP VALUES(3,0)
INSERT INTO #TEMP VALUES(4,0)
INSERT INTO #TEMP VALUES(5,NULL)
DECLARE #sex BIT
SET #sex=NULL
SELECT * FROM #TEMP WHERE #sex IS NULL or sex=#sex
DROP TABLE #TEMP