SQL - Previous order date per row - sql

I have two tables:
Order (order_id, client_id, order_date)
Client (client_id)
where clients can have many orders
What I need is a previous_order(date) column per row
Example:
order_id | order_date | order_client | previous_order
-----------------------------------------------------
1 | 02/02 | 1 | null
2 | 02/03 | 1 | 02/02
3 | 02/10 | 1 | 02/03
4 | 02/10 | 1 | 02/10
I tried this command:
select a.*, b.previous_date from order a
join (select a.order_client, min(a.order_date) as previous_date from order a
group by a.order_client) b
on a.order_client = b.order_client
But using this i get the first order of each client.
I also saw lag() and lead() functions. But they are not avaiable in the microsoft sql server 10.50
Can anyone help me with this SQL command?
Thanks in advance

Perhaps this:
CREATE TABLE #Client (Client_ID INT )
CREATE TABLE #Order (Order_ID INT, Client_ID INT, Order_Date DATE)
INSERT INTO #Client
( Client_ID )
VALUES
( 1 ) -- Client_ID - int
INSERT INTO #Order
( Order_ID, Client_ID, Order_Date )
VALUES
( 1 , 1 , '20150202' ),
( 2 , 1 , '20150203' ),
( 3 , 1 , '20150210' ),
( 4 , 1 , '20150210' )
SELECT
Order_ID AS order_id
, Order_Date AS order_date
, Client_ID AS order_client
, (SELECT MAX(Ord2.Order_Date)
FROM #Order AS Ord2
WHERE
Ord2.Order_Date <= #Order.Order_Date
AND Ord2.Order_ID < #Order.Order_ID
AND Ord2.Client_ID = #Order.Client_ID) AS previous_order
FROM #Order
-- Cleanup
DROP TABLE #Client
DROP TABLE #Order

Related

How to update one row that has max value in column (SQL Server)

I am trying to update the Rows that have the Max score with the value of 'Yes' in the Text1 column.
This is straightforward except when there are multiple rows with the max score.
If they have the same score, I just want to select the top row to have the value 'Yes'. Only one row with identical Vendor IDs should have the 'Yes' value.
UPDATE Suppliers
SET Text1='Yes'
--SELECT DISTINCT *
FROM Suppliers INNER JOIN
(
SELECT Vendor, MAX(VCScore) as MaxVCScore
FROM Suppliers
GROUP BY Vendor
) maxTable
ON Suppliers.Vendor=maxTable.Vendor
AND Suppliers.VCScore=maxTable.MaxVCScore
I do not want to use TOP 1 because that will only update one row in the whole table. I instead want only one row for each Vendor to be updated. (Vendor can be identical which is what I am trying to fix.) I cannot add a Group By clause to the Update statement as I would like to group by Vendor but that is incorrect syntax.
with t as (
select * , row_number() over (partition by Vendor order by VCScore desc) rn
from Suppliers
)
update s
set Text1 = 'Yes'
from supplier s
join t on s.pkey = t.pkey and t.rn = 1
Here's one way you may about this using a Common Table Expression (CTE). The following may be run in SSMS:
DECLARE #Suppliers table ( Vendor varchar(20), VCScore int, Text1 varchar(3), SupplierPK int IDENTITY (1,1) );
INSERT INTO #Suppliers ( Vendor, VCScore ) VALUES
( 'Vendor1', 85 ), ( 'Vendor1', 85 ), ( 'Vendor1', 85 ), ( 'Vendor2', 65 ), ( 'Vendor2', 65 );
DECLARE #Vendor table ( Vendor varchar(20), VCScore int );
INSERT INTO #Vendor VALUES
( 'Vendor1', 85 ), ( 'Vendor1', 25 ), ( 'Vendor1', 45 ), ( 'Vendor2', 45 ), ( 'Vendor2', 65 );
WITH cte AS (
SELECT
Vendor,
Text1,
SupplierPK,
ROW_NUMBER() OVER ( PARTITION BY Vendor ORDER BY SupplierPK ) AS RowNo
FROM #Suppliers AS s
OUTER APPLY (
SELECT MAX ( VCScore ) AS MaxVCScore FROM #Vendor AS v WHERE v.Vendor = s.Vendor
) AS x
WHERE
s.VCScore = x.MaxVCScore
)
UPDATE cte
SET
Text1 = 'Yes'
WHERE
cte.RowNo = 1;
SELECT * FROM #Suppliers ORDER BY Vendor, SupplierPK;
Returns
+---------+---------+-------+------------+
| Vendor | VCScore | Text1 | SupplierPK |
+---------+---------+-------+------------+
| Vendor1 | 85 | Yes | 1 |
| Vendor1 | 85 | NULL | 2 |
| Vendor1 | 85 | NULL | 3 |
| Vendor2 | 65 | Yes | 4 |
| Vendor2 | 65 | NULL | 5 |
+---------+---------+-------+------------+
I am making the assumption that you have a primary key value that can be sorted in your Suppliers table.
I recommend using an updatable CTE:
with toupdate as (
select s.* ,
row_number() over (partition by Vendor order by VCScore desc) as seqnum
from Suppliers s
)
update toupdate
set Text1 = 'Yes'
where seqnum = 1;
Note that no JOIN is needed.

How to find max value from each group and display their information when using "group by"

For example, i create a table about people contribue to 2 campaigns
+-------------------------------------+
| ID Name Campaign Amount (USD) |
+-------------------------------------+
| 1 A 1 10 |
| 2 B 1 5 |
| 3 C 2 7 |
| 4 D 2 9 |
+-------------------------------------+
Task: For each campaign, find the person (Name, ID) who contribute the most to
Expected result is
+-----------------------------------------+
| Campaign Name ID |
+-----------------------------------------+
| 1 A 1 |
| 2 D 4 |
+-----------------------------------------+
I used "group by Campaign" but the result have 2 columns "Campagin" and "max value" when I need "Name" and "ID"
Thanks for your help.
Edited: I fix some values, really sorry
You can use analytic functions for this:
select name, id, amount
from (select t.*, max(amount) over (partition by campaign) as max_amount
from t
) t
where amount = max_amount;
You can also do it by giving a rank/row_number partiton by campaign and order by descending order of amount.
Query
;with cte as(
select [num] = dense_rank() over(
partition by [Campaign]
order by [Amount] desc
), *
from [your_table_name]
)
select [Campaign], [Name], [ID]
from cte
where [num] = 1;
Try the next query:-
SELECT Campaign , Name , ID
FROM (
SELECT Campaign , Name , ID , MAX (Amount)
FROM MyTable
GROUP BY Campaign , Name , ID
) temp;
Simply use Where Clause with the max of amount group by Campaign:-
As following generic code:-
select a, b , c
from tablename
where d in
(
select max(d)
from tablename
group by a
)
Demo:-
Create table #MyTable (ID int , Name char(1), Campaign int , Amount int)
go
insert into #MyTable values (1,'A',1,10)
insert into #MyTable values (2,'B',1,5)
insert into #MyTable values (3,'C',2,7)
insert into #MyTable values (4,'D',2,9)
go
select Campaign, Name , ID
from #MyTable
where Amount in
(
select max(Amount)
from #MyTable
group by Campaign
)
drop table #MyTable
Result:-
Please find the below code for the same
SELECT *
FROM #MyTable T
OUTER APPLY (
SELECT COUNT(1) record
FROM #MyTable T1
where t.Campaign = t1.Campaign
and t.amount < t1.amount
)E
where E.record = 0

T-SQL How to "Flatten" top 3 rows into a single row

I've searched for an answer to this question and found questions similar to my own, however I do not have a "ColumnHeader" column to denote which field the record should go into. Ex:
TSQL Pivot without aggregate function
trying to flatten rows into columns
Fetching Columns of a multiple rows in one row
My problem is thus - I have data in this format (selected as a top 3 result from a product recommendation query):
------------------------------
CustID | StyleNo | Brand | ID
------------------------------
1 | ABC | BrandA| 1
------------------------------
1 | DEF | BrandB| 2
------------------------------
1 | GHI | BrandC| 3
------------------------------
2 | JKL | BrandA| 4
------------------------------
2 | MNO | BrandB| 5
------------------------------
2 | PQR | BrandD| 6
------------------------------
That I'd like to make look like this:
-----------------------------------------------------------------
CustID | StyleNo1| StyleNo2| StyleNo3 | Brand1 | Brand2 | Brand3
-----------------------------------------------------------------
1 | ABC | DEF | GHI | BrandA | BrandB | BrandC
-----------------------------------------------------------------
2 | JKL | MNO | PQR | BrandA | BrandB | BrandD
-----------------------------------------------------------------
In order for my program to simply read the row of recommendations for each customer.
What I have attempted is a PIVOT - however I have nothing to really aggregate upon. I've also attempted the Min(Case...When...Then...End) as outlined in the second linked question, but as stated I don't have reference to a "Header" column.
The ID column is completely inconsequential for the time being, but it may help to solve this problem. It is NOT needed in the end result.
I am currently using SQLServer 2012
With the window function Row_Number() and a conditional aggregation
Select CustID
,StyleNo1 = max(case when RN=1 then StyleNo else null end)
,StyleNo2 = max(case when RN=2 then StyleNo else null end)
,StyleNo3 = max(case when RN=3 then StyleNo else null end)
,Brand1 = max(case when RN=1 then Brand else null end)
,Brand2 = max(case when RN=2 then Brand else null end)
,Brand3 = max(case when RN=3 then Brand else null end)
From (
Select *,RN = Row_Number() over (Partition By CustID Order by StyleNo,Brand)
From YourTable
) A
Where RN<=3
Group By CustID
Returns
What you are doing is called "pivoting" - for this you could use PIVOT. A better way IMHO is to use approach that Jeff Moden talks about in this article.
WITH idSort AS
(
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY CustID ORDER BY ID) FROM #yourTable
)
SELECT
CustID,
StyleNo1 = MAX(CASE rn WHEN 1 THEN StyleNo END),
StyleNo2 = MAX(CASE rn WHEN 2 THEN StyleNo END),
StyleNo3 = MAX(CASE rn WHEN 3 THEN StyleNo END),
Brand1 = MAX(CASE rn WHEN 1 THEN Brand END),
Brand2 = MAX(CASE rn WHEN 2 THEN Brand END),
Brand3 = MAX(CASE rn WHEN 3 THEN Brand END)
FROM idSort
GROUP BY CustID;
Other approach can be using CTE's and Cross Apply.
CREATE TABLE #UnFlattenedData
(
CustID TINYINT ,
StyleNo CHAR(3) ,
Brand CHAR(6) ,
ID TINYINT
);
INSERT INTO #UnFlattenedData
( CustID, StyleNo, Brand, ID )
VALUES ( 1, -- CustID - tinyint
'ABC', -- StyleNo - char(3)
'BrandA', -- Brand - char(6)
1 -- ID - tinyint
),
( 1, -- CustID - tinyint
'DEF', -- StyleNo - char(3)
'BrandB', -- Brand - char(6)
2 -- ID - tinyint
),
( 1, -- CustID - tinyint
'GHI', -- StyleNo - char(3)
'BrandC', -- Brand - char(6)
3 -- ID - tinyint
),
( 2, -- CustID - tinyint
'JKL', -- StyleNo - char(3)
'BrandA', -- Brand - char(6)
4 -- ID - tinyint
),
( 2, -- CustID - tinyint
'MNO', -- StyleNo - char(3)
'BrandB', -- Brand - char(6)
5 -- ID - tinyint
),
( 2, -- CustID - tinyint
'PQR', -- StyleNo - char(3)
'BrandD', -- Brand - char(6)
6 -- ID - tinyint
);
WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY u1.CustID ORDER BY u1.ID ) AS R1
FROM #UnFlattenedData AS u1
),
u1
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 1
),
u2
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 2
),
u3
AS ( SELECT C1.CustID ,
U1.StyleNo ,
U1.Brand
FROM cte AS C1
INNER JOIN #UnFlattenedData AS U1 ON U1.CustID = C1.CustID
AND U1.ID = C1.ID
WHERE C1.R1 = 3
)
SELECT u1.CustID ,
u1.StyleNo AS StyleNo1 ,
u2.StyleNo AS StyleNo2 ,
u3.StyleNo AS StyleNo3 ,
u1.Brand AS Brand1 ,
u2.Brand AS Brand2 ,
u3.Brand AS Brand3
FROM u1
CROSS APPLY ( SELECT *
FROM u2
WHERE u2.CustID = u1.CustID
) AS u2
CROSS APPLY ( SELECT *
FROM u3
WHERE u3.CustID = u1.CustID
) AS u3;

