SQL Function for updating column with values - sql

Those who have helped me before, i tend to use SAS9.4 a lot for my day to day work, however there are times when i need to use SQL Server
There is a output table i have with 2 variables (attached output.csv)
output table
ID, GROUP, DATE
The table has 830 rows:
330 have a "C" group
150 have a "A" group
50 have a "B" group
the remaining 300 have group as "TEMP"
within SQL i do not now how to programatically work out the total volume of A+B+C. The aim is to update "TEMP" column to ensure there is an Equal amount of "A" and "B" totalling 250 of each (the remainder of the total count)
so the table totals
330 have a "C" group
250 have a "A" group
250 have a "B" group

You want to proportion the "temp" to get equal amounts of "A" and "B".
So, the idea is to count up everything in A, B, and Temp and divide by 2. That is the final group size. Then you can use arithmetic to allocate the rows in Temp to the two groups:
select t.*,
(case when seqnum + a_cnt <= final_group_size then 'A' else 'B' end) as allocated_group
from (select t.*, row_number() over (order by newid()) as seqnum
from t
where group = 'Temp'
) t cross join
(select (cnt_a + cnt_b + cnt_temp) / 2 as final_group_size,
g.*
from (select sum(case when group = 'A' then 1 else 0 end) as cnt_a,
sum(case when group = 'B' then 1 else 0 end) as cnt_b,
sum(case when group = 'Temp' then 1 else 0 end) as cnt_temp
from t
) g
) g
SQL Server makes it easy to put this into an update:
with toupdate as (
select t.*,
(case when seqnum + a_cnt <= final_group_size then 'A' else 'B' end) as allocated_group
from (select t.*, row_number() over (order by newid()) as seqnum
from t
where group = 'Temp'
) t cross join
(select (cnt_a + cnt_b + cnt_temp) / 2 as final_group_size,
g.*
from (select sum(case when group = 'A' then 1 else 0 end) as cnt_a,
sum(case when group = 'B' then 1 else 0 end) as cnt_b,
sum(case when group = 'Temp' then 1 else 0 end) as cnt_temp
from t
) g
) g
)
update toupdate
set group = allocated_group;

I'd go with a top 250 update style approach
update top (250) [TableName] set Group = 'A' where exists (Select * from [TableName] t2 where t2.id = [TableName].id order by newid()) and Group = 'Temp'
update top (250) [TableName] set Group = 'B' where exists (Select * from [TableName] t2 where t2.id = [TableName].id order by newid()) and Group = 'Temp'

Related

Oracle Specific Sorting

I have the following problem:
I need to sort some products where one needs to be a specific row and others to be random.
So if I have products: A B C D, I need for example B to be the third product while others can be random like:
C 1
A 2
B 3
D 4
Best shot I have tried is (3 is a dynamic value):
SELECT
product_name,
CASE
WHEN product = 'B' THEN 3
ELSE ( CASE WHEN rownum < 3 THEN rownum ELSE rownum + 1 END )
END sorting
FROM
products
ORDER BY
sorting ASC;
but I'm not always getting the desired outcome.
Any help or lead is appreciated.
This is rather tricky, but you can use row_number() and a bunch of arithmetic:
select p.*
from (select p.*,
row_number() over (order by case when product = 'B' then 2 else 1 end),
dbms_random.value
) as seqnum
from products p
) p
order by (case when seqnum < 3 then seqnum end),
(case when product = 'B' then 1 else 2 end),
seqnum;
The logic is:
Enumerate the values randomly, with the special value going last.
Put in the rows with lower values.
Put in the row with the special value.
Put in the rest of the rows.
The above uses a subquery because the randomness is enforced. You can do this without a subquery as:
order by (case when row_number() over (order by (case when product = 'B' then 2 else 1 end) < 3
then dbms_random.value
else 2 -- bigger than value
end),
(case when product = 'B' then 1 else 2 end),
dbms_random.value;

SQL count and combine

I'm setting up a query to change the data of a form, count data and format it. At this moment I've got a table with vertical data. The data is shown in the image below.
What I want to do is to create Group by on Number, after that count how many times a specific TypePak there is and split it to the right. As shown in the image on the right.
I've tried to do Pivot and it helped for a part of it, but that's not a good method. Then i've tried XML Path.
PIVOT
FROM dbo.des_ombouw
GROUP BY Number, typePak) src
pivot
(
max(Expr1)
for typePak in ([COLLI],[DOOS],[pallet],[Envelop])
) piv1
XML Path
select distinct Number, abc = STUFF((
select ',' + TypePak
from des_ombouw t1
where t1.Number = t2.Number
FOR XML PATH ('')),1,1,'')
from des_ombouw t2
In the image is what I want. There are more columns that has to be added, like weight of some package.
One of the problems too is that there are coming more columns, so this is not all!
Two steps of aggregation with row_number() may do what you want:
select d.number,
max(case when seqnum = 1 then cnt end) as cnt_1,
max(case when seqnum = 1 then typepak end) as typepak_1,
max(case when seqnum = 2 then cnt end) as cnt_2,
max(case when seqnum = 2 then typepak end) as typepak_2,
max(case when seqnum = 3 then cnt end) as cnt_3,
max(case when seqnum = 3 then typepak end) as typepak_3,
max(case when seqnum = 4 then cnt end) as cnt_4,
max(case when seqnum = 4 then typepak end) as typepak_4
from (select d.number, d.typepak, count(*) as cnt,
row_number() over (partition by d.number order by count(*) desc) as seqnum
from dbo.des_ombouw d
) d
group by d.number

