Search all of the text fields in a sql server database for a particular value… with LINQ (Pad)


So, the question was posed on how to do this with LINQPad, and thus with LINQ. Here is one way. This is basically the same thing I did in my last post about searching for this kind of value. This also shows how to return an arbitrary result from a linq query to a very generic class object for later processing. Note that I was not able to use params because the table name couldn't be a param. I'm sure there is probably some more elegant way to do this, but this works fine and hopefully this is one of those scripts/snippets of code that you never have to use. Since you can just do something like this with SQL, I'm not sure of the usefulness of this code in a realistic setting, but I suppose if you needed to throw up a utility page and you already had a LINQ data context in your app for other stuff, you could do something in a pinch using executequery. I'm personally not a big fan of using executequery as it stands today. I generally just take it as a sign I need to switch to sql, but for something like this (I mean, I could be joining on a file with a list of text to look for right?), the versatility is nice to have. =)

 

void Main()

{

    this.ExecuteCommand("set transaction isolation level read uncommitted");

    string FindThis = "some text to find";

    string q = @"select

        '[' + table_catalog + '].[' + table_schema + '].[' + table_name + ']' [t]

        , '[' + column_name    + ']' [c]

    from

        INFORMATION_SCHEMA.COLUMNS

    where

        data_type in ('nvarchar','nchar','varchar','char')

        and character_maximum_length >= len({0})";

    string e = @"select case when exists(select * from {0} with (nolock) where {1} like '%{2}%') then 1 else 0 end";

        

    var matches = from a in this.ExecuteQuery<tc>(q,FindThis).Where (t =>

        this.ExecuteQuery<int>(e.Replace("{0}",t.t).Replace("{1}",t.c).Replace("{2}",FindThis)

            ).Single().Equals(1)

        ) select a;

            

    matches.Dump();

}

 

class tc

{

    public string t{get;set;}

    public string c{get;set;}

}

 

 

author: Roy Ashbrook | posted @ Monday, August 30, 2010 6:32 PM | Feedback (0)

Search all of the text fields in sql server database for a particular value


-- try and do dirty reads, and turn off the record counting unless you want

-- spam in the middle of your prints below. you can also just do a select

-- instead of a print.

set transaction isolation level read uncommitted

set nocount on

 

-- vars to hold the commands to queue up and the command var for the one we'll run

-- identity is used to preserve order in case we decide to have a particular order

-- otherwise it will just run in the order we create the commands

declare @command nvarchar(max), @id int

declare @commands table(id int IDENTITY(1,1) PRIMARY KEY CLUSTERED, command nvarchar(max))

 

-- strint to look for

declare @StringToFind varchar(50)

select @StringToFind = 'some text you want to find'

 

-- just using a CTE here to make it a bit more readable, this could

-- simply be a select statement. this will build the commands to execute

with a (t, c) as

(

    select

        '[' + table_catalog + '].[' + table_schema + '].[' + table_name + ']' [t]

        , '[' + column_name    + ']' [c]

    from

        INFORMATION_SCHEMA.COLUMNS

    where

        data_type in ('nvarchar','nchar','varchar','char')

        and character_maximum_length >= len(@StringToFind)

)

insert @commands(command)