Group function is nested too deeply SQL Error

I have a table which looks like this:
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| orderNumber (PK)| int |
| orderDate | date |
| requiredDate | date |
| shippedDate | date |
| status | char(15) |
| comments | char(200) |
| customerNumber | int |
+-----------------+--------------+
I need to return the customerNumber which has maximum number of orders.
I tried the following command:
SELECT customerNumber FROM ORDERS WHERE customerNumber IN (SELECT customerNumber FROM ORDERS HAVING MAX(COUNT(customerNumber)) GROUP BY customerNumber);
I think an error: group function is nested too deeply
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE ORDERS (
orderNumber int PRIMARY KEY,
orderDate date,
requiredDate date,
shippedDate date,
status char(15),
comments char(200),
customerNumber int
);
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 1, 1 );
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 2, 1 );
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 3, 2 );
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 4, 2 );
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 5, 3 );
INSERT INTO ORDERS ( ORDERNUMBER, CUSTOMERNUMBER ) VALUES ( 6, 4 );
Query 1 - If you only want to get a single customer:
SELECT CUSTOMERNUMBER
FROM (
SELECT CUSTOMERNUMBER,
COUNT( ORDERNUMBER ) AS num_orders
FROM ORDERS
GROUP BY CUSTOMERNUMBER
ORDER BY num_orders DESC
)
WHERE ROWNUM = 1
Results:
| CUSTOMERNUMBER |
|----------------|
| 1 |
Query 2 - If you want to get all customers with the highest number of orders:
SELECT CUSTOMERNUMBER
FROM (
SELECT CUSTOMERNUMBER,
RANK() OVER ( ORDER BY NUM_ORDERS DESC ) AS RNK
FROM (
SELECT CUSTOMERNUMBER,
COUNT( ORDERNUMBER ) AS num_orders
FROM ORDERS
GROUP BY CUSTOMERNUMBER
ORDER BY num_orders DESC
)
)
WHERE RNK = 1
Results:
| CUSTOMERNUMBER |
|----------------|
| 1 |
| 2 |
One way to do it is using ctes, where you get the count of orders in the first cte, then select the maximum value. Finally join them to get the customer with the maximum orders.
with ordercount as (select customernumber, count(distinct ordernumber) ordercount
from orders
group by customernumber)
,maxorders as (select max(ordercount) maxcount from ordercount)
select o.customernumber
from ordercount o
join maxorders m on m.maxcount = o.ordercount

