how to make your data horizontal - sql-server

i have 2 identical data in 2 row and i intend to make this data become 1 row. for example i have this data sample
Name Status Bank
Thung Active ABC Bank
Thung Hold ABC Bank
can i make something like this
Name Status 1 Bank 1 Status 2 Bank 2
Thung Active ABC Bank Hold ABC Bank
sorry i cant explain it properly

SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table MyTable(Name varchar(max),BStatus varchar(max),Bank varchar(max))
insert into MyTable (Name,BStatus,Bank)values('Thung','Active', 'ABC Bank')
insert into MyTable (Name,BStatus,Bank)values('Thung','Hold', 'ABC Bank')
Query 1:
with CTE AS (select *,
(CASE WHEN BStatus='Active' THEN BStatus END) AS Status1,
(CASE WHEN BStatus = 'Hold' THEN BStatus END) AS Status2,
(CASE WHEN Bank='ABC Bank' THEN Bank END) AS Bank1,
(CASE WHEN Bank='ABC Bank' THEN Bank END) AS Bank2,
ROW_NUMBER() OVER (PARTITION BY BStatus,Bank Order By Name) as rn
from MyTable
group by Name,BStatus,Bank )
select c.Name
,max(c.Status1) AS Status1
,max(c.Status2) AS Status2
,max(c.Bank1) AS Bank1
,max(c.Bank2) AS Bank2
from cte c
where rn=1
group by c.Name,c.Bank
Results:
| Name | Status1 | Status2 | Bank1 | Bank2 |
|-------|---------|---------|----------|----------|
| Thung | Active | Hold | ABC Bank | ABC Bank |

Related

Update Column Data of Table Aliases According to other Column's Data

I have a store Procedure as Following
BEGIN
;with Data as(
select
E.Id,
E.FirstName as [Employee],
E.IsDeleted AS [status],
Co.Name as [Company],
S.SalaryType,
S.Date as [Date],
E.Desc as Notes,
CASE #SortField
WHEN 'Id' THEN ROW_NUMBER() OVER (ORDER BY E.Id)
WHEN 'date' THEN ROW_NUMBER() OVER (ORDER BY S.Date)
END rn
From Employee E
Inner Join Company Co on E.CompId = Co.Id
Inner Join Salary S on E.Salarytype = S.Id
Where
E.Name Like '%'+#EmployeeName+'%'
-- Other AND Conditions --
)
select *,(Select Count(1) From data) FilteredCount -- can i Update it here ??--
FROM data
ORDER BY CASE WHEN #SortDir = 'ASC' THEN rn else -rn END
OFFSET #StartIndex ROWS
FETCH NEXT #PageSize ROWS ONLY
END
It of course Gives more columns in result but to keep question short i kept only two columns.
+------+-------+
| Name | Status|
+------+-------+
| John | 1 |
| Mark | 1 |
| Sami | 0 |
+------+-------+
So now I just want some changes in my table aliases so I can update result SET Name = 'Deleted' Where I am getting Status as 0
+--------+-------+
| Name | Status|
+--------+-------+
| John | 1 |
| Mark | 1 |
| Deleted| 0 |
+------+---------+
Is this possible to update Table aliases an perform Order by Like Operations on it ?
You don't need a CTE to UPDATE the table, or even get the result set you require, but you can OUTPUT the column of a table you UPDATE at the same time you UPDATE it. Normally for the above, however, you would only UPDATE the rows you want to UPDATE, rather than them all, though that doesn't mean you can't:
UPDATE dbo.Employee
SET [Name] = CASE Status WHEN 1 THEN [Name] ELSE 'Deleted' END
OUTPUT inserted.[Name],
inserted.Status;

Find biggest order by client in Microsoft SQL Server