select

    'if exists(select * from ' + t + ' with (nolock ) where ' + c + ' like ''%' + @StringToFind + '%'')'

        + ' print ''found ' + @StringToFind + ' in '+ t + ' in column ' + c + ''''

from a

 

-- if you just want to see the commands you can run the following

-- select * from @commands

 

-- if you don't want to run the commands, comment out the following.

-- run commands

while (select count(*) from @commands) != 0

    begin

        select top 1 @id=id,@command=command from @commands order by id asc

        exec( @command )

        delete from @commands where id=@id

    end

 

 

Obviously you can adjust this to taste for different fields, types, outputs, etc. Originally I simply used a select statement and just copy/pasted the gen'd code, but since I was asked for something that would 'do' this, I figured I would go ahead and queue up and exec the commands. I also switched to a CTE just to provide a little separation between 'get the list of tables/columns' and 'produce the list of commands' in case that was needed. The set trans up at the top should remove the need for the with nolocks, but I figured just in case someone snipped that code, they would think twice before issuing broader locking selects in a scope like this so I dropped some nolock hints in. Of course, if you *must* have clean reads, you would have to remove that. This should, according to docs issue and honor no locks during its life but as always, beware when running something like this in prod.

 

Enjoy!

author: Roy Ashbrook | posted @ Monday, August 30, 2010 3:23 PM | Feedback (0)

LINQPad and Active Directory


I am a huge fan of LINQPad. Something on my wish-list, as far as my own personal knowledge, for a longtime with it was how to query Active Directory information when I needed it. This was more for convenience because quite often I need to look up some AD info when I am looking at a scrub for some data regarding a user. Normally I just net use or dsquery/dsget etc. The key is System.DirectoryServices.AccountManagement. Note this is a .NET 3.5 and 4.0 only library so earlier than that, sorry. You aren't out of luck, but the other ways seem to be less cumbersome to me than this very easy way. Be mindful of the fact that performing a .Dump() on one of these objects will produce a TON of stuff. So doing something like User.GetGroups().Dump() is probably a Very Bad IdeaTM. Also, don't forget that since we're using LINQ to Objects, you will have case sensitivity issues unless you deal with it yourself.

  1. Edit the Query Properties in LINQPad:

     

  2. Add a reference to System.DirectoryServices.AccountManagement.dll:

     

  3. Update the Additional Namespace Imports tab to reflect the namespace you want to import:

     

  4. PROFIT!! =P

    using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "MyDomain))

        using(UserPrincipal usr = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, "MyUserID"))

            usr.Dump();

author: Roy Ashbrook | posted @ Friday, August 20, 2010 4:13 PM | Feedback (0)

set transaction isolation level read only uncommitted in LINQ


apparently you can use a transaction scope for this and just wrap everything that way. but to me that seems more complicated than just enabling it with execute command.

 

some LINQ code like (note that ‘this’ is a data context):

this.ExecuteCommand("set transaction isolation level read uncommitted");
MyTable.Take(5).Dump();

Produces this sql code:

set transaction isolation level read uncommitted
GO

SELECT TOP 5 <fieldnames…> FROM [MyTable] AS [t0]
GO

 

That’s what I want to see, so yay. =)

 

 

some links:

http://madprops.org/blog/linq-to-sql-and-nolock-hints/

http://www.hanselman.com/blog/GettingLINQToSQLAndLINQToEntitiesToUseNOLOCK.aspx

 

ps. trying to use livewriter today. so far, not excited about it and still like word more =P hopefully i’ll get elightened.

author: Roy Ashbrook | posted @ Friday, August 13, 2010 10:43 AM | Feedback (0)

Space used by tables, long form


After my recent post, I had a couple of conversations and an email exchange with a colleague of mine and I was reminded of the fact that I went originally out to search for my 'original' table space used script. I couldn't find it online (even though I found it on my hdd) and that prompted me to put together the newer method. Which is considerably shorter. But here is my original script which essentially I wrote by looking at what was in sp_spaceused and just formatting it more to my liking.

 

-- view all table sizes

-- author: royashbrook - royashbrook@yahoo.com

-- april 2005

 

select

so.id as [OBJECT_ID],

so.name as [OBJECT_NAME],

coalesce(j_rows.rows,0) as [ROWCOUNT],

coalesce(j_ru.sum_reserved,0) * cast(m.low as dec) / 1024 as [RESERVED (KB)],

d.data * cast(m.low as dec) / 1024 as [DATA (KB)],

(coalesce(j_ru.sum_used,0) - d.data) * cast(m.low as dec) / 1024 as [INDEX (KB)],

(coalesce(j_ru.sum_reserved,0) - coalesce(j_ru.sum_used,0)) * cast(m.low as dec) / 1024 as [UNUSED (KB)]

from

sysobjects so

join master.dbo.spt_values m

on m.number = 1 and m.type = 'E'

-- rows

left join sysindexes j_rows

on j_rows.indid < 2 and j_rows.id = so.id

-- reserved: sum(reserved) where indid in (0, 1, 255)

-- index: sum(used) where indid in (0, 1, 255) - data

-- unused: sum(reserved) - sum(used) where indid in (0, 1, 255)

left join

(

select

id

, sum(reserved) as sum_reserved

, sum(used) as sum_used

from

sysindexes

where

indid in (0, 1, 255)

group by

id

) j_ru

on j_ru.id = so.id

-- data: sum(dpages) where indid < 2

-- + sum(used) where indid = 255 (text)

left join

(

select

j_dpages.id

, coalesce(j_dpages._sum,0) +

coalesce(j_used._sum,0) [data]

from

(

select

id

, sum(dpages) [_sum]

from

sysindexes

where

indid < 2

group by

id

) j_dpages

left join

(

select

id

, sum(used) [_sum]

from

sysindexes

where

indid = 255

group by

id

) j_used

on j_used.id = j_dpages.id

) d

on d.id = so.id

where

objectproperty(so.id, N'IsUserTable') = 1

author: Roy Ashbrook | posted @ Tuesday, August 03, 2010 12:08 PM | Feedback (0)

sp_spaceused for all the tables in your database in one table


Why? Well, sometimes you just want it all in one table.

create table #t (

    tablename sysname,row_count int,

    reserved varchar(50),data varchar(50),

    index_size varchar(50),unused varchar(50))

exec sp_msforeachtable 'insert #t exec sp_spaceused ''?'''

