SQL count and combine - sql

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

Related

SQL Function for updating column with values

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'

sql transformation - rows to column

i have been trying to solve this one image
my initial idea is like this
select name,
CASE
when count(name) = 1 then get first distinct value
when count(name) = 2 then get first distinct value
else get first distinct value
END as val1,
CASE
when count(name) = 1 then null
when count(name) = 2 then get second distinct value
else get second distinct value
END as val2,
CASE
when count(name) = 1 then null
when count(name) = 2 then null
else get third distinct value
END as val3
into desired_table
from source_table
group by name
is my attempt feasible? if so, how do i access the first, second and third distinct values?
use pivot . Your output table was incorrect. The correct form is available in db<>fiddle.
select name,x as value1,y as value2,z as value3
from
(
select *
from t1
) as SourceTable
pivot
(
max(value) for value in(x,y,z)
) as PivotTable
demo in db<>fiddle
You can use conditional aggregation along with row_number():
select name,
max(case when seqnum = 1 then value end) as value_1,
max(case when seqnum = 2 then value end) as value_2,
max(case when seqnum = 3 then value end) as value_3
into desired_table
from (select s.*,
row_number() over (partition by name order by value) as seqnum
from source_table s
) s
group by name;

Keep the order of a multi string field using the pivot function?

I have a field "Remarks" that can have up to 10 codes pipe delimited. An example of the field results are below. I almost have the issue solved by using the Pivot function as shown below. The problem I cant seem to figure out is how to keep them in the correct order as in the remarks field. In the remark below 1|120|482|10 it returns in the rows in order 1 10 120 482 instead of 1 120 482 10. Any ideas?
SELECT
,Remarks
,[1] AS Remark4
,[2] AS Remark5
,[3] AS Remark6
,[4] AS Remark7
,[5] AS Remark8
,[6] AS Remark9
FROM (SELECT
,Remarks
,value
,dense_Rank() OVER(PARTITION BY Remarks ORDER BY value) as testnbr
from incident
CROSS APPLY STRING_SPLIT(Remarks, '|') AS BK
) as srctbl
PIVOT(
max(VALUE)
FOR testnbr IN([1],[2],[3],[4],[5],[6])
) as PVT
Remarks
1|120|170
1|120|375
1|120|482|10
I would just use conditional aggregation instead. But, the problem is that string_split() does not guarantee the ordering of the records. So, you can emulate this assuming there are no duplicate remarks :
select i.remarks, r.*
from incident i cross apply
(select max(case when seqnum = 1 then value end) as remark_1,
max(case when seqnum = 2 then value end) as remark_2,
max(case when seqnum = 3 then value end) as remark_3,
max(case when seqnum = 4 then value end) as remark_4,
max(case when seqnum = 5 then value end) as remark_5,
max(case when seqnum = 6 then value end) as remark_6
from (select value,
row_number() over (order by charindex('|' + value + '|', '|' + i.Remarks + '|') as seqnum
from string_split(i.Remarks, '|') s
) r

SQL query to return history as flat format

Looking for help with an SQL query to turn a history table into flat file format with up to 5 instances of results on table B. I have only shown 2 instances in the results. For a bonus point can these be sorted by EFF_DATE ascending?!
My query so far is
SELECT a.REFNO, a.M_NAME, b.EFF_DATE, b.VAL
FROM TABLEA a INNER JOIN TABLEB b ON (a.REFNO=b.REFNO)
WHERE a.REFNO = '1'
This is fine for returning results once per row, but how do I modify so up to 5 EFF_DATE and VAL instances are repeated on one row. The dates can be any date and ideally would like them sorted ascending left to right. Only those rows on TABLEB where Val > 0 should be included.
If you know the number of columns you want in the history, then you can use conditional aggregation or pivot. The challenge is not having a column for the pivot.
You can easily generate one, though, using ROW_NUMBER():
SELECT a.REFNO, a.M_NAME,
MAX(CASE WHEN seqnum = 1 THEN b.EFF_DATE END) as EFF_DATE_1,
MAX(CASE WHEN seqnum = 1 THEN b.VAL END) as VAL_1,
MAX(CASE WHEN seqnum = 2 THEN b.EFF_DATE END) as EFF_DATE_2,
MAX(CASE WHEN seqnum = 2 THEN b.VAL END) as VAL_2,
MAX(CASE WHEN seqnum = 3 THEN b.EFF_DATE END) as EFF_DATE_3,
MAX(CASE WHEN seqnum = 3 THEN b.VAL END) as VAL_3
FROM TABLEA a INNER JOIN
(SELECT b.*,
ROW_NUMBER() OVER (PARTITION BY REFNO ORDER BY EFF_DATE) as seqnum
FROM TABLEB b
) b
ON a.REFNO = b.REFNO
WHERE a.REFNO = '1'
GROUP BY a.REFNO, a.M_NAME;
If you don't know the number of columns in the output, then you will need dynamic SQL or to do the formatting at the application layer.

Transforming rows having dynamic dates into columns

Need to convert this:
DATE VALUE
18-DEC-15 2877544
17-DEC-15 2974276
16-DEC-15 4862020
into
18-DEC-15 17-DEC-15 16-DEC-15
2877544 2974276 4862020
The dates are updated everyday and I need to pick only the latest 3 dates and convert them into columns with the corresponding values below.
I tried using Pivots but I'm able to create the column headers dynamically.
Try this
WITH pivot_data AS (
select date, value from table)
SELECT *
FROM pivot_data
PIVOT (
max(value) --<-- pivot_clause
FOR table --<-- pivot_for_clause
);
A SQL query has well-defined column names, so for variable column names you need dynamic SQL. But, you can do what you want with fixed names:
select max(case when seqnum = 1 then value end) as MostRecentDay,
max(case when seqnum = 2 then value end) as SecondMostRecentDay,
max(case when seqnum = 3 then value end) as ThirdMostRecentDay
from (select t.*, dense_rank() over (order by date desc) as seqnum
from t
) t;
You could even add three columns specifying the date values:
select max(case when seqnum = 1 then value end) as MostRecentDay,
max(case when seqnum = 2 then value end) as SecondMostRecentDay,
max(case when seqnum = 3 then value end) as ThirdMostRecentDay,
max(case when seqnum = 1 then date end) as MostRecentDate,
max(case when seqnum = 2 then date end) as SecondMostRecentDate,
max(case when seqnum = 3 then date end) as ThirdMostRecentDate
from (select t.*, dense_rank() over (order by date desc) as seqnum
from t
) t;
Use Conditional Aggregate to pivot the data
select MAX(case when "DATE" = '18-DEC-15' then VALUE END) as '18-DEC-15',
MAX(case when "DATE" = '17-DEC-15' then VALUE END) as '17-DEC-15',
MAX(case when "DATE" = '16-DEC-15' then VALUE END) as '16-DEC-15'
from yourtable
Note : since dates are unknown, you need to use EXECUTE IMMEDIATE