How to get name of column for use in SELECT clause - sql

I have a table in an Access DB that has columns for each of the 12 months of the year. The columns have names of "1" to "12". That is to say, the names of the columns are just the number of the month it represents. The columns contain numbers as data, and I need to sum the columns for the months remaining in the year. For example, right now we're in September, so I'd need the SELECT clause to sum the values in columns (months) 9 through 12. This must be able to dynamically sum up the relevant months, so that next month (Oct) Sep will be excluded and only 10 through 12 are summed.
How can I reference the name of the column in the SELECT clause to be able to perform a test on it. I need something like the following:
IIf(Table1.[1].ColumnName >= Month(Now), Table1.[1], 0)
+ IIf(Table1.[2].ColumnName >= Month(Now), Table1.[2], 0)
...
+ IIf(Table1.[12].ColumnName >= Month(Now), Table1.[12], 0)
This would be one approach, but, in passing, if there's a better way to do this, please let me know as well.
I've seen other posts on SO that discuss returning all column names for a table. That is not what I need here. I need to return the column name and perform tests on it within a SELECT clause. Thanks.
EDIT
I understand that this structure (having the data across 12 different columns) is not the greatest. This is not a new DB that I'm setting up. It is an old DB that I've inherited, so I can't make changes to the structure.

Not sure I understood your question, but it would seem the major issue is the fact that you store values in different columns instead of something like .
One solution could be to use union in a subquery to make the columns available to the outer select and use that to filter, sum and group by.
If you have a table looking like:
id (number, primary key), 1..12 (number, month values)
Then a query like this should work (this is for month 1-5 only):
SELECT id, SUM(MonthValue) AS SumFutureMonths
FROM (
SELECT 1 AS MonthNo, id, YourTable.[1] AS MonthValue FROM YourTable
union all
SELECT 2 AS MonthNo, id, YourTable.[2] AS MonthValue FROM YourTable
union all
SELECT 3 AS MonthNo, id, YourTable.[3] AS MonthValue FROM YourTable
union all
SELECT 4 AS MonthNo, id, YourTable.[4] AS MonthValue FROM YourTable
union all
SELECT 5 AS MonthNo, id, YourTable.[5] AS MonthValue FROM YourTable
)
WHERE MonthNo > MONTH(NOW())
GROUP BY id
Whether it would perform well I can't say - it depends on your data, but the retrieving all data in the table for a union can be a costly operation. Anyway, please try it out to see if it works.