Divide N rows to N columns

I have a result set which provide me 2 columns named Sequence and CorrectAns and it contains N rows(100 rows right now to be specific).
Now what I want is to divide these 100 rows to N columns(right now into 4 columns).
So, how to do that? Any help would be appreciated.
This is the result that i am getting. Now what I want is something like this:
Seq ColA Seq ColB Seq ColC Seq ColD
1 C 4 A 7 C 10 D
2 A 5 C 8 A 11 C
3 A 6 A 9 C 12 A
and so on.
Hope this helps
What you want is to pivot your data. Aside from the PIVOT command, one way to do that is to use conditional aggregation:
SQL Fiddle
;WITH Cte AS(
SELECT *,
grp = (ROW_NUMBER() OVER(ORDER BY Seq) -1) %
(SELECT CEILING(COUNT(*) / (4 * 1.0)) FROM tbl)
FROM tbl
),
CteFinal AS(
SELECT *,
rn = ROW_NUMBER() OVER(PARTITION BY grp ORDER BY Seq)
FROM Cte
)
SELECT
SeqA = MAX(CASE WHEN rn = 1 THEN Seq END),
ColA = MAX(CASE WHEN rn = 1 THEN CorrectAns END),
SeqB = MAX(CASE WHEN rn = 2 THEN Seq END),
ColB = MAX(CASE WHEN rn = 2 THEN CorrectAns END),
SeqC = MAX(CASE WHEN rn = 3 THEN Seq END),
ColC = MAX(CASE WHEN rn = 3 THEN CorrectAns END),
SeqD = MAX(CASE WHEN rn = 4 THEN Seq END),
ColD = MAX(CASE WHEN rn = 4 THEN CorrectAns END)
FROM CteFinal
GROUP BY grp
use the following query,
SELECT *
FROM (
SELECT
Seq, CorrectAns,
gro
FROM your_table
) as t
PIVOT
(
SUM(gro)
FOR CorrectAns IN (A,B,C,D....)
)AS pvt
If i understood what you said clearly, you can put your result set in an Array.
and then use this SQL QUERY
ALTER TABLE table_name
ADD column_name datatype
Example :
ALTER TABLE customer ADD name VARCHAR(Max);
You can specified a loop from the 0 to size of the Array and put that query inside the loop.
Edit :
Ok, I think it should be something like this;
DECLARE #cnt INT = 0;
DECLARE #str = "";
WHILE #cnt < 100000
BEGIN
SET #cnt = #cnt + 1;
SET #str='yourRowName';
ALTER TABLE yourTableName
ADD str datatype
END;
Take a look at this.
http://www.techonthenet.com/sql_server/loops/for_loop.php

SQL Server duplicate row