Select invoices based on quantity needed

I have a table that looks like this:
+---------------+---------------+------------------+--------------+
| InvoiceNumber | ProductNumber | ReceivedQuantity | ReceivedDate |
+---------------+---------------+------------------+--------------+
| INV001 | P001 | 500 | 09/01/2015 |
| INV002 | P001 | 600 | 09/02/2015 |
| INV003 | P001 | 700 | 09/03/2015 |
+---------------+---------------+------------------+--------------+
When a product is ordered. System needs to know which invoice it gets it from. First in first out.
For example I need 1000 quantity of product number P001. It should select the following invoices. It does not display the last invoice since 500 + 600 is already sufficient quantity
+---------------+---------------+------------------+--------------+
| InvoiceNumber | ProductNumber | ReceivedQuantity | ReceivedDate |
+---------------+---------------+------------------+--------------+
| INV001 | P001 | 500 | 09/01/2015 |
| INV002 | P001 | 600 | 09/02/2015 |
+---------------+---------------+------------------+--------------+
I can replicate this by making a cursor and looping through the table but looking for the best way to achieve this. Any nudge to the right direction would help a lot.
I think you can use a query like this:
;WITH t As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY ReceivedDate, InvoiceNumber) As RowNo
FROM yourTable
), firstOverflow AS (
SELECT TOP(1)
t1.RowNo
FROM t t1
LEFT JOIN
t t2 ON t1.ProductNumber = t2.ProductNumber AND t1.ReceivedDate >= t2.ReceivedDate
GROUP BY t1.RowNo, t1.InvoiceNumber, t1.ProductNumber, t1.ReceivedQuantity, t1.ReceivedDate
HAVING SUM(t2.ReceivedQuantity) >= 1000
ORDER BY SUM(t2.ReceivedQuantity) - 1000)
SELECT *
FROM t
JOIN
firstOverflow ON t.RowNo <= firstOverflow.RowNo;
A better solution is this:
DECLARE #value int = 1000;
WITH t As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY ReceivedDate, InvoiceNumber) As seq
FROM yourTable
), s As (
SELECT t.InvoiceNumber, t.ProductNumber, t.ReceivedQuantity, t.ReceivedDate, SUM(tt.ReceivedQuantity) As currentTotal
FROM t
LEFT JOIN
t tt ON t.ProductNumber = tt.ProductNumber AND t.seq >= tt.seq
GROUP BY t.InvoiceNumber, t.ProductNumber, t.ReceivedQuantity, t.ReceivedDate
), st As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY (CASE WHEN s.currentTotal > #value THEN -currentTotal ELSE Null END) DESC) As seq
FROM s)
SELECT st.InvoiceNumber, st.ProductNumber, st.ReceivedQuantity, st.ReceivedDate
FROM st
WHERE currentTotal < #value
UNION ALL
SELECT st.InvoiceNumber, st.ProductNumber, st.ReceivedQuantity, st.ReceivedDate
FROM st
WHERE currentTotal >= #value AND st.seq = 1;
Try this query and give some feedback:
DECLARE #table TABLE (InvoiceNumber nvarchar(100),
ProductNumber nvarchar(100),
ReceivedQuantity int)
INSERT INTO #table VALUES ('inv001', 'p001', 500)
INSERT INTO #table VALUES ('inv002', 'p001', 600)
INSERT INTO #table VALUES ('inv003', 'p001', 600)
INSERT INTO #table VALUES ('inv004', 'p001', 600)
SQL 2012:
SELECT v.* FROM
(
SELECT t.*,
SUM(ReceivedQuantity) OVER (PARTITION BY ProductNumber ORDER BY InvoiceNumber) AS sum
FROM #table t
) v
WHERE sum <= 1000
SQL 2008:
SELECT v.* FROM
(
SELECT
a.InvoiceNumber
, a.ProductNumber
, SUM(b.ReceivedQuantity) AS sum
FROM
#table a
INNER JOIN #table b
ON a.InvoiceNumber >= b.InvoiceNumber AND a.ProductNumber = b.ProductNumber
GROUP BY
a.InvoiceNumber
, a.ProductNumber
) v
WHERE sum <= 1000