I don't know if this works in Access; but in MS SQL you can write a string and execute it..
For example
#sqlStr = "Select 9, 10, 11, 12 from table"
Exec(#sqlStr)
And a string would be fairly easy to manipulate to contain relevant info..
But another question - why would you have all your data in each column instead of different rows? That way you could really easily get the data you want and show it the way you would want to..
UPDATE:
DECLARE #i int
SET #i = MONTH(getdate())
DECLARE #str nvarchar(max)
SET #str = 'SELECT '
WHILE #i <= 12
BEGIN
SET #str = #str + cast(#i as nvarchar(max))+ ','
SET #i = #i + 1
END
SET #str = LEFT(#str, len(#str) - 1) + ' FROM TABLE'
Exec(#str)

Related

Query multiple tables that have date pattern appended to their names

Dears,
We have a database that creates a new table for each new day. The naming of the tables follows this pattern: History_tbl_[year]_[month]_[day]. A sample name of the tables for the last 5 days for example is:
History_tbl_2021_10_02
History_tbl_2021_10_01
History_tbl_2021_09_30
History_tbl_2021_09_29
History_tbl_2021_09_28
My goal is to be able to query all the tables from a given date range at once. I can manually select the tables with union all, but it takes lots of time especially if I want to do a long date range. Is there a better way to solve this?
Note: Unfortunately, I don't have the privilege to change the structure and make all data being stored in a single table.
One bare-bones method to generate a list of unioned queries and dynamically execute them would be as follows, tweak as necessary:
declare #from date='20211001', #to date='20211004', #sql nvarchar(max)='';
with d as (
select DateAdd(day, number, #from) dt
from master..spt_values
where type = 'P'
and DateAdd(day, number, #from) <= #to
)
select #sql=String_Agg(qry,' ')
from (
select 'select col1, col2, col3 from History_tbl_'
+ Concat(Year(dt),'_',Right(Concat('0',month(dt)),2),'_', Right(Concat('0',Day(dt)),2))
+ Iif( dt=Max(dt) over() ,'', ' union all') qry
from d
)x
exec sp_executesql #sql
Note that the CTE generates the date range on the fly, ideally you would use a permanent calendar table

SQL Server recent rows

I'm sure this is easy but I have googled a lot and searched.
Ok, I have a table WITHOUT dates etc with 100000000000000000 records.
I want to see the latest entries, i.e.
Select top 200 *
from table
BUT I want to see the latest entries. Is there a rowidentifier that I could use in a table?
ie
select top 200 *
from table
order by rowidentifer Desc
Thanks
Is there a row.identifier that i could use in a table ie set top 200 * from table order by row.identifer Desc
As already stated in the comment's, there is not. The best way is having an identity, timestamp or some other form of identifying the record. Here is an alternative way using EXCEPT to get what you need, but the execution plan isn't the best... Play around with it and change as needed.
--testing purposes...
DECLARE #tbl TABLE(FirstName VARCHAR(50))
DECLARE #count INT = 0
WHILE (#count <= 12000)
BEGIN
INSERT INTO #tbl(FirstName)
SELECT
'RuPaul ' + CAST(#count AS VARCHAR(5))
SET #count += 1
END
--adjust how many records you would like, example is 200
SELECT *
FROM #tbl
EXCEPT(SELECT TOP (SELECT COUNT(*) - 200 FROM #tbl) * FROM #tbl)
--faster than above
DECLARE #tblCount AS INT = (SELECT COUNT(*) FROM #tbl) - 200
SELECT *
FROM #tbl
EXCEPT(SELECT TOP (#tblCount) * FROM #tbl)
On another note, you could create another Table Variable that has an ID and other columns, then you could insert the records you would need. Then you can perform other operations against the table, for example OrderBy etc...
What you could do
ALTER TABLE TABLENAME
ADD ID INT IDENTITY
This will add another column to the table "ID" and automatically give it an ID. Then you have an identifier you can use...
Nope, in short, there is none, if you don`t have a column dedicated as one (ie. an IDENTITY, or a SEQUENCE, or something similar). If you did, then you could get an ordered result back.

Parsing a string in SQL with If statement

I have a table with a string in some columns values that tells me if I should delete the row....however this string needs some parsing to understand whether to delete or not.
What is the string: it tells me the recurrence of meetings eg everyday starting 21st march for 10 meetings.
My table is a single column called recurrence:
Recurrence
-------------------------------
daily;1;21/03/2015;times;10
daily;1;01/02/2016;times;8
monthly;1;01/01/2016;times;2
weekly;1;21/01/2016;times;4
What to do: if the meetings are finished then remove the row.
The string is of the following format
<frequency tag>;<frequency number>;<start date>;times;<no of times>
For example
daily;1;21/03/2016;times;10
everyday starting 21st march, for 10 times
Does anybody know how I would calculate if the string indicates all meetings are in past? I want a select statement that tells me if the recurrence values are in past - true or false
I added one string ('weekly;1;21/05/2016;times;4') that definitely must not be deleted to show some output. At first try to add to temp table `#table1' all data from your table and check if all is deleted well.
DECLARE #table1 TABLE (
Recurrence nvarchar(max)
)
DECLARE #xml xml
INSERT INTO #table1 VALUES
('daily;1;21/03/2016;times;10'),
('daily;1;21/03/2015;times;10'),
('daily;1;01/02/2016;times;8'),
('monthly;1;01/01/2016;times;2'),
('weekly;1;21/01/2016;times;4'),
('weekly;1;21/05/2016;times;4')
SELECT #xml= (
SELECT CAST('<s><r>' + REPLACE(Recurrence,';','</r><r>') + '</r><r>'+ Recurrence+'</r></s>' as xml)
FROM #table1
FOR XML PATH ('')
)
;WITH cte as (
SELECT t.v.value('r[1]','nvarchar(10)') as how,
t.v.value('r[2]','nvarchar(10)') as every,
CONVERT(date,t.v.value('r[3]','nvarchar(10)'),103) as since,
t.v.value('r[4]','nvarchar(10)') as what,
t.v.value('r[5]','int') as howmany,
t.v.value('r[6]','nvarchar(max)') as Recurrence
FROM #xml.nodes('/s') as t(v)
)
DELETE t
FROM #table1 t
LEFT JOIN cte c ON c.Recurrence=t.Recurrence
WHERE
CASE WHEN how = 'daily' THEN DATEADD(day,howmany,since)
WHEN how = 'weekly' THEN DATEADD(week,howmany,since)
WHEN how = 'monthly' THEN DATEADD(month,howmany,since)
ELSE NULL END < GETDATE()
SELECT * FROM #table1
Output:
Recurrence
-----------------------------
weekly;1;21/05/2016;times;4
(1 row(s) affected)

Finding strings with duplicate letters inside

Can somebody help me with this little task? What I need is a stored procedure that can find duplicate letters (in a row) in a string from a table "a" and after that make a new table "b" with just the id of the string that has a duplicate letter.
Something like this:
Table A
ID Name
1 Matt
2 Daave
3 Toom
4 Mike
5 Eddie
And from that table I can see that Daave, Toom, Eddie have duplicate letters in a row and I would like to make a new table and list their ID's only. Something like:
Table B
ID
2
3
5
Only 2,3,5 because that is the ID of the string that has duplicate letters in their names.
I hope this is understandable and would be very grateful for any help.
In your answer with stored procedure, you have 2 mistakes, one is missing space between column name and LIKE clause, second is missing single quotes around search parameter.
I first create user-defined scalar function which return 1 if string contains duplicate letters:
EDITED
CREATE FUNCTION FindDuplicateLetters
(
#String NVARCHAR(50)
)
RETURNS BIT
AS
BEGIN
DECLARE #Result BIT = 0
DECLARE #Counter INT = 1
WHILE (#Counter <= LEN(#String) - 1)
BEGIN
IF(ASCII((SELECT SUBSTRING(#String, #Counter, 1))) = ASCII((SELECT SUBSTRING(#String, #Counter + 1, 1))))
BEGIN
SET #Result = 1
BREAK
END
SET #Counter = #Counter + 1
END
RETURN #Result
END
GO
After function was created, just call it from simple SELECT query like following:
SELECT
*
FROM
(SELECT
*,
dbo.FindDuplicateLetters(ColumnName) AS Duplicates
FROM TableName) AS a
WHERE a.Duplicates = 1
With this combination, you will get just rows that has duplicate letters.
In any version of SQL, you can do this with a brute force approach:
select *
from t
where t.name like '%aa%' or
t.name like '%bb%' or
. . .
t.name like '%zz%'
If you have a case sensitive collation, then use:
where lower(t.name) like '%aa%' or
. . .
Here's one way.
First create a table of numbers
CREATE TABLE dbo.Numbers
(
number INT PRIMARY KEY
);
INSERT INTO dbo.Numbers
SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number > 0;
Then with that in place you can use
SELECT *
FROM TableA
WHERE EXISTS (SELECT *
FROM dbo.Numbers
WHERE number < LEN(Name)
AND SUBSTRING(Name, number, 1) = SUBSTRING(Name, number + 1, 1))
Though this is an old post it's worth posting a solution that will be faster than a brute force approach or one that uses a scalar udf (which generally drag down performance). Using NGrams8K this is rather simple.
--sample data
declare #table table (id int identity primary key, [name] varchar(20));
insert #table([name]) values ('Mattaa'),('Daave'),('Toom'),('Mike'),('Eddie');
-- solution #1
select id
from #table
cross apply dbo.NGrams8k([name],1)
where charindex(replicate(token,2), [name]) > 0
group by id;
-- solution #2 (SQL 2012+ solution using LAG)
select id
from
(
select id, token, prevToken = lag(token,1) over (partition by id order by position)
from #table
cross apply dbo.NGrams8k([name],1)
) prep
where token = prevToken
group by id; -- optional id you want to remove possible duplicates.
another burte force way:
select *
from t
where t.name ~ '(.)\1';

sql combination

I have a sql query which select the best combination of hours with the heighest weight, It uses a recursive CTE to do find all the combinations of hours as shown below
Declare #EmpClasses table(Id int identity(1,1),ClassNum int,ClassWeight int,ClassHours int)
Declare #HoursReq int;
SET #HoursReq = 20;
INSERT INTO #EmpClasses VALUES(1001,10,10),(1002,9,5),(1003,8,4),(1004,7,3),(1005,6,2),(1006,5,2),(1007,4,1);
--INSERT INTO #EmpClasses VALUES(1001,2,2),(1002,2,2),(1003,2,2),(1004,2,2),(1005,2,2),(1006,2,2),(1007,2,2),(1008,2,2),(1009,2,2),(1010,2,2);
--INSERT INTO #EmpClasses VALUES(1011,2,2),(1012,2,2),(1013,2,2),(1014,2,2),(1015,2,2),(1016,2,2),(1017,2,2),(1018,2,2),(1019,2,2),(1020,2,2);
--INSERT INTO #EmpClasses VALUES(1021,2,2),(1022,2,2),(1023,2,2),(1024,2,2),(1025,2,2),(1026,2,2),(1027,2,2),(1028,2,2),(1029,2,2),(1030,2,2);
WITH cte (Id,comIds,Total,TotalWeight)
AS
(
SELECT Id
,Cast(ClassNum as varchar(max)) + ','
,ClassHours
,ClassWeight
FROM #EmpClasses
UNION ALL
SELECT ec.Id
,cte.comIds + Cast(ec.ClassNum as varchar(max)) + ','
,cte.Total + ec.ClassHours
,cte.TotalWeight + ec.ClassWeight
FROM #EmpClasses AS ec JOIN cte ON ec.Id < cte.Id
)
SELECT top(1)comids,Total,TotalWeight
FROM cte
Where Total = #HoursReq
order by TotalWeight desc
However the problems lies, when the number of rows increases to iterate. It takes a very long time.
Note:- Please uncomment above lines to check for the issue
Any help regarding this will be greatly appreciated
The number of rows generated in cte is 2^n-1 where n is the number of records in the #EmpClasses table.
So, if you use just 7 records, you generate 127 records.
However if you use 37 records, as in your full example, it needs to generate 137.438.953.471 rows. That takes some time, even if you have a fast server.
You can verify if you change the selection at the bottom of the cte, with a simple
select * from cte