I am looking for some elegant solution to find preferred channel by client.
As an input we get list of transactions, which contains clientid, date, invoice_id, channel and amount. For every client we need to find preferred channel based on amount.
In case some specific client has 2 channels - outcome should be RANDOM among those channels.
Input data:
Clients ID | Date | Invoice Id | Channel | Amount
-----------+------------+------------+---------+--------
Client #1 | 01-01-2020 | 0000000001 | Retail | 90
Client #1 | 07-01-2020 | 0000000002 | Website | 180
Client #2 | 08-01-2020 | 0000000003 | Retail | 70
Client #2 | 09-01-2020 | 0000000004 | Website | 70
Client #3 | 10-01-2020 | 0000000005 | Retail | 140
Client #4 | 11-01-2020 | 0000000006 | Retail | 70
Client #4 | 13-01-2020 | 0000000007 | Website | 30
Desired output:
Clients ID | Top-Channel
-----------+-----------------
Client #1 | Website >> website 180 > retail 90
Client #2 | Retail >> random choice from Retail and Website
Client #3 | Retail >> retail 140 > website 0
Client #4 | Retail >> retail 70 > website 30
Usually to solve such tasks I do some manipulations with GROUP BY, add a random number which is less than 1, and many other tricks. But most probably, there is a better solution.
This is for Microsoft SQL Server
If you have the totals, then you can use window functions:
select t.*
from (select t.*,
row_number() over (partition by client_id order by amount desc) as seqnum
from t
) t
where seqnum = 1;
If you need to aggregate to get the totals, the same approach works with aggregation:
select t.*
from (select t.client_id, t.channel, sum(amount) as total_amount,
row_number() over (partition by client_id order by sum(amount) desc) as seqnum
from t
group by t.client_id, t.channel
) t
where seqnum = 1;
So keeping your desired output in mind, I wrote the following T-SQL without using group by
declare #clients_id int =1
declare #clients_id_max int = (select max(clients_id) from random)
declare #tab1 table (clients_id int, [Top-Channel] nvarchar(10), amount int)
declare #tab2 table (clients_id int, remarks nvarchar (100))
while #clients_id <= #clients_id_max
begin
if ((select count(*) from random where clients_id =#clients_id) > 1)
begin
insert into #tab2 select top 1 a.clients_id, a.channel +' '+ cast (a.amount as nvarchar(5)) +' ; '+ b.channel +' '+ cast (b.amount as nvarchar(5)) as remarks
from random a, random b where a.clients_id =#clients_id and a.clients_id = b.clients_id and a.channel <> b.channel
order by a.amount desc
end
else
begin
insert into #tab2 select a.clients_id, a.channel +' '+ cast (a.amount as nvarchar(5)) as remarks
from random a, random b where a.clients_id =#clients_id and a.clients_id = b.clients_id
order by a.amount desc
end
insert into #tab1 select top 1 clients_id, Channel as [Top-Channel], amount from random where clients_id = #clients_id order by amount desc
set #clients_id = #clients_id +1
end
select a.clients_id, a.[Top-Channel], b.Remarks from #tab1 a join #tab2 b on a.clients_id = b.clients_id
[Query Output
: https://i.stack.imgur.com/7RzcV.jpg ]
This will work:
select distinct ID,FIRST_VALUE(Channel) over (partition by ID order by amount desc,NEWID())
from Table1

SQL Server - assign value to a field based on a running total

For a customer, I'm sending through an XML file to another system, the sales orders and I sum the quantities for each item across all sales orders lines (e.g.: if I have "ItemA" in 10 sales orders with different quantities in each one, I sum the quantity and send the total).
In return, I get a response whether the requested quantities can be delivered to the customers or not. If not, I still get the total quantity that can be delivered. However, could be situations when I request 100 pieces of "ItemA" and I cannot deliver all 100, but 98. In cases like this, I need to distribute (to UPDATE a custom field) those 98 pieces FIFO, according to the requested quantity in each sales order and based on the registration date of each sales order.
I tried to use a WHILE LOOP but I couldn't achieve the desired result. Here's my piece of code:
DECLARE #PickedQty int
DECLARE #PickedERPQty int
DECLARE #OrderedERPQty int=2
SET #PickedQty =
WHILE (#PickedQty>0)
BEGIN
SET #PickedERPQty=(SELECT CASE WHEN #PickedQty>#OrderedERPQty THEN #OrderedERPQty ELSE #PickedQty END)
SET #PickedQty=#PickedQty-#PickedERPQty
PRINT #PickedQty
IF #PickedQty>=0
BEGIN
UPDATE OrderLines
SET UDFValue2=#PickedERPQty
WHERE fDocID='82DADC71-6706-44C7-9B78-7FCB55D94A69'
END
IF #PickedQty <= 0
BREAK;
END
GO
Example of response
I requested 35 pieces but only 30 pieces are available to be delivered. I need to distribute those 30 pieces for each sales order, based on requested quantity and also FIFO, based on the date of the order. So, in this example, I will update the RealQty column with the requested quantity (because I have stock) and in the last one, I assign the remaining 5 pieces.
ord_Code CustOrderCode Date ItemCode ReqQty AvailQty RealQty
----------------------------------------------------------------------------
141389 CV/2539 2018-11-25 PX085 10 30 10
141389 CV/2550 2018-11-26 PX085 5 30 5
141389 CV/2563 2018-11-27 PX085 10 30 10
141389 CV/2564 2018-11-28 PX085 10 30 5
Could anyone give me a hint? Thanks
This might be more verbose than it needs to be, but I'll leave it to you to skinny it down if that's possible.
Set up the data:
DECLARE #OrderLines TABLE(
ord_Code INTEGER NOT NULL
,CustOrderCode VARCHAR(7) NOT NULL
,[Date] DATE NOT NULL
,ItemCode VARCHAR(5) NOT NULL
,ReqQty INTEGER NOT NULL
,AvailQty INTEGER NOT NULL
,RealQty INTEGER NOT NULL
);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2539','2018-11-25','PX085',10,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2550','2018-11-26','PX085', 5,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2563','2018-11-27','PX085',10,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2564','2018-11-28','PX085',10,0,0);
DECLARE #AvailQty INTEGER = 30;
For running totals, for SQL Server 20012 and up anyway, SUM() OVER is the preferred technique so I started off with some variants on that. This query brought in some useful numbers:
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
ol.ReqQty,
#AvailQty AS AvailQty,
SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS TotalOrderedQty,
#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS RemainingQty
FROM
#OrderLines AS ol;
Then I used the RemainingQty to do a little math. The CASE expression is hairy, but the first step checks to see if the RemainingQty after processing this row will be positive, and if it is, we fulfill the order. If not, we fulfill what we can. The nested CASE is there to stop negative numbers from coming into the result set.
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
ol.ReqQty,
#AvailQty AS AvailQty,
SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS TotalOrderedQty,
#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS RemainingQty,
CASE
WHEN (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty
ELSE
CASE
WHEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]))
ELSE 0
END
END AS RealQty
FROM
#OrderLines AS ol
Windowing functions (like SUM() OVER) can only be in SELECT and ORDER BY clauses, so I had to do a derived table with a JOIN. A CTE would work here, too, if you prefer. But I used that derived table to UPDATE the base table.
UPDATE Lines
SET
Lines.AvailQty = d.AvailQty
,Lines.RealQty = d.RealQty
FROM
#OrderLines AS Lines
JOIN
(
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
#AvailQty AS AvailQty,
CASE
WHEN (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty
ELSE
CASE
WHEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]))
ELSE 0
END
END AS RealQty
FROM
#OrderLines AS ol
) AS d
ON d.CustOrderCode = Lines.CustOrderCode
AND d.ord_Code = Lines.ord_Code
AND d.ItemCode = Lines.ItemCode
AND d.Date = Lines.Date;
SELECT * FROM #OrderLines;
Results:
+----------+---------------+---------------------+----------+--------+----------+---------+
| ord_Code | CustOrderCode | Date | ItemCode | ReqQty | AvailQty | RealQty |
+----------+---------------+---------------------+----------+--------+----------+---------+
| 141389 | CV/2539 | 25.11.2018 00:00:00 | PX085 | 10 | 30 | 10 |
| 141389 | CV/2550 | 26.11.2018 00:00:00 | PX085 | 5 | 30 | 5 |
| 141389 | CV/2563 | 27.11.2018 00:00:00 | PX085 | 10 | 30 | 10 |
| 141389 | CV/2564 | 28.11.2018 00:00:00 | PX085 | 10 | 30 | 5 |
+----------+---------------+---------------------+----------+--------+----------+---------+
Play with different available qty values here: https://rextester.com/MMFAR17436

