How to split an SQL Table into half and send the other half of the rows to new columns with SQL Query? - sql

Country Percentage
India 12%
USA 20%
Australia 15%
Qatar 10%
Output :
Country1 Percentage1 Country2 Percentage2
India 12% Australia 15%
USA 20% Qatar 10%
For example there is a table Country which has percentages, I need to divide the table in Half and show the remaining half (i.e. the remaining rows) in the new columns. I've also provided the table structure in text.

First, this type of operation should be done at the application layer and not in the database. That said, it can be an interesting exercise to see how to do this in the database.
I would use conditional aggregation or pivot. Note that SQL tables are inherently unordered. Your base table has no apparent ordering, so the values could come out in any order.
select max(case when seqnum % 2 = 0 then country end) as country_1,
max(case when seqnum % 2 = 0 then percentage end) as percentage_1,
max(case when seqnum % 2 = 1 then country end) as country_2,
max(case when seqnum % 2 = 1 then percentage end) as percentage_2
from (select c.*,
(row_number() over (order by (select null)) - 1) as seqnum
from country c
) c
group by seqnum / 2;

Try this
declare #t table
(
Country VARCHAR(20),
percentage INT
)
declare #cnt int
INSERT INTO #T
VALUES('India',12),('USA',20),('Australia',15),('Quatar',12)
select #cnt = count(1)+1 from #t
;with cte
as
(
select
SeqNo = row_number() over(order by Country),
Country,
percentage
from #t
)
select
*
from cte c1
left join cte c2
on c1.seqno = (c2.SeqNo-#cnt/2)
and c2.SeqNo >= (#cnt/2)
where c1.SeqNo <= (#cnt/2)

My variant
SELECT 'A' Country,1 Percentage INTO #Country
UNION ALL SELECT 'B' Country,2 Percentage
UNION ALL SELECT 'C' Country,3 Percentage
UNION ALL SELECT 'D' Country,4 Percentage
UNION ALL SELECT 'E' Country,5 Percentage
;WITH numCTE AS(
SELECT
*,
ROW_NUMBER()OVER(ORDER BY Country) RowNum,
COUNT(*)OVER() CountOfCountry
FROM #Country
),
set1CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum<=CEILING(CountOfCountry/2.)
),
set2CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum>CEILING(CountOfCountry/2.)
)
SELECT
s1.Country,s1.Percentage,
s2.Country,s2.Percentage
FROM set1CTE s1
LEFT JOIN set2CTE s2 ON s1.RowNum=s2.RowNum
DROP TABLE #Country

I just wanted to try something. I have used the function OFFSET. It does the requirement i think for your sample data, but dont know if its bulletproof all the way:
SQL Code
declare #myt table (country nvarchar(50),percentage int)
insert into #myt
values
('India' ,12),
('USA' ,20),
('Australia' ,15),
('Qatar' ,10),
('Denmark',10)
DECLARE #TotalRows int
SET #TotalRows = (select CEILING(count(*) / 2.) from #myt);
WITH dataset1 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET 0 rows FETCH FIRST #TotalRows ROWS ONLY
) z
)
,dataset2 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET #TotalRows rows FETCH NEXT #TotalRows ROWS ONLY
) z
)
SELECT * FROM dataset1 a LEFT JOIN dataset2 b ON a.rn = b.rn
Result

Assuming you want descending alphabetic country names, but the left column is determined by where India is located in the result:
with CoutryCTE as (
select c.*
, row_number() over (order by country)-1 as rn
from country c
)
, Col as (
select rn % 2 as num from CoutryCTE
where Country = 'India'
)
select max(case when rn % 2 = Col.num then country end) as country_1
, max(case when rn % 2 = Col.num then percentage end) as percentage_1
, max(case when rn % 2 <> Col.num then country end) as country_2
, max(case when rn % 2 <> Col.num then percentage end) as percentage_2
from CoutryCTE
cross join Col
group by rn / 2
;
SQLFiddle Demo
| country_1 | percentage_1 | country_2 | percentage_2 |
|-----------|--------------|-----------|--------------|
| India | 12% | Australia | 15% |
| USA | 20% | Qatar | 10% |
nb: this is extremely similar to an earlier answer by Gordon Linoff