I have a table with duplicate records. I want to mark whether the record is a duplicate or not in a another column, let's say a column name Flag. If the records is a duplicate mark it as 1 in Flag column else 0.
How to do this?
I can use a query to select duplicate records.
select
o.clientid, oc.dupeCount, o.pannodesc, o.CustNo
from
CustomerMaster1 o
inner join
(SELECT clientid, COUNT(*) AS dupeCount
FROM CustomerMaster1
WHERE ISNULL(PanNoDesc, '') <> ''
GROUP BY clientid
HAVING COUNT(*) > 1) oc ON o.clientid = oc.clientid
Simply saying, if there are two similar records, mark 1 against the second duplicated row, if three similar records mark 1 against two rows, leaving the original record as 0.
Just use count(*) as a window function to calculate the flag:
select o.clientid, oc.dupeCount, o.pannodesc, o.CustNo,
(case when count(*) over (partition by clientId) > 1
then 1 else 0
end) as IsDuplicate
from CustomerMaster1 o;
If you only case about certain records, then you can count them instead:
select o.clientid, oc.dupeCount, o.pannodesc, o.CustNo,
(case when sum(case when PanNoDesc <> '' or PanNoDesc is not null
then 1 else 0
end) over (partition by clientId) > 1
then 1 else 0
end) as IsDuplicate
from CustomerMaster1 o;
EDIT:
If you want to modify the data, assuming you have a flag, you can just use these statements as a CTE:
with toupdate as (
select o.clientid, oc.dupeCount, o.pannodesc, o.CustNo,
(case when sum(case when PanNoDesc <> '' or PanNoDesc is not null
then 1 else 0
end) over (partition by clientId) > 1
then 1 else 0
end) as NewIsDuplicate
from CustomerMaster1 o
)
update toupdate
set Flag = NewIsDuplicate;
You can write as
CREATE TABLE CustomerMaster1 (clientid INT,PanNoDesc VARCHAR(10),DupFlag bit)
INSERT INTO CustomerMaster1 VALUES(1,'A',NULL ),(1,'B',NULL )
SELECT clientid,PanNoDesc,DupFlag FROM CustomerMaster1
;WITH CTE AS(
SELECT clientid,
ROW_NUMBER()OVER (PARTITION BY clientid ORDER BY clientid ASC) AS rownum
FROM CustomerMaster1
WHERE ISNULL(PanNoDesc, '') <> ''
)
UPDATE T
SET T.DupFlag = (case WHEN rownum > 1 THEN 1 ELSE 0 END)
FROM CustomerMaster1 T
JOIN CTE ON CTE.clientid = T.clientid
SELECT clientid,PanNoDesc,DupFlag FROM CustomerMaster1
demo
Edit: Demo based on sample fields provided:
http://sqlfiddle.com/#!3/4592f/1

Joining two different queries under one answer

I have two different queries that have produced the correct result, but I would like to have them produce the answer out in one table. How do I do that?
Here is my code:
SELECT count(distinct ID) as NoOfEmployees
FROM Table_Name
WHERE date<= '2012-05-31';
select count(subA.ID) as EmployeesChanged from (
SELECT A.ID
FROM Table_Name A
WHERE A.date < '2012-06-01'
GROUP BY 1
HAVING COUNT(A.Service_type) > 1 ) subA
Currently I have the following output:
Number of Employees
x
Employees Changed
x
How do I make it
Number of Employees | Employees Changed | (Number of employees - number changed)
x | x | x
I don't know what database do you use. But for some databases you can try:
select q1.Value, q2.Value, q1.Value - q2.Value from
(SELECT count(distinct ID) as Value FROM Table_Name
WHERE date<= '2012-05-31') q1,
(select count(subA.ID) as Value from
( SELECT A.ID FROM Table_Name A
WHERE A.date < '2012-06-01' GROUP BY 1
HAVING COUNT(A.Service_type) > 1 ) subA) q2
If date<= '2012-05-31' is the same as A.date < '2012-06-01' ?
SELECT COUNT(1) AS NoOfEmployees,
SUM(CASE WHEN STCount > 0 then 1 else 0 end) as HasChange,
SUM(CASE WHEN STCount = 0 then 1 else 0 end) as NoChange
FROM
(SELECT ID,
COUNT(A.Service_type) STCount
FROM Table_Name
WHERE date<= '2012-05-31'
GROUP BY ID) AS Data
You can use CROSS JOIN:
SELECT a.*, b.*, a.NoOfEmployees - b.EmployeesChanged
FROM
(
SELECT count(distinct ID) as NoOfEmployees
FROM Table_Name
WHERE date<= '2012-05-31'
) a
CROSS JOIN
(
SELECT count(subA.ID) as EmployeesChanged
FROM
(
SELECT A.ID
FROM Table_Name A
WHERE A.date < '2012-06-01'
GROUP BY 1
HAVING COUNT(A.Service_type) > 1
) subA
) b
Edit:
You might be able to greatly optimize your query by using conditional aggregation instead of executing two separate queries:
SELECT a.NoOfEmployees, a.EmployeesChanged, a.NoOfEmployees - a.EmployeesChanged
FROM
(
SELECT
COUNT(DISTINCT CASE WHEN date <= '2012-05-31' THEN ID END) as NoOfEmployees,
COUNT(DISTINCT CASE WHEN date < '2012-06-01' AND COUNT(Service_type) > 1 THEN ID END) AS EmployeesChanged
FROM Table_Name
GROUP BY ID
) a