TSQL - Return duplicate rows with highest value and longest date

I have got a list of staff who are contractors and it includes duplicates as some work on multiple contracts at the same time. I need to find the row with the most hours for that person and secondly with the end date furthest away (if the hours is the same). I guess this is the Current main contract. I also need to make sure the Date From and the Date to is in between the current date - how can this be done?
+------------+----------+------+-------+------------+------------+
| ContractID | PersonID | Name | Hours | Date From | Date To |
+------------+----------+------+-------+------------+------------+
| 8 | 1 | John | 30 | 20/02/2018 | 26/02/2018 |
| 8 | 2 | Paul | 5 | 20/02/2018 | 26/02/2018 |
| 7 | 3 | John | 7 | 20/02/2018 | 26/02/2018 |
+------------+----------+------+-------+------------+------------+
In the above example, I would need to bring back the John – 30hours and the Paul 5 Hours row. PS - The PersonID is different for each row but the "Name" is the same for the person if on multiple contracts.
Thanks
One approach is simply to use exists with appropriate ordering logic:
select c.*
from contracts c
where c.contractid = (select top 1 c2.contractid
from contracts c2
where c2.name = c.cname and
getdate() >= c2.datefrom and
getdate() < c2.dateto
order by c2.hours desc, c2.dateto desc
);
You can put similar logic into a window function:
select c.*
from (select c.*,
row_number() over (partition by c.name order by c.hours desc, c.dateto desc) as seqnum
from contracts c
where getdate() >= c.dateto and getdate() < c.datefrom
) c
where seqnum = 1;
If you need the full row, I'd do somehthing like this:
with
rankedByHours as (
select
ContractID,
PersonID,
Name,
Hours,
[Date From],
[Date To],
row_number() over (partition by PersonID order by Hours desc) as RowID
from
Contracts
)
select
ContractID,
PersonID,
Name,
Hours,
[Date From],
[Date To],
case
when getdate() between [Date From] and [Date To] then 'Current'
when getdate() < [Date From] then 'Not Started'
else 'Expired'
end as ContractStatus
from
RankedByHours
where
RowID = 1;
Use the CTE to inject a row_number() sorting all rows by your sort criteria, then select out the top one in the main body. It can be easily extended to also capture your farthest-out end date.