Related

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

How do I get the count of distinct values in the first row of the result

I want to check if the colour and city are multiple for a document for the highest amount. if yes, I want to set a bit as 1 and if not, it should be 0
Sample data:
Code doc year amount colour city
AB 123 2021 485 Red Paris
AB 123 2021 416 Red Paris
AB 123 2021 729 Red London
AB 123 2021 645 Red Bengaluru
Expected output:
I want the output in one row
Code Doc Year Amount Colour City Col_Mul City_Mul
AB 123 2021 729 Red London 0 1
Amount, Colour and city should be the maximum one.
What I tried:
To get the data in one row, I used the row number and ordered by the maximum amount and selected the data where the row number is one. But after that I used dense rank for the Colour and City column. But I didn't get the expected output.
You can use CROSS APPLY and get data as given below:
Thanks #Gayani for test data.
select TOPROW.*,case when T1.colorcount > 1 THEN 1 else 0 end as Multi_color,
case when T2.citycount > 1 THEN 1 else 0 end as Multi_city
from
(SELECT TOP 1 * FROM tes_firstRow
order by amount desc) as toprow
cross apply
(
SELECT count(distinct color) from tes_firstrow WHERE doc = toprow.doc
) as t1(colorcount)
cross apply
(
SELECT count(distinct city) from tes_firstrow WHERE doc = toprow.doc
) as t2(citycount)
Code
doc
year
amount
color
City
Multi_color
Multi_city
AB
123
2021
729
RED
LONDON
0
1
There you go. Weird requirement though...
SELECT T.Code, Doc, Year, MAX(T.Amount) Amount,
(SELECT TOP 1 Colour FROM T as X WHERE Amount = MAX(T.Amount)) Colour,
(SELECT TOP 1 City FROM T as X WHERE Amount = MAX(T.Amount)) City,
CASE WHEN COUNT(DISTINCT T.Colour) > 1 THEN 1 ELSE 0 END as Col_Mul,
CASE WHEN COUNT(DISTINCT T.City) > 1 THEN 1 ELSE 0 END as City_Mul
FROM T
GROUP BY T.Code, Doc, Year
I think you just want window functions combined with conditional aggregation:
select code, doc, year, max(amount),
max(case when seqnum = 1 then color end) as color,
max(case when seqnum = 1 then city end) as city,
(case when seqnum = 1 and color_count > 1 then 1 else 0 end) as color_dup,
(case when seqnum = 1 and city_count > 1 then 1 else 0 end) as city_dup,
from (select t.*,
row_number() over (partition by code, doc, year order by amount desc) as seqnum,
count(*) over (partition by code, doc, year, color) as color_count,
count(*) over (partition by code, doc, year, city) as city_count
from t
) t
group by code, doc, year;
I'm not actually sure if you want 1 when the value is duplicated or not, so those values might be backwards.
I hope this code sample will help you with this.
Please try the below code and let me know if it helps with what you need. I used temporary tables here. You can use any of the technology to build the logic. CTE (Common table expressions) or derived tables.
CREATE TABLE tes_firstRow
(
Code varchar(100)
, doc int
, [year] int
, amount int
, color varchar(100)
, City varchar(100)
)
insert into tes_firstRow values ('AB', 123,2021,485,'RED','PARIS')
insert into tes_firstRow values ('AB', 123,2021,416,'RED','PARIS')
insert into tes_firstRow values ('AB', 123,2021,729,'RED','LONDON')
insert into tes_firstRow values ('AB', 123,2021,645,'RED','BENGALURU')
SELECT
RANK() OVER (PARTITION BY Code, doc,[year] ORDER BY amount DESC) AS [rank]
,Code
,doc
,[year]
,amount
,color
,City
INTO #temp_1
FROM tes_firstRow
SELECT
[#temp_1].[Code]
,[#temp_1].[doc]
,[#temp_1].[year]
,[#temp_1].[amount]
,[#temp_1].[color]
,[#temp_1].[City]
, (Select COUNT(Distinct [#temp_1].[color] ) where [#temp_1].[rank] = 1 ) as Col_Mul
, (Select COUNT(Distinct [#temp_1].[City]) where [#temp_1].[rank] = 1) as City_Mul
,1 as City_Mul
FROM #temp_1
WHERE #temp_1.[rank] = 1
group by [#temp_1].[Code]
,[#temp_1].[doc]
,[#temp_1].[year]
,[#temp_1].[amount]
,[#temp_1].[color]
,[#temp_1].[City]
,[#temp_1].[rank]
DROP TABLE #temp_1
Result:

Find nearest SUM amount from table SQL SERVER

Query
Declare #table1 TABLE (accountno varchar(max), saved_amount decimal)
INSERT INTO #table1 VALUES
('001',25),
('002',5)
Declare #table2 TABLE (accountno varchar(max), payamount decimal,ilno int)
INSERT INTO #table2 VALUES
('001',10,1),
('001',10,2),
('001',10,3),
('001',10,4),
('002',10,1),
('002',10,2);
WITH aa
AS (
SELECT a.*
,b.ilno
,b.payamount
,SUM(payamount) OVER (
PARTITION BY a.accountno ORDER BY CAST(a.accountno AS INT)
,ilno
) AS total_amount
FROM #table1 a
LEFT JOIN #table2 b ON a.accountno = b.accountno
)
,bb
AS (
SELECT accountno
,MAX(ilno) AS ilno
FROM aa
WHERE saved_amount >= total_amount
GROUP BY accountno
)
SELECT a.* FROM aa a INNER JOIN bb b on a.accountno =b.accountno AND a.ilno = b.ilno
Result
accountno | saved_amount | ilno | payamount | total_amount
----------------------------------------------------------
001 | 25 | 2 | 10 | 20
Expected Result
accountno | saved_amount | ilno | payamount | total_amount
----------------------------------------------------------
001 | 25 | 2 | 10 | 20
002 | 5 | 1 | 10 | 10
What I want is
If saved_amount is less than the first ilno, then get the first ilno else
get the highest ilno where saved_amount>=total_amount
You have a running total that you compare with the saved amount. You want the highest running total that doesn't exceed the saved amount. But in case even the initial pay amount exceeds the saved amount already, you want to default to this record. So the main task is to find a way of ranking the records. In my query I do it like this:
Prefer records where the running total does not exceed the saved amount.
Then look at the abolute of their difference and take the smallest.
There are certainly other ways that achieve the same. Maybe even methods that you find more readable. Then just adjust the order by clause in the ranking query.
with summed as
(
select
t1.*,
from #table1 t1
join
(
select
ilno,
payamount,
sum(payamount) over (partition by accountno order by ilno) as total_amount
from #table2
) on t2.accountno = t1.accountno
)
, ranked as
(
select summed.*,
row_number() over (partition by accountno
order by case when saved_amount >= total_amount then 1 else 2 end,
abs(saved_amount - total_amount)
) as rn
)
select *
from ranked
where rn = 1;
This is not the "nearest sum", as you said in the title, but the one that obeys the specified rules. So with a saved amount of 100 and paid amounts of first 1 and then 100, you'd get the record with a total of 1 (which is 99 less than the saved amount) and not the one with a total of 101 (which is only 1 more than the saved amount).
Other way to solve using flags:
first calculated one flag to point if saved_amount >= payamount for current row
calculated three more flags:
group_flag to show is there a case where saved_amount >= payamount for the given accountno
[min_ilno] and [max_ilno] for given account
Having this flags, the final result set is calculated easily. Here is the code:
WITH DataSource AS
(
SELECT a.*
,b.ilno
,b.payamount
,SUM(payamount) OVER (PARTITION BY a.accountno ORDER BY ilno) AS total_amount
,IIF(a.saved_amount >= SUM(payamount) OVER (PARTITION BY a.accountno ORDER BY ilno), 1, 0) AS [flag]
FROM #table1 a
LEFT JOIN #table2 b
ON a.accountno = b.accountno
),
DataSourceFinal AS
(
SELECT *
,MAX(flag) OVER (PARTITION BY accountno) as [group_flag]
,MIN(IIF(flag = 0 ,ilno, NULL)) OVER (PARTITION BY accountno) as [min_ilno]
,MAX(IIF(flag = 1 ,ilno, NULL)) OVER (PARTITION BY accountno) as [max_ilno]
FROM DataSource
)
SELECT accountno, saved_amount, ilno, payamount, total_amount
FROM DataSourceFinal
WHERE ([group_flag] = 1 AND [ilno] = [max_ilno])
OR ([group_flag] = 0 AND [ilno] = [min_ilno]);
and the output:

sql : get consecutive group 'n' rows (could be inbetween)

Below is my theater table:
create table theater
(
srno integer,
seatno integer,
available boolean
);
insert into theater
values
(1, 100,true),
(2, 200,true),
(3, 300,true),
(4, 400,false),
(5, 500,true),
(6, 600,true),
(7, 700,true),
(8, 800,true);
I want a sql which should take input as 'n' and returns me the first 'n' consecutive available seats, like
if n = 2 output should be 100,200
if n = 4 output should be 500,600,700,800
NOTE: I am trying to build an query for postgres 9.3
In SQL-Server you can do It in following:
DECLARE #num INT = 4
;WITH cte AS
(
SELECT *,COUNT(1) OVER(PARTITION BY cnt) pt FROM
(
SELECT tt.*
,(SELECT COUNT(srno) FROM theater t WHERE available <> 'true' and srno < tt.srno) AS cnt
FROM theater tt
WHERE available = 'true'
) t1
)
SELECT TOP (SELECT #num) srno, seatno, available
FROM cte
WHERE pt >= #num
OUTPUT
srno seatno available
5 500 true
6 600 true
7 700 true
8 800 true
This will find the available seats. written for sqlserver 2008+:
DECLARE #num INT = 4
;WITH CTE as
(
SELECT
srno-row_number() over (partition by available order by srno) grp,
srno, seatno, available
FROM theater
), CTE2 as
(
SELECT grp, count(*) over (partition by grp) cnt,
srno, seatno, available
FROM CTE
WHERE available = 'true'
)
SELECT top(#num)
srno, seatno, available
FROM CTE2
WHERE cnt >= #num
ORDER BY srno
Result:
srno seatno available
5 500 1
6 600 1
7 700 1
8 800 1
-- naive solution without window using functions
-- [the funny +-100 constants are caused by
-- "consecutive" seats being 100 apart]
-- -------------------------------------------
WITH bot AS ( -- start of an island --
SELECT seatno FROM theater t
WHERE t.available
AND NOT EXISTS (select * from theater x
where x.available AND x.seatno = t.seatno -100)
)
, top AS ( -- end of an island --
SELECT seatno FROM theater t
WHERE t.available
AND NOT EXISTS (select * from theater x
where x.available AND x.seatno = t.seatno +100)
)
, mid AS ( -- [start,end] without intervening gaps --
SELECT l.seatno AS bot, h.seatno AS top
FROM bot l
JOIN top h ON h.seatno >= l.seatno
AND NOT EXISTS (
SELECT * FROM theater x
WHERE NOT x.available
AND x.seatno >= l.seatno AND x.seatno <= h.seatno)
)
-- all the consecutive ranges
-- [ the end query should select from this
-- , using "cnt >= xxx" ]
SELECT bot, top
, 1+(top-bot)/100 AS cnt
FROM mid;
Result:
bot | top | cnt
-----+-----+-----
100 | 300 | 3
500 | 800 | 4
(2 rows)
thanks guys, but i have done achieved it like below,
select srno, seatno from (
select *, count(0) over (order by grp) grp1 from (
select t1.*,
sum(group_flag) over (order by srno) as grp
from (
select *,
case
when lag(available) over (order by srno) = available then null
else 1
end as group_flag
from theater
) t1 ) tx ) tr where tr.available=true and tr.grp1 >= 2 limit 2

TSQL getting max and min date with a seperate but not unique record

example table:
test_date | test_result | unique_ID
12/25/15 | 100 | 50
12/01/15 | 150 | 75
10/01/15 | 135 | 75
09/22/14 | 99 | 50
04/10/13 | 125 | 50
I need to find the first and last test date as well as the test result to match said date by user. So, I can group by ID, but not test result.
SELECT MAX(test_date)[need matching test_result],
MIN(test_date) [need matching test_result],
unique_id
from [table]
group by unique_id
THANKS!
Create TABLE #t
(
test_date date ,
Test_results int,
Unique_id int
)
INSERT INTO #t
VALUES ( '12/25/15',100,50 ),
( '12/01/15',150,75 ),
( '10/01/15',135,75 ),
( '09/22/14',99,50 ),
( '04/10/13',125,50 )
select 'MinTestDate' as Type, a.test_date, a.Test_results, a.Unique_id
from #t a inner join (
select min(test_date) as test_datemin, max(test_date) as test_datemax, unique_id from #t
group by unique_ID) b
on a.test_date = b.test_datemin
union all
select 'MaxTestDate' as Type, a.test_date, a.Test_results, a.Unique_id from #t a
inner join (
select min(test_date) as test_datemin, max(test_date) as test_datemax, unique_id from #t
group by unique_ID) b
on a.test_date = b.test_datemax
I would recommend window functions. The following returns the information on 2 rows per id:
select t.*
from (select t.*,
row_number() over (partition by unique_id order by test_date) as seqnum_asc,
row_number() over (partition by unique_id order by test_date desc) as seqnum_desc
from table t
) t;
For one row, use conditional aggregation (or pivot if you prefer):
select unique_id,
min(test_date), max(case when seqnum_asc = 1 then test_result end),
max(test_date), max(case when seqnum_desc = 1 then test_result end)
from (select t.*,
row_number() over (partition by unique_id order by test_date) as seqnum_asc,
row_number() over (partition by unique_id order by test_date desc) as seqnum_desc
from table t
) t
group by unique_id;
Consider using a combination of self-joins and derived tables:
SELECT t1.unique_id, minTable.MinOftest_date, t1.test_result As Mintestdate_result,
maxTable.MaxOftest_date, t2.test_result As Maxtestdate_result
FROM TestTable AS t1
INNER JOIN
(
SELECT Min(TestTable.test_date) AS MinOftest_date,
TestTable.unique_ID
FROM TestTable
GROUP BY TestTable.unique_ID
) As minTable
ON (t1.test_date = minTable.MinOftest_date
AND t1.unique_id = minTable.unique_id)
INNER JOIN TestTable As t2
INNER JOIN
(
SELECT Max(TestTable.test_date) AS MaxOftest_date,
TestTable.unique_ID
FROM TestTable
GROUP BY TestTable.unique_ID
) AS maxTable
ON t2.test_date = maxTable.MaxOftest_date
AND t2.unique_ID = maxTable.unique_ID
ON minTable.unique_id = maxTable.unique_id;
OUTPUT
unique_id MinOftest_date Mintestdate_result MaxOftest_date Maxtestdate_result
50 4/10/2013 125 12/25/2015 100
75 10/1/2015 135 12/1/2015 150