select * from #t

drop table #t

author: Roy Ashbrook | posted @ Thursday, July 29, 2010 11:13 PM | Feedback (0)

How to regain administrative access to your own sql server


Question: how do I regain sysadmin role for myself if I am a local admin, but not a sql sysadmin currently. This scenario can happen for a variety of reasons, but if you are the owner and administrator of a box, how do you get to where you can manage it again? It's pretty simple. Basically stop sql, then start it in single user mode, then add yourself, reset sa, whatever. A sample script is below. This script makes several assumptions.

  1. Your server doesn't take longer than 10 seconds to start, otherwise modify the sleep line (or just do this manually and wait for it to start)
  2. You already have an account with access on the server. If you don't you can add it or just reset the SA password. I find that usually the people interested in doing this at least have access to a database on the server so you can just grant that group admin access for the duration of the recovery.
  3. You don't mind killing the sql server task ungracefully like I'm doing below. If you do mind that, you can always just do all this one step at a time instead of copy and pasting it or something.
  4. You're server binary is in Q:\MSSQL\MSSQL.9999\MSSQL\Binn\sqlservr. I assume that yours is not which is why I am using this in the script so change the path to wherever yoru sqlservr.exe is actually located.
  5. You already had sql agent running. If you didn't, you can drop the last line. If you did the first line will automatically end it with the /y

 

net stop mssqlserver /y

start "sqlserver" Q:\MSSQL\MSSQL.9999\MSSQL\Binn\sqlservr -x -m

sleep 10

sqlcmd -E -S localhost -d master -Q "EXEC master..sp_addsrvrolemember @loginame = N'domainname\somedomaingroupthatalreadyhasanacccount', @rolename = N'sysadmin'"

taskkill /IM sqlservr.exe

net start mssqlserver

net start "SQL Server Agent (MSSQLSERVER)"

author: Roy Ashbrook | posted @ Thursday, July 29, 2010 1:10 PM | Feedback (0)

ah yes…. LINQ is case sensitive….


So I am a huge fan of LINQPad. I use it all the time for ad hoc data processing mostly because of the .Dump() method which allows me to copy and paste something a little prettier than what I get outta mgmt studio or a simple command line processor. It's really pretty much the same to me, but for some reason normal people seem *wayyyyy* happier when they get a little blue box with some lines instead of just text with tabs or commas =P

Anyway, I was fixing to import some data from a file and I was joining it against the db, but there were some values to lookup that in the import were text files. No big deal, just join the values and get the ids based on the names. This did work, but I was trying to shrink the data being sent/retrieved over the wire so I did a pre-fetch on the distinct values and joined them on the ids on the lookup table. This worked fine, but I found that when I later went to join the object I retrieved against my file imported object, things didn't match. This made me a little crazy for about an hour or two as I kept coming back to it and writing queries that would show more and more of the data. It was particularly annoying because usually I just import this data into a table and do it all in sql, but I figured at some point I wanted this to be some kind of GUI so I'd do it in linqpad in preparation of using EF or Linq2Sql or whatever more .net'ey way of doing things. So since I have done this about 50 times in the past with no problems, it was irritating that it was causing me any time at all. Trying to 'research' what was up with my data caused me to need to do an outer join which is its own particular flavor of silliness (IMO) in LINQ.

So I let it sit overnight. Came back the next day and realized what was up. I was doing a linq2objects compare really because I was crossing the retrieved data (an anon type with just my id and name I joined with the distinct values from my import file) with my import data (a anon type with the stripped lines from my import csv) and linq2objects is case sensitive. For me, I am simply dealing with basic imports with all English chars etc so .ToUpper() on both sides of the compare fixed everything. But if you have to deal with larger international type imports with different globalization/culture concerns, you might need something else.

Anyway, here's a little code snip even though this almost doesn't really warrant one. There are some other ways to 'join' this data, but this is basically going to get the TableInDB and then It will do the compare client side, that's why it fails without the case insensitivity.

var data_with_keys_to_import =

    from f in DataImportedFromFile

        join t in TableInDB on d.TextValueForKey.ToUpper() equals t.TextValueForKey.ToUpper()

    select new {...};

 

And here's a few of links about case sensitivity stuff if you can't find your own on google/bing. I found the last one the most poignant.

  1. http://stackoverflow.com/questions/841226/case-insensitive-string-compare-in-linq-to-sql
  2. http://blogs.msdn.com/b/michkap/archive/2007/10/01/5218976.aspx
  3. http://blogs.msdn.com/b/michkap/archive/2005/03/10/391564.aspx

 

 

author: Roy Ashbrook | posted @ Tuesday, July 20, 2010 2:29 PM | Feedback (0)

Showing status in c# loop using the modulus operator (%)


I needed to take some records in one file, do some stuff with the record, then put the results in another file. The 'do stuff' part was taking a bit longer than I thought it would so I wanted to see status. This sample illustrates taking 1000 records from a source file, uppercasing each line, putting it in another file, and letting us know every 100 lines.

using(TextWriter tw = new StreamWriter(@"uppercase.txt",false))

{

    int i = 0;

    var q = File.ReadAllLines(@"lowercase.txt").Take(1000);

    foreach(var v in q)

    {

        tw.WriteLine("{0}", v.ToUpper());

//print status on every 100th record

        if ((i % 100) == 0) Console.WriteLine("{0}/{1} processed", i.ToString(),q.Count().ToString());

        i++;

    }

    Console.WriteLine("{0}/{1} processed", q.Count(),q.Count().ToString());

}

author: Roy Ashbrook | posted @ Friday, June 25, 2010 9:08 AM | Feedback (0)

ROW_NUMBER() AND PIVOT AND TEST DATA, OH MY!


So the other day I got the following question about reporting on some log data:

The data being loaded is a bunch of machine log files. (E.g. MachineName Status Timestamp) The status is a simple Start or Stop. My question is can I use Datediff between sequential records to determine elapsed time between start and stop? I know this would work if the start and stop data were in the same record and I believe this will work between sequential records but I'm fuzzy on what's best and putting it into action

Below is the end result of my answer. There are three basic parts of the solution below:

  1. Build some test data and stage it
  2. Group the data and make some keys to re-join it
  3. Write a little report to show what we are after
  4. (bonus) a little pivot table to show some other groupings on the data =D

   

-- our test data will simulate 10 machines that make 100 total

--    connections per day that last between 1 and 300 seconds.

-- this sample log is stores three items, the box name, what

--    happened (start/stop) and a timedatestamp

-- this data is supposed to happen in a sequence mostly. there

--    could be overlapping time events with the machines, but since

--    we would just eliminate that in a sort by machine later anyway

--    i am not going to worry about that and we will do everything in

--    one long sequence. our start date will be getdate() if there is

--    no date already in the table.

   

-- this table var will hold our 'raw' data

declare @t table

(

box char(5)

, evt varchar(5)

, tds datetime

)

-- loop to fill in our temp table. basically just some vars and some loops with random info

declare @i int,@ii int, @box varchar(5), @tds datetime

select @i = 0,@ii = 0

while @i < 10

begin

    select @box = 'box' + CAST(@i as char(1))

    while @ii < 100

    begin

        select @tds = coalesce(MAX(tds),getdate()) from @t

        insert @t values(@box, 'start', @tds )

        insert @t values(@box, 'stop', dateadd(SECOND,RAND()*300,@tds))

        set @ii = @ii + 1

    end

    select @i = @i + 1, @ii = 0

end

   

-- this is our 'staging' table that we'd be importing this data into for analysis

declare @tt table

(

box char(12)

, evt varchar(5)

, tds datetime

, group_key int

, primary key clustered (box, evt, group_key)

)

   

-- this represents our load. we are adding a row_number line to provide

--    a unique extra key for each set of box/evt combos

insert @tt

select box, evt, tds

, row_number() over (partition by box,evt order by tds) [group_key]

from @t

   

-- now we can select from our loaded data table twice, once for each

--    of our two events start/stop and join based on the group_key which will

--    already be unique for each set based on our load above.

-- this shows us the data how we want to see it. or at least how i was

--    originally asked for it.

select

starts.box

, starts.tds [start]

, stops.tds [stop]

, DATEDIFF(ss,starts.tds,stops.tds) [duration]

from

@tt starts

join @tt stops on

stops.evt = 'stop'

and stops.box = starts.box

and stops.group_key = starts.group_key

where

starts.evt = 'start'

   

-- here is a little pivot table to show you how many connections

--    in the timeframes took certain lengths of time to complete.

-- you could do this a number of ways, but i don't get to use

--    the pivot command enough so i figured i'd tack it on here =P

select

    *

from

    (

    select

        box, case

            when duration < 30 then '<30s'

            when duration between 30 and 60 then '<60s'

            when duration between 60 and 120 then '<120s'

            else '120+s'

            end [d]

    from

        (

        select

            starts.box

            , DATEDIFF(ss,starts.tds,stops.tds) [duration]

        from

            @tt starts

            join @tt stops on

                stops.evt = 'stop'

                and stops.box = starts.box

                and stops.group_key = starts.group_key

        where

             starts.evt = 'start'

        ) ii

    ) i

PIVOT

(

count(d)

FOR d IN ([<30s],[<60s],[<120s],[120+s])

) p

author: Roy Ashbrook | posted @ Tuesday, June 22, 2010 12:00 AM | Feedback (0)