Optimization of SQL Server Query

My problem is that I created a query that takes too long to execute.
City | Department | Employee | Attendance Date | Attendance Status
------------------------------------------------------------------------
C1 | Dept 1 | Emp 1 | 2016-01-01 | ABSENT
C1 | Dept 1 | Emp 2 | 2016-01-01 | LATE
C1 | Dept 2 | Emp 3 | 2016-01-01 | VACANCY
So I want to create a view that contains same data and adds a column that contains the total number of employees (that serves me later in a SSRS project to determine the percentage of each status).
So I created a function that makes simple select filtering by department and date.
and this is the query that uses the function:
SELECT City, Department, Employee, [Attendence Date], [Attendance Status], [Get Department Employees By Date](Department, [Attendence Date]) AS TOTAL
FROM attendenceTable
This is the function:
CREATE FUNCTION [dbo].[Get Department Employees By Date]
(
#deptID int = null,
#date datetime = null
)
RETURNS nvarchar(max)
AS
BEGIN
declare #result int = 0;
select #result = count(*) from attendenceTable where DEPT_ID = #deptID and ATT_DATE_G = #date;
RETURN #result;
END
The problem is that query takes too long (I mean very long time) to execute.
Any Suggestion of optimization?
Your function is a scalar function, which is run once for every row in the result set (~600,000) times, and is a known performance killer. It can be rewritten into an inline table-valued function, or if the logic is not required elsewhere, a simple group, count & join would suffice:
WITH EmployeesPerDeptPerDate
AS ( SELECT DEPT_ID ,
ATT_DATE_G ,
COUNT(DISTINCT Employee) AS EmployeeCount
FROM attendenceTable
GROUP BY DEPT_ID ,
ATT_DATE_G
)
SELECT A.City ,
A.Department ,
A.Employee ,
A.[Attendence Date] ,
A.[Attendance Status] ,
ISNULL(B.EmployeeCount, 0) AS EmployeeCount
FROM attendenceTable AS A
LEFT OUTER JOIN EmployeesPerDeptPerDate AS B ON A.DEPT_ID = B.DEPT_ID
AND A.ATT_DATE_G = B.ATT_DATE_G